Class: Shipping::ItemMd5Extractor
- Inherits:
-
BaseService
- Object
- BaseService
- Shipping::ItemMd5Extractor
- Defined in:
- app/services/shipping/item_md5_extractor.rb
Overview
Takes an item, looks at shipping dimensions, create a packing entry
Race Condition Note:
This service can be triggered by multiple sources simultaneously:
- Item.after_save callback when box dimensions change
- Delivery state transitions (picking -> pending_ship_labels, any -> shipped)
- Pre-pack workflow calling set_packaged_items_md5_hash
To prevent ActiveRecord::RecordNotUnique errors when two processes try to
create the same Packing (same md5 + service_type), we use upsert which is
atomic at the database level.
Defined Under Namespace
Classes: Result
Instance Attribute Summary collapse
-
#ignore_timestamp ⇒ Object
readonly
Returns the value of attribute ignore_timestamp.
Attributes inherited from BaseService
Instance Method Summary collapse
-
#initialize(options = {}) ⇒ ItemMd5Extractor
constructor
A new instance of ItemMd5Extractor.
- #process(item) ⇒ Object
Methods inherited from BaseService
#log_debug, #log_error, #log_info, #log_warning, #logger, #tagged_logger
Constructor Details
#initialize(options = {}) ⇒ ItemMd5Extractor
Returns a new instance of ItemMd5Extractor.
26 27 28 29 |
# File 'app/services/shipping/item_md5_extractor.rb', line 26 def initialize( = {}) @ignore_timestamp = .delete(:ignore_timestamp).to_b super end |
Instance Attribute Details
#ignore_timestamp ⇒ Object (readonly)
Returns the value of attribute ignore_timestamp.
24 25 26 |
# File 'app/services/shipping/item_md5_extractor.rb', line 24 def @ignore_timestamp end |
Instance Method Details
#process(item) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 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 |
# File 'app/services/shipping/item_md5_extractor.rb', line 31 def process(item) result = Result.new logger.tagged("Item:#{item.id}") do # We only create packaging for goods # We only create packaging if shipping dimensions are present? (just in case validation didn't catch it) unless item.is_goods? && item.box1_defined? result. = msg = "Item #{item.id} is not a good or has no shipping dimensions" logger.warn msg return result end md5_result = Shipping::Md5HashItem.process(item) logger.info "item md5: #{md5_result.md5}, item string: #{md5_result.items_string}" result.item_md5 = md5_result.md5 # Create a packing for this single item service_type = Packing.service_types[item.shipping_class] raise "Unable to match item shipping class #{item.shipping_class}" unless service_type # Skip when a higher-authority fresh row already exists (Packing#authoritative_over?). # Consolidates the previous two ad-hoc checks (recency vs `item.updated_at` and # the hard-coded from_delivery/from_manual_entry block list) into the same # origin-rank + freshness gate that DeliveryMd5Extractor uses. existing_packing = Packing.find_by(md5: md5_result.md5, service_type:) if existing_packing && ! && existing_packing.(:from_item) result. = msg = "Existing #{existing_packing.origin} packing #{existing_packing.id} outranks incoming from_item (last updated #{existing_packing.updated_at}); skip saving" logger.info msg return result end # Build packing attributes contents = if item.ships_in_single_box? md5_result.full_item_id_hash else item.get_multi_box_item_contents_item_id_hash_array end packdims = item.to_packdims packdim_contents = Packing.format_packdim_contents(md5: md5_result.md5, contents: contents, packdims: packdims) = item.updated_at || item.created_at # Use upsert to atomically insert or update - prevents race condition when # multiple workers try to create the same packing simultaneously Packing.upsert( { md5: md5_result.md5, service_type:, origin: Packing.origins[:from_item], packdims: packdims, contents:, packdim_contents: packdim_contents.to_json, item_id: item.id, delivery_id: nil, shipment_id: nil, created_at: , updated_at: Time.current }, unique_by: :index_packings_on_md5_and_service_type ) # Reload the packing to get the record (upsert doesn't return the full object) packing = Packing.find_by(md5: md5_result.md5, service_type:) unless packing msg = "Packing upsert did not persist (weight audit or DB); item #{item.id} md5=#{md5_result.md5}" result. = msg logger.warn msg return result end # Link kit item IDs for searchability (use union to avoid duplicates) packing.item_ids |= md5_result.kit_item_ids result.packings << packing logger.info "Packing #{packing.id} upserted successfully for item #{item.id}" end result end |