Class: Shipping::PackagingImporter
- Inherits:
-
BaseService
- Object
- BaseService
- Shipping::PackagingImporter
- Defined in:
- app/services/shipping/packaging_importer.rb
Overview
Converts Packaging records (warehouse-specific "N items fit in box X" rules)
into Packing records so they can be found by the MD5 history lookup in
Shipping::DeterminePackaging.
Background
Packaging is the legacy warehouse-assignment model: it records that up to N
units of a StoreItem fit into a specific WarehousePackage. Shipping::DeterminePackaging
used to fall back to Packaging when no Packing history existed. Now that the
fallback is the 3D-aware Shipping::PackingCalculator, Packaging is no longer
consulted at run-time — but its stacking knowledge (e.g. "25 thermal sheets
fit in a 36×24×3 box") cannot be derived from individual shipping_dimensions
alone, so it must be seeded into the Packing table.
Only records with number_items > 1 are processed. Single-item records are
already covered by Shipping::ItemMd5Extractor which fires on item save.
Quantity coverage
For each Packaging(store_item, warehouse_package, N=number_items):
- Single-box tier: qty = 1 … N (all fit in one warehouse box)
- When N > 50, only a representative subset is generated (1, steps of N/5)
- Multi-box tier: qty = 2N, 3N, 4N, 5N (2–5 full boxes)
Idempotency
Records are inserted but NOT updated if a Packing with the same md5 already
exists. Packings derived from real deliveries (from_delivery, from_shipment)
are authoritative and must not be overwritten.
Constant Summary collapse
- SINGLE_BOX_MAX_STEPS =
Maximum number of individual single-box qty steps to generate.
50- MAX_MULTI_BOX_TIERS =
Maximum number of additional multi-box tiers to generate (2×N … MAX_MULTI×N).
5
Class Method Summary collapse
- .process ⇒ Object
-
.process_one(packaging) ⇒ Object
Seed/refresh Packing records for a single Packaging row.
Instance Method Summary collapse
- #process ⇒ Object
-
#process_one(packaging) ⇒ Object
Seed/refresh Packing records for a single Packaging row.
Methods inherited from BaseService
#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #options, #tagged_logger
Constructor Details
This class inherits a constructor from BaseService
Class Method Details
.process ⇒ Object
43 44 45 |
# File 'app/services/shipping/packaging_importer.rb', line 43 def self.process new.process end |
.process_one(packaging) ⇒ Object
Seed/refresh Packing records for a single Packaging row.
Called by PackagingImportWorker after Packaging saves.
49 50 51 |
# File 'app/services/shipping/packaging_importer.rb', line 49 def self.process_one(packaging) new.process_one(packaging) end |
Instance Method Details
#process ⇒ Object
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 |
# File 'app/services/shipping/packaging_importer.rb', line 53 def process imported = 0 skipped = 0 # Only multi-item stacking records are missing from Packing. scope = Packaging .includes(:warehouse_package, store_item: :item) .where('number_items > 1') .where.not(warehouse_package_id: nil) scope.find_each(batch_size: 200) do |packaging| item = packaging.store_item&.item next unless item&.is_goods? wp = packaging.warehouse_package n = packaging.number_items service_type = Packing.service_types[item.shipping_class] next unless service_type ct_int = Shipment.container_types[wp.container_type] dims = [wp.length, wp.width, wp.height].sort.reverse.map { |d| d.to_f.ceil(1) } quantities_for(n).each do |qty| md5_result = Shipping::Md5HashItem.process(item, qty_override: qty) next if md5_result.md5.blank? packdims, contents = build_packing(item, dims, ct_int, qty, n) pdc = Packing.format_packdim_contents(md5: md5_result.md5, contents: contents, packdims: packdims) attrs = { md5: md5_result.md5, service_type: service_type, origin: Packing.origins[:from_item], packdims: packdims, contents: contents, packdim_contents: pdc.to_json, item_id: item.id, delivery_id: nil, shipment_id: nil, created_at: Time.current, updated_at: Time.current } existing = Packing.find_by(md5: attrs[:md5], service_type:) if existing&.origin.in?(%w[from_delivery from_manual_entry]) skipped += 1 next end # Only count as "imported" if we are creating a brand-new record. # Re-runs over already-imported records are silent updates, not new imports, # which makes the importer idempotent from a counting perspective. was_new = existing.nil? upsert_ok = Packing.upsert(attrs, unique_by: :index_packings_on_md5_and_service_type) imported += 1 if was_new && upsert_ok end end logger.info "Shipping::PackagingImporter: #{imported} imported, #{skipped} skipped (already existed)" { imported:, skipped: } end |
#process_one(packaging) ⇒ Object
Seed/refresh Packing records for a single Packaging row.
Returns the number of Packing records created/updated.
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 |
# File 'app/services/shipping/packaging_importer.rb', line 118 def process_one(packaging) return 0 unless packaging.number_items.to_i > 1 item = packaging.store_item&.item return 0 unless item&.is_goods? wp = packaging.warehouse_package return 0 unless wp n = packaging.number_items service_type = Packing.service_types[item.shipping_class] return 0 unless service_type ct_int = Shipment.container_types[wp.container_type] dims = [wp.length, wp.width, wp.height].sort.reverse.map { |d| d.to_f.ceil(1) } count = 0 quantities_for(n).each do |qty| md5_result = Shipping::Md5HashItem.process(item, qty_override: qty) next if md5_result.md5.blank? packdims, contents = build_packing(item, dims, ct_int, qty, n) pdc = Packing.format_packdim_contents(md5: md5_result.md5, contents: contents, packdims: packdims) attrs = { md5: md5_result.md5, service_type: service_type, origin: Packing.origins[:from_item], packdims: packdims, contents: contents, packdim_contents: pdc.to_json, item_id: item.id, delivery_id: nil, shipment_id: nil, created_at: Time.current, updated_at: Time.current } existing = Packing.find_by(md5: attrs[:md5], service_type:) next if existing&.origin.in?(%w[from_delivery from_manual_entry]) count += 1 if Packing.upsert(attrs, unique_by: :index_packings_on_md5_and_service_type) end logger.info "Shipping::PackagingImporter#process_one: #{count} record(s) for Packaging##{packaging.id}" count end |