Class: Packaging
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Packaging
- Includes:
- Models::Auditable
- Defined in:
- app/models/packaging.rb
Overview
== Schema Information
Table name: packagings
Database name: primary
id :integer not null, primary key
number_items :integer
created_at :datetime
updated_at :datetime
store_item_id :integer
warehouse_package_id :integer
Indexes
index_packagings_on_store_item_id (store_item_id)
index_packagings_on_warehouse_package_id (warehouse_package_id)
Foreign Keys
packagings_warehouse_package_id_fk (warehouse_package_id => warehouse_packages.id) ON DELETE => cascade
Constant Summary collapse
- DEFAULT_BOX_SIZE =
[20, 10, 6]
- PER_PACKAGE_WEIGHT_LIMIT =
70.0- LTL_FREIGHT_WASTED_SPACE_IN_PALLETS_FACTOR =
wasted space factor in packing the pallet compared to perfect packing
0.75- LTL_FREIGHT_MAX_PALLET_HEIGHT_IN_FT =
max of 5 feet or 60" per pallet
5.0- LTL_FREIGHT_MIN_PALLET_HEIGHT_IN_INCHES =
here we want every pallet to realistically be minimum 24 inches high
24.0- CRATE_PADDING =
3.5
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Instance Attribute Summary collapse
- #number_items ⇒ Object readonly
- #warehouse_package_id ⇒ Object readonly
Belongs to collapse
Methods included from Models::Auditable
Class Method Summary collapse
- .add_to_packages_hash(packages_hash, packaging, num_items) ⇒ Object
-
.by_store_id ⇒ ActiveRecord::Relation<Packaging>
A relation of Packagings that are by store id.
- .consolidate_package_entries(package_entries_arr, si_cache: {}) ⇒ Object
- .consolidate_packages(packages) ⇒ Object
- .fit_store_items_into_respective_packaging_legacy(packagings_by_store_item_id, number_by_store_item_id) ⇒ Object
- .fit_store_items_into_respective_packaging_new(packagings_by_store_item_id, number_by_store_item_id) ⇒ Object
-
.get_freight_pallet_packages_hash_from(line_items) ⇒ Object
Delegates to Shipping::FreightPalletCalculator (the authoritative implementation).
- .get_packaging_for_line_items(line_items:, store: nil, is_freight: false) ⇒ Object
- .use_shipping_dimensions?(li) ⇒ Boolean
Instance Method Summary collapse
- #check_dimensions ⇒ Object protected
- #enqueue_packing_reimport ⇒ Object protected
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::EventPublishable
Instance Attribute Details
#number_items ⇒ Object (readonly)
36 |
# File 'app/models/packaging.rb', line 36 validates :warehouse_package_id, :store_item, :number_items, presence: true |
#warehouse_package_id ⇒ Object (readonly)
36 |
# File 'app/models/packaging.rb', line 36 validates :warehouse_package_id, :store_item, :number_items, presence: true |
Class Method Details
.add_to_packages_hash(packages_hash, packaging, num_items) ⇒ Object
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
# File 'app/models/packaging.rb', line 331 def self.add_to_packages_hash(packages_hash, packaging, num_items) # get the info from the packaging store_item = packaging.store_item # puts "store_item.sku: #{store_item.sku}" warehouse_package_id = packaging.warehouse_package_id # puts "warehouse_package_id: #{warehouse_package_id}" num_items_per_package = packaging.number_items # puts "num_items_per_package: #{num_items_per_package}" per_store_item_usage = (1.0 / num_items_per_package).to_f # puts "per_store_item_usage: #{per_store_item_usage}" # add an entry unless it exists packages_hash[warehouse_package_id] = [] unless packages_hash[warehouse_package_id] # see if we can fill up any existing package num_items_to_pack_in_existing_packages = 0 # find any package entries that can take at least one item packages_hash[warehouse_package_id].each do |package_entry| # puts "package_entry: #{package_entry.inspect}" next unless package_entry[:fraction_leftover] >= per_store_item_usage # puts "package_entry[:fraction_leftover]: #{package_entry[:fraction_leftover]}" # take integral number that can fit in leftover space num_items_to_pack_in_this_package = (package_entry[:fraction_leftover] / per_store_item_usage).floor num_items_to_pack_in_this_package = num_items if num_items_to_pack_in_this_package > num_items # puts "num_items_to_pack_in_this_package: #{num_items_to_pack_in_this_package}" num_items_to_pack_in_existing_packages += num_items_to_pack_in_this_package # puts "num_items_to_pack_in_existing_packages: #{num_items_to_pack_in_existing_packages}" # update store items list, weight and fraction leftover package_entry[:store_items] << { store_item.id => { number: num_items_to_pack_in_this_package, per_store_item_usage: per_store_item_usage } } package_entry[:weight] += (num_items_to_pack_in_this_package * store_item.shipping_weight) package_entry[:fraction_leftover] -= (num_items_to_pack_in_this_package * per_store_item_usage).to_f # puts "added items: package_entry: #{package_entry.inspect}" end # figure out how many new packages we need, rounding up number_new_packages = ((num_items - num_items_to_pack_in_existing_packages) / num_items_per_package).ceil # puts "number_new_packages: #{number_new_packages}" num_items_remaining = (num_items - num_items_to_pack_in_existing_packages) # puts "num_items_remaining: #{num_items_remaining}" # create a package_entry for each one if number_new_packages > 1 (number_new_packages - 1).times do |_i| packages_hash[warehouse_package_id] << { store_items: [{ store_item.id => { number: num_items_per_package, per_store_item_usage: per_store_item_usage } }], weight: num_items_per_package * store_item.shipping_weight, fraction_leftover: 0.0 } num_items_remaining -= num_items_per_package # puts "added items: package_entry: #{packages_hash[warehouse_package_id].last.inspect}" # puts "num_items_remaining: #{num_items_remaining}" end end # for the last package figure out remainder return unless num_items_remaining > 0 packages_hash[warehouse_package_id] << { store_items: [{ store_item.id => { number: num_items_remaining, per_store_item_usage: per_store_item_usage } }], weight: (num_items_remaining * store_item.shipping_weight), fraction_leftover: (1.0 - (num_items_remaining * per_store_item_usage).to_f) } # puts "added items: package_entry: #{packages_hash[warehouse_package_id].last.inspect}" end |
.by_store_id ⇒ ActiveRecord::Relation<Packaging>
A relation of Packagings that are by store id. Active Record Scope
42 |
# File 'app/models/packaging.rb', line 42 scope :by_store_id, ->(store_id) { joins(:store_item).where(store_items: { store_id: store_id }) } |
.consolidate_package_entries(package_entries_arr, si_cache: {}) ⇒ Object
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'app/models/packaging.rb', line 241 def self.consolidate_package_entries(package_entries_arr, si_cache: {}) # puts "consolidate_package_entries: package_entries_arr: #{package_entries_arr.inspect}" # take smallest package entry out of the array return if package_entries_arr.length == 1 smallest_package_entry = package_entries_arr.delete_at(0) smallest_package = smallest_package_entry.first smallest_package_entry_packages = smallest_package_entry.last # puts "000000000 smallest_package: #{smallest_package.description}" # puts "000000000 smallest_package_entry_packages: #{smallest_package_entry_packages.inspect}" total_available_package_usage = package_entries_arr.sum { |peg| smallest_package.fits_inside?(peg.first) ? peg.last.sum { |pei| pei[:fraction_leftover] } : 0.0 } # puts "000000000 total_available_package_usage: #{total_available_package_usage}" consolidated_smallest_package_away = false smallest_package_entry_packages.sort_by { |s| s[:fraction_leftover] }.each do |spe| # puts "111111111 working on distributing items in: spe[:store_items].inspect: #{spe[:store_items].inspect}" spe[:store_items].each do |si| small_package_usage = si.values.first[:number].to_f * si.values.first[:per_store_item_usage] # puts "222222222 looking at store item: #{si.inspect}, small_package_usage: #{small_package_usage}" si_entry = si.values.first store_item_id = si.keys.first store_item = si_cache[store_item_id] || StoreItem.find(store_item_id) per_store_item_usage = si_entry[:per_store_item_usage] # see if we can even consolidate away this package next unless small_package_usage <= total_available_package_usage # puts "222222222 small_package_usage <= total_available_package_usage: #{small_package_usage} <= #{total_available_package_usage}" # iterate over the array package_entries_arr.each_with_index do |package_entry, _i| package = package_entry.first package_entry_packages = package_entry.last # puts "333333333 i: #{i}, package: #{package.description}" # puts "333333333 i: #{i}, package_entry_packages: #{package_entry_packages.inspect}" # see if we can fit the smallest package into the next biggest package # puts "333333333 smallest_package.fits_inside?(package): #{smallest_package.fits_inside?(package)}" next unless smallest_package.fits_inside?(package) # it fits inside available_package_usage = package_entry_packages.sum { |pei| pei[:fraction_leftover] } # puts "333333333 available_package_usage: #{available_package_usage}" next unless small_package_usage <= available_package_usage # puts "444444444 small_package_usage <= available_package_usage: #{small_package_usage} <= #{available_package_usage}" # see if we can move any items into the bigger package package_entry_packages.each_with_index do |pei, _j| # puts "444444444 looking at package entry #{i}, #{j}: #{pei.inspect}" next unless pei[:fraction_leftover] >= per_store_item_usage # puts "555555555 pei[:fraction_leftover] >= #{per_store_item_usage}" # take integral number that can fit in leftover space num_items_to_pack_in_this_package = (pei[:fraction_leftover] / per_store_item_usage).floor num_items_to_pack_in_this_package = si_entry[:number] if num_items_to_pack_in_this_package > si_entry[:number] # puts "555555555 num_items_to_pack_in_this_package: #{num_items_to_pack_in_this_package}" # update store items list, weight and fraction leftover psi = pei[:store_items].detect { |s| s.keys.first == store_item_id } if psi # puts "666666666 found store item entry in this package, adding to it" psi.values.first[:number] += num_items_to_pack_in_this_package else # puts "666666666 did not find store item entry in this package, creating it" pei[:store_items] << { store_item.id => { number: num_items_to_pack_in_this_package, per_store_item_usage: per_store_item_usage } } end pei[:weight] += (num_items_to_pack_in_this_package * store_item.shipping_weight) pei[:fraction_leftover] -= (num_items_to_pack_in_this_package * per_store_item_usage).to_f # puts "555555555 added items: pei: #{pei.inspect}" si_entry[:number] -= num_items_to_pack_in_this_package spe[:weight] -= (num_items_to_pack_in_this_package * store_item.shipping_weight) spe[:fraction_leftover] += (num_items_to_pack_in_this_package * per_store_item_usage).to_f # puts "555555555 removing items from smaller package, store_item entry: #{spe.inspect}" next unless si_entry[:number] == 0 spe[:store_items].delete(si) # puts "777777777 no more store items in entry, removing entry: #{spe[:store_items].inspect}" next unless spe[:store_items].empty? smallest_package_entry_packages.delete(spe) # puts "888888888 no more items in package, removing package: #{smallest_package_entry_packages.inspect}" if smallest_package_entry_packages.empty? consolidated_smallest_package_away = true # puts "999999999 consolidated_smallest_package_away = true" end end end end end Packaging.consolidate_package_entries(package_entries_arr, si_cache: si_cache) return if consolidated_smallest_package_away package_entries_arr << smallest_package_entry end |
.consolidate_packages(packages) ⇒ Object
227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'app/models/packaging.rb', line 227 def self.consolidate_packages(packages) consolidated_packages = {} wp_by_id = WarehousePackage.where(id: packages.keys).index_by(&:id) package_entries_arr = packages.collect { |id, data| [wp_by_id[id], data] }.sort_by { |parr| parr.first.volume } # Batch-load all StoreItem records referenced in package data for consolidation all_si_ids = packages.values.flat_map { |data_arr| data_arr.flat_map { |d| d[:store_items].map { |si| si.keys.first } } }.uniq si_cache = StoreItem.where(id: all_si_ids).index_by(&:id) Packaging.consolidate_package_entries(package_entries_arr, si_cache: si_cache) package_entries_arr.each do |package_entry| consolidated_packages[package_entry.first.id] = package_entry.last end consolidated_packages end |
.fit_store_items_into_respective_packaging_legacy(packagings_by_store_item_id, number_by_store_item_id) ⇒ Object
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 |
# File 'app/models/packaging.rb', line 386 def self.fit_store_items_into_respective_packaging_legacy(packagings_by_store_item_id, number_by_store_item_id) packages = {} packagings_by_store_item_id.each do |store_item_id, packagings| # basically for each store item, you fill the larger boxes until you have a small enough number to fit in the smaller boxes num_store_items = number_by_store_item_id[store_item_id] # get the packagings array and reverse sort in descending order of number_items that fit per package, we should end up at 1 item per package for the last one sorted_packagings = packagings.sort_by { |p| p.number_items }.reverse sorted_packagings.each_index do |i| # puts "!!!!! #{store_item.sku}, packagings: #{sorted_packagings[i].warehouse_package.description}: #{sorted_packagings[i].number_items}" if sorted_packagings[i].number_items == 1 # special case for package that can fit only one item # puts "111111 package #{sorted_packagings[i].warehouse_package.description} can handle only 1 of these items (please make sure: package.number_items: #{sorted_packagings[i].number_items} == 1?)" if num_store_items > 1 # here rather than use more than a single of the current package, put them all into the previous (presumably bigger) package # puts "121212 we have more than one item to deal with" if i > 0 && packages[sorted_packagings[i - 1].warehouse_package_id] # we have a larger package to put them in # puts "131313 we have a larger package to put them in: #{sorted_packagings[i-1].warehouse_package.description} which holds #{sorted_packagings[i-1].number_items}" Packaging.add_to_packages_hash(packages, sorted_packagings[i - 1], num_store_items) # puts "141414 Added package: #{sorted_packagings[i-1].warehouse_package_id} => #{packages[sorted_packagings[i-1].warehouse_package_id].inspect}" else # unless this is the largest package, whereupon we need as many as we need # puts "151515 we do NOT have a larger package to put them in" Packaging.add_to_packages_hash(packages, sorted_packagings[i], num_store_items) # puts "161616 Added package: #{sorted_packagings[i].warehouse_package_id} => #{packages[sorted_packagings[i].warehouse_package_id].inspect}" end elsif num_store_items == 1 # here we are going for a smaller package since it fits the one item rather than using a larger package # puts "171717 we have only one item to deal with" Packaging.add_to_packages_hash(packages, sorted_packagings[i], 1) # puts "181818 Added package: #{sorted_packagings[i].warehouse_package_id} => #{packages[sorted_packagings[i].warehouse_package_id].inspect}" end break else finished = false until finished # get the number of packages needed by dividing the number of items by the number that fit per package num_packages = (num_store_items / sorted_packagings[i].number_items).floor # puts "111 #{num_packages} of this package are required" if num_packages > 0 # this means we can at least fill one of these packages so add this package entry to packages with the number needed # get the number left over after filling the packages num_store_items %= sorted_packagings[i].number_items # puts "222 After filling #{num_packages} of this package, #{num_store_items} are left over" Packaging.add_to_packages_hash(packages, sorted_packagings[i], num_packages * sorted_packagings[i].number_items) # puts "333 Added package: #{sorted_packagings[i].warehouse_package_id} => #{packages[sorted_packagings[i].warehouse_package_id].inspect}" finished = true if num_store_items == 0 elsif num_store_items.positive? # we have less items than fit in the current box and at least one item to deal with # puts "444 We don't fill at least one of this package" # see if these fit in the next smaller package, if it exists if sorted_packagings[i + 1] and num_store_items <= sorted_packagings[i + 1].number_items # puts "555 We can fit #{num_store_items} items into package #{sorted_packagings[i+1].warehouse_package.description} which can handle #{sorted_packagings[i+1].number_items} of these items" # yes, so let iteration on the next sorted_packagings handle this by setting finished true finished = true end unless finished # no or this is the smallest box, so just use the current box # puts "888 No smaller packages to use, so we will fit #{num_store_items} items into package #{sorted_packagings[i].warehouse_package.description} which can handle #{sorted_packagings[i].number_items} of these items" Packaging.add_to_packages_hash(packages, sorted_packagings[i], num_store_items) # puts "999 Added package: #{sorted_packagings[i].warehouse_package_id} => #{packages[sorted_packagings[i].warehouse_package_id].inspect}" # puts "101010 We are done with this item" finished = true end break else finished = true break end end end end end packages end |
.fit_store_items_into_respective_packaging_new(packagings_by_store_item_id, number_by_store_item_id) ⇒ Object
213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'app/models/packaging.rb', line 213 def self.fit_store_items_into_respective_packaging_new(packagings_by_store_item_id, number_by_store_item_id) packages = {} packagings_by_store_item_id.each do |store_item_id, packagings| # basically for each store item, you fill the larger boxes until you have a small enough number to fit in the smaller boxes num_store_items = number_by_store_item_id[store_item_id] # get the packagings array and reverse sort in descending order of number_items that fit per package, we should end up at 1 item per package for the last one sorted_packagings = packagings.sort_by { |p| p.number_items }.reverse largest_packaging = sorted_packagings.first Packaging.add_to_packages_hash(packages, largest_packaging, num_store_items) # #puts "Added package: #{largest_packaging.warehouse_package_id} => #{packages[largest_packaging.warehouse_package_id].inspect}" end packages end |
.get_freight_pallet_packages_hash_from(line_items) ⇒ Object
Delegates to Shipping::FreightPalletCalculator (the authoritative implementation).
Kept for backward compatibility with any callers outside DeterminePackaging.
464 465 466 |
# File 'app/models/packaging.rb', line 464 def self.get_freight_pallet_packages_hash_from(line_items) Shipping::FreightPalletCalculator.call(line_items) end |
.get_packaging_for_line_items(line_items:, store: nil, is_freight: false) ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'app/models/packaging.rb', line 44 def self.get_packaging_for_line_items(line_items:, store: nil, is_freight: false) # First only goods to be sure line_items = line_items.select(&:is_goods?) # Short circuit this for freight return Packaging.get_freight_pallet_packages_hash_from(line_items) if is_freight # puts "get_packaging_for_line_items: store: #{store.id}" # puts "get_packaging_for_line_items: line_items.map{|li| [li.sku, li.quantity]}: #{line_items.map{|li| [li.sku, li.quantity]}}" # Here are the assumptions for this rather complex calculation: # # UPS (and other carriers) shipping charges are focused on weight and number of packages, two packages of 5 lbs will cost about twice a single package of 10 lbs, regardless of package dimensions, as long as the packages are not oversize (as UPS defines it: lxwxh > 5196 cubic inches; also if l+2(h+w) > 130 inches then a large item surcharge is applied) # So, to get an accurate rating, you need to take into account the way the warehouse will package the items, paying special attention to oversize items, and you can then simplify by combining non oversize items into one package # # here is how this calculation will proceed: # - take all oversize items grouped by packaging and fit them into their respective packages by using the number_items attribute # - consolidate, assuming if one package can fit into another, so can its store_items, proportional to the number_items it has in its packagings # - if there are any non-oversize items, find the item with the largest minimum package (that is the smallest package by volume in the packaging set for a store item), and assume all remaining items fit into the store_item's packaging ship_weights = [] ship_dimensions = [] os_store_items_list = line_items.find_all { |li| Packaging.use_shipping_dimensions?(li) }.collect { |li| li.store_item.id }.uniq.sort non_os_store_items_list = line_items.find_all { |li| Packaging.use_shipping_dimensions?(li) != true }.collect { |li| li.store_item.id }.uniq.sort # puts "os_store_items_list: #{os_store_items_list.inspect}" # puts "non_os_store_items_list: #{non_os_store_items_list.inspect}" os_packagings_by_store_item_id = {} unless os_store_items_list.empty? packagings = Packaging.includes(:warehouse_package).where(store_item_id: os_store_items_list) packagings = packagings.by_store_id(store&.id) if store os_packagings_by_store_item_id = packagings.sort_by { |p| p.warehouse_package.volume }.reverse.group_by(&:store_item_id) end non_os_packagings_by_store_item_id = {} unless non_os_store_items_list.empty? packagings = Packaging.includes(:warehouse_package).where(store_item_id: non_os_store_items_list) packagings = packagings.by_store_id(store&.id) if store non_os_packagings_by_store_item_id = packagings.sort_by { |p| p.warehouse_package.volume }.reverse.group_by(&:store_item_id) end # puts "os_packagings_by_store_item_id: #{os_packagings_by_store_item_id.inspect}" # puts "non_os_packagings_by_store_item_id: #{non_os_packagings_by_store_item_id.inspect}" os_number_by_store_item_id = {} non_os_number_by_store_item_id = {} line_items.each do |li| store_item_id = li.store_item.id if os_packagings_by_store_item_id[store_item_id] os_number_by_store_item_id[store_item_id] = 0 unless os_number_by_store_item_id[store_item_id] os_number_by_store_item_id[store_item_id] += li.quantity.abs end if non_os_packagings_by_store_item_id[store_item_id] non_os_number_by_store_item_id[store_item_id] = 0 unless non_os_number_by_store_item_id[store_item_id] non_os_number_by_store_item_id[store_item_id] += li.quantity.abs end end # puts "os_number_by_store_item_id: #{os_number_by_store_item_id.inspect}" # puts "non_os_number_by_store_item_id: #{non_os_number_by_store_item_id.inspect}" unless os_packagings_by_store_item_id.empty? # - take all oversize items grouped by packaging and fit them into their respective packages by using the number_items attribute # we have two strategies, try both and take the one that minimizes the number of packages and the volumes of those os_packages_1 = Packaging.fit_store_items_into_respective_packaging_new(os_packagings_by_store_item_id, os_number_by_store_item_id) os_packages_2 = Packaging.fit_store_items_into_respective_packaging_legacy(os_packagings_by_store_item_id, os_number_by_store_item_id) # - consolidate, assuming if one package can fit into another, so can its store_items, proportional to the number_items it has in its packagings # puts "!!!!!!!!!!!!!!!!!!os_packages_1: #{os_packages_1.inspect}" # puts "!!!!!!!!!!!!!!!!!!os_packages_2: #{os_packages_2.inspect}" consolidated_os_packages_1 = Packaging.consolidate_packages(os_packages_1) consolidated_os_packages_2 = Packaging.consolidate_packages(os_packages_2) # puts "!!!!!!!!!!!!!!!!!!consolidated_os_packages_1: #{consolidated_os_packages_1.inspect}" # puts "!!!!!!!!!!!!!!!!!!consolidated_os_packages_2: #{consolidated_os_packages_2.inspect}" # Batch-load all WarehousePackage records needed for both consolidation strategies all_wp_ids = (consolidated_os_packages_1.keys + consolidated_os_packages_2.keys).uniq wp_by_id = WarehousePackage.where(id: all_wp_ids).index_by(&:id) ship_weights_1 = [] ship_dimensions_1 = [] volume_1 = 0.0 consolidated_os_packages_1.each do |package_id, packages| warehouse_package = wp_by_id[package_id] packages.each do |package_data| ship_weights_1 << package_data[:weight] ship_dimensions_1 << [warehouse_package.length, warehouse_package.width, warehouse_package.height] volume_1 += warehouse_package.volume end end ship_weights_2 = [] ship_dimensions_2 = [] volume_2 = 0.0 consolidated_os_packages_2.each do |package_id, packages| warehouse_package = wp_by_id[package_id] packages.each do |package_data| ship_weights_2 << package_data[:weight] ship_dimensions_2 << [warehouse_package.length, warehouse_package.width, warehouse_package.height] volume_2 += warehouse_package.volume end end if ship_weights_2.length > ship_weights_1.length # puts "taking ship_weights_1 by number of packages: #{ship_weights_2.length} > #{ship_weights_1.length}" ship_weights.concat(ship_weights_1) ship_dimensions.concat(ship_dimensions_1) elsif ship_weights_1.length > ship_weights_2.length # puts "taking ship_weights_2 by number of packages: #{ship_weights_1.length} > #{ship_weights_2.length}" ship_weights.concat(ship_weights_2) ship_dimensions.concat(ship_dimensions_2) elsif volume_2 >= volume_1 # equal numbers, take that with least volume # puts "equal number of packages: #{ship_weights_1.length}" ship_weights.concat(ship_weights_1) ship_dimensions.concat(ship_dimensions_1) # puts "taking ship_weights_1 by volume: #{volume_2} >= #{volume_1}" elsif volume_1 > volume_2 # puts "taking ship_weights_2 by volume: #{volume_1} > #{volume_2}" ship_weights.concat(ship_weights_2) ship_dimensions.concat(ship_dimensions_2) end end unless non_os_packagings_by_store_item_id.empty? # - if there are any non-oversize items, find the item with the largest minimum package (that is the smallest package by volume in the packaging set for a store item), and assume all remaining items fit into the store_item's packaging # choose first available packaging's smallest warehouse package largest_minimum_non_os_package = non_os_packagings_by_store_item_id.first[1].sort_by { |p| p.warehouse_package.volume }.first.warehouse_package # iterate over all store_items non_os_packagings_by_store_item_id.each do |_store_item, packagings| # get smallest package in the packagings and see if it is larger than what we have so far smallest_packaging = packagings.sort_by { |p| p.warehouse_package.volume }.first largest_minimum_non_os_package = smallest_packaging.warehouse_package if largest_minimum_non_os_package.volume < smallest_packaging.warehouse_package.volume end non_os_si_by_id = StoreItem.where(id: non_os_number_by_store_item_id.keys).index_by(&:id) non_os_store_items_ship_weight = non_os_number_by_store_item_id.sum { |sid, num| non_os_si_by_id[sid].shipping_weight * num } if non_os_store_items_ship_weight <= PER_PACKAGE_WEIGHT_LIMIT # use this as our nominal maximum, though UPS per package limit is 150 LBS, we use 70 lbs for PER_PACKAGE_WEIGHT_LIMIT to support Purolator's maximum ship_dimensions << [largest_minimum_non_os_package.length, largest_minimum_non_os_package.width, largest_minimum_non_os_package.height] ship_weights << non_os_store_items_ship_weight else # simply divide into equal number of boxes num_boxes = (non_os_store_items_ship_weight / PER_PACKAGE_WEIGHT_LIMIT).ceil weight_per_box = non_os_store_items_ship_weight / num_boxes num_boxes.times do |_i| ship_dimensions << [largest_minimum_non_os_package.length, largest_minimum_non_os_package.width, largest_minimum_non_os_package.height] ship_weights << weight_per_box end end # puts "non_os_packagings_by_store_item_id: #{non_os_packagings_by_store_item_id.inspect}" # puts "largest_minimum_non_os_package: #{largest_minimum_non_os_package.inspect}" # puts "{:weights => ship_weights, :dimensions => ship_dimensions}: #{{:weights => ship_weights, :dimensions => ship_dimensions}.inspect}" end if ship_weights.empty? && ship_dimensions.empty? ship_weights = line_items.map { |li| li.item.base_weight } ship_dimensions = line_items.map { |li| li.item.shipping_dimensions } end { weights: ship_weights, dimensions: ship_dimensions, container_types: ship_weights.size.times.map { |_t| Shipment.container_types.keys.first } } # Shipment.container_types.keys.first is 'carton' end |
.use_shipping_dimensions?(li) ⇒ Boolean
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'app/models/packaging.rb', line 191 def self.use_shipping_dimensions?(li) return true if li.item&.dropship # use dropship item shipping dimensions # Here we are using the shipping_dimensions method on line items when this test passes*, to avoid unrealistic scenarios of 200 items in a single regular box. # * Note this test should apply to items that have sizable shipping dimensions q = li.quantity.abs.to_i logger.debug "use_shipping_dimensions, q: #{q}, li.item.shipping_dimensions: #{begin li.item.shipping_dimensions.inspect rescue StandardError 'n/a' end}, li.item.shipping_dimensions.inject(:*): #{begin li.item.shipping_dimensions.inject(:*) rescue StandardError 'n/a' end}" if li.oversize? or (li.item and li.item.shipping_dimensions and (li.item.shipping_dimensions.inject(:*) > 250.0 && li.item.shipping_dimensions.any? { |dim| dim > 20.0 })) true else false end end |
Instance Method Details
#check_dimensions ⇒ Object (protected)
609 610 611 612 |
# File 'app/models/packaging.rb', line 609 def check_dimensions errors.add(:base, 'Warehouse package dimensions are smaller the shipping dimensions defined for this item') if warehouse_package && !warehouse_package.can_contain_dimensions?(store_item.shipping_dimensions, 0.1) errors.empty? end |
#enqueue_packing_reimport ⇒ Object (protected)
605 606 607 |
# File 'app/models/packaging.rb', line 605 def enqueue_packing_reimport PackagingImportWorker.perform_async(id) end |
#store_item ⇒ StoreItem
Validations:
33 |
# File 'app/models/packaging.rb', line 33 belongs_to :store_item, inverse_of: :packagings, optional: true |
#warehouse_package ⇒ WarehousePackage
32 |
# File 'app/models/packaging.rb', line 32 belongs_to :warehouse_package, optional: true, counter_cache: true |