Class: Delivery
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Delivery
- Includes:
- Memery, Models::Auditable, Models::LegacyRateRequest, Models::Notable, Models::Packable, Models::Payable, Models::Profitable, Models::ShipMeasurable
- Defined in:
- app/models/delivery.rb
Overview
== Schema Information
Table name: deliveries
Database name: primary
id :integer not null, primary key
actual_shipping_cost :decimal(, )
bill_shipping_to_customer :boolean
carrier_bol :string
carrier_responses :jsonb
cod_collection_type :string(255)
do_not_recalculate :boolean
do_not_reserve_stock :boolean default(FALSE)
flag_failed_return_label :boolean default(FALSE), not null
freight_load_number :string
freight_order_number :string
future_release_date :date
incorrectly_packaged_ups_canada_order :boolean
incorrectly_packaged_ups_canada_order_fixed :boolean
is_newly_created_from_delivery_quote :boolean default(FALSE), not null
jde_shipping_stop_code :string(255)
label_instructions :string(255)
line_total :decimal(10, 2)
locked :boolean default(FALSE), not null
ltl_freight :boolean
ltl_freight_guaranteed :boolean
ltl_pro_number :string
manual_release_only :boolean
master_tracking_number :string(255)
md5_hash_override :string
old_shipping_cost :decimal(8, 2)
packaged_items_md5_hash :string(255)
pickup_confirmation_number :string
quoted_shipping_cost :decimal(8, 2)
saturday_delivery :boolean
ship_labeled_at :datetime
shipment_instructions :text
shipped_date :datetime
shipping_cost :decimal(, )
signature_confirmation :boolean
state :string(255)
suggested_packaging_text :text
tax_total :decimal(8, 2)
total :decimal(10, 2)
created_at :datetime
updated_at :datetime
destination_address_id :integer
order_id :integer
origin_address_id :integer
prepack_requester_id :integer
quote_id :integer
selected_shipping_cost_id :integer
shipengine_label_id :string
shipping_account_number_id :integer
shipping_option_id :integer
supplier_id :integer
Indexes
index_deliveries_on_carrier_bol (carrier_bol)
index_deliveries_on_destination_address_id (destination_address_id)
index_deliveries_on_order_id_and_id (order_id,id)
index_deliveries_on_order_id_and_state (order_id,state)
index_deliveries_on_origin_address_id (origin_address_id)
index_deliveries_on_quote_id (quote_id)
index_deliveries_on_shipped_date (shipped_date) USING brin
index_deliveries_on_shipping_account_number_id (shipping_account_number_id)
index_deliveries_on_shipping_option_id (shipping_option_id)
index_deliveries_on_state_and_id (state,id)
index_deliveries_on_supplier_id (supplier_id)
Foreign Keys
deliveries_destination_address_id (destination_address_id => addresses.id) ON DELETE => cascade
deliveries_order_id_fk (order_id => orders.id) ON DELETE => cascade
deliveries_origin_address_id (origin_address_id => addresses.id) ON DELETE => cascade
deliveries_quote_id_fk (quote_id => quotes.id) ON DELETE => cascade
deliveries_shipping_option_id_fk (shipping_option_id => shipping_options.id)
rubocop:disable Metrics/ClassLength -- 2265 lines; pre-existing god object, tracked
for decomposition (see the god-object-decomposition skill). Disabled at the class
level so reviews do not repeatedly flag acknowledged debt whenever Delivery is
touched.
Defined Under Namespace
Classes: ApplySelectedShippingCost, CalculateShippingOptions, GenerateLabels, GroupWwwShippingCosts, InvoicingHandler, PrintLabels, RetrieveShippingCosts, SetProperShippingCost
Constant Summary collapse
- SHIPPING_STATES =
States the delivery passes through during warehouse handling and carrier handoff.
%i[at_warehouse picking pending_pickup_confirm pending_ship_labels pending_carrier_confirm pending_ship_confirm shipped].freeze
- ANY_EMPLOYEE_CANCELABLE_STATES =
States any sales-rep / CRM employee may cancel from (no warehouse work in progress).
%i[quoting awaiting_po_fulfillment at_warehouse future_release service_ready_to_fulfill return_labels_complete].freeze
- WAREHOUSE_CANCELABLE_STATES =
States only warehouse staff can cancel from (mid-pick / mid-pack).
%i[pre_pack picking pending_pickup_confirm pending_ship_labels].freeze
- WAREHOUSE_STATES =
All warehouse-side states — used to gate "is this delivery in the warehouse's hands?".
%i[at_warehouse future_release pre_pack picking pending_ship_labels pending_carrier_confirm pending_ship_confirm pending_pickup_confirm pending_manifest_completion].freeze
- CANCELABLE_STATES =
Combined cancelable-states set (employee + warehouse).
(ANY_EMPLOYEE_CANCELABLE_STATES + WAREHOUSE_CANCELABLE_STATES).uniq
- CHECK_COLD_LEAD_NOTE =
Free-typed note marker used by call-recording transcribers when a customer
call meets the "cold lead" criteria. 'Check notes, contains COLD LEAD.'- SHIP_LABEL_HOLD_PERCENT_THRESHOLD =
Percentage by which actual shipping cost may exceed estimate before the
delivery is auto-held for review. 25.0- SHIP_LABEL_HOLD_DOLLAR_THRESHOLD =
Minimum absolute-dollar overage (in tandem with the percent threshold)
required before auto-holding for review. 25.0- SHIP_LABEL_HOLD_WEIGHT_PERCENT_THRESHOLD =
Percent variance between actual and estimated package weight before holding.
15.0- SHIP_LABEL_HOLD_WEIGHT_THRESHOLD =
Absolute lb variance threshold paired with the percent threshold above.
7.5- CARRIERS_REQUIRING_MANIFEST_COMPLETION =
Carriers that require a manifest-completion handoff after pickup
(Speedee). The state machine routes these throughpending_manifest_completion. ['SpeedeeDelivery'].freeze
- CROSS_BORDER_BROKER_INSTRUCTIONS =
Default broker instructions printed on cross-border BOL/CI documents.
'Broker: Willson International, Email: service@willsonintl.com'- CROSS_BORDER_COUNTRY_SPECIFIC_BROKER_TEXT =
Per-destination-country broker office address & phone, appended after
CROSS_BORDER_BROKER_INSTRUCTIONS on customs paperwork. { US: 'Address: 160 Wales Avenue, Suite 100, Tonawanda, NY, 14150, USA, Tel: 800-315-1918', CA: 'Address: 2345 Argentia Road, Suite 201, Mississauga, ON, L5N 8K4, CAN, Tel: 905-643-9054' }
- CARRIERS_TO_SEND_COMMERCIAL_INVOICES =
Carriers our system emails commercial-invoice copies to as soon as the
ship-confirm fires — keyed bydelivery.carrier(thereported_carrier
string) since each entry maps a single carrier name to a customs team.
Each entry:{ name:, customs_email: }.RlCarriersis the legacy XML-API adapter (Shipping::RlCarriers);
ShipengineRlCarriersis the modern ShipEngine LTL adapter against
the same R+L Carriers freight network and the same customs team. We
list both becausedelivery.carriercarries the adapter name, not
the underlying freight carrier — and we want the auto-email to fire
regardless of which adapter generated the label. R+L is in the
async-PRO group per ShipEngine's supported-carriers table, so for
the ShipEngine adapter the email fires via the
ltl_pro_number_changed? && invoiced?controller path (not at
invoicing time), once the warehouse enters the PRO at pickup. [ { name: 'RlCarriers', customs_email: 'transbordersolutiongroup@rlcarriers.com' }, { name: 'ShipengineRlCarriers', customs_email: 'transbordersolutiongroup@rlcarriers.com' }, { name: 'Freightquote' } ]
- CARRIERS_NAMES_TO_SEND_COMMERCIAL_INVOICES =
Just the carrier names from CARRIERS_TO_SEND_COMMERCIAL_INVOICES for
quickinclude?checks. CARRIERS_TO_SEND_COMMERCIAL_INVOICES.map{|carr| carr[:name]}
- FREIGHTQUOTE_CARRIERS_TO_SEND_COMMERCIAL_INVOICES =
Freightquote sub-carriers that need a separate CI email — keyed by SCAC
because Freightquote rates expose the actual carrier as a SCAC code rather
than a name. [ { key: "polaris", name: "Polaris Transport Carriers Inc.", carrierCode: "T408447", scac:"POLT", customs_email: 'customs@polaristransport.com' } ]
- FREIGHTQUOTE_CARRIER_SCACS_TO_SEND_COMMERCIAL_INVOICES =
Just the SCACs from FREIGHTQUOTE_CARRIERS_TO_SEND_COMMERCIAL_INVOICES.
FREIGHTQUOTE_CARRIERS_TO_SEND_COMMERCIAL_INVOICES.map{|fcarr| fcarr[:scac]}
- SUBQUERY_ORDER_STORE_ID =
SUBQUERY_ORDER_STORE_ID = %{
EXISTS(SELECT 1
FROM orders o
INNER JOIN parties cu ON cu.id = o.customer_id
INNER JOIN catalogs cat on cat.id = cu.catalog_id
WHERE o.id = deliveries.order_id
AND cat.store_id = :store_id)
} %{ EXISTS(SELECT 1 FROM orders o INNER JOIN parties cu ON cu.id = o.customer_id INNER JOIN catalogs cat on cat.id = cu.catalog_id WHERE o.id = deliveries.order_id AND o.order_type <> 'ST' AND cat.store_id = :store_id UNION ALL SELECT 1 FROM orders o WHERE o.id = deliveries.order_id AND o.order_type = 'ST' AND o.from_store_id = :store_id) }- SUBQUERY_QUOTE_STORE_ID =
SUBQUERY_ORDER_STORE_ID = %{
EXISTS(SELECT 1
FROM orders o
INNER JOIN parties cu ON cu.id = o.customer_id
INNER JOIN catalogs cat on cat.id = cu.catalog_id
WHERE o.id = deliveries.order_id
AND o.order_type <> 'ST'
AND cat.store_id = :store_id
UNION ALL
SELECT 1
FROM orders o
WHERE o.id = deliveries.order_id
AND o.order_type = 'ST'
AND o.from_store_id = :store_id
AND (o.from_store_id NOT IN (3,5) and o.to_store_id is not null)
UNION ALL
SELECT 1
FROM orders o
WHERE o.id = deliveries.order_id
AND o.order_type = 'ST'
AND o.to_store_id = :store_id
AND (o.from_store_id IN (3,5) and o.to_store_id is not null))
} # Here we want non STs to use customer catalog store for warehouse dashboard, otherwise non-FBA inbound STs use the from store. FBA inbound STs use the to store so that they can deal with the inbound shipments %{ EXISTS(SELECT 1 FROM quotes quo INNER JOIN opportunities opp ON opp.id = quo.opportunity_id INNER JOIN parties cu ON cu.id = opp.customer_id INNER JOIN catalogs cat on cat.id = cu.catalog_id WHERE quo.id = deliveries.quote_id AND cat.store_id = :store_id) }- FEDEX_GROUND_SHIPPING_OPTION_IDS =
FedEx Ground, Ground Home Delivery or International Ground, US and Canada
[139, 135, 146, 152, 171]
- FALLBACK_MIN_OVERRIDE_COST_WWW =
Public-website fallback shipping cost: minimum dollar amount used
when the carrier rate-shop fails entirely. 20.0- FALLBACK_PER_LB_OVERRIDE_COST_WWW =
Per-pound add-on for the public-website fallback shipping cost.
5.0- FALLBACK_MAX_OVERRIDE_COST_FRACTION_WWW =
Cap (as a fraction of subtotal) on the public-website fallback
shipping cost so a tiny order doesn't get a shipping bill larger
than the goods. 20.0- FALLBACK_OVERRIDE_COST_CRM =
CRM-side fallback shipping cost (deliberately higher than the WWW
fallback so CRM users notice and re-rate-shop manually rather than
silently quoting an unrealistic number). 500.0- GATEWAY_PAYMENT_TYPES =
Guard: verify the order still has valid payment coverage before the
delivery reaches pending_ship_confirm (ready to ship).Only applies to orders with gateway-backed payments (Credit Card, PayPal,
Amazon Pay). PO, Store Credit, Check, Cash, Wire, etc. are terms-based
or manually processed and don't need gateway verification.Payment is validated when the order is released to the warehouse, and
periodically by PaymentCheckerWorker, but authorizations can expire or
be voided externally between release and the warehouse finishing labels.
Catching it here — before the delivery is ready for carrier pickup —
gives the team time to resolve the payment while the order is still in
the warehouse, rather than blocking at the shipped transition when
goods have already left. [Payment::CREDIT_CARD, Payment::PAYPAL, Payment::PAYPAL_INVOICE, Payment::AMAZON_PAY].freeze
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#do_not_validate_line_items ⇒ Object
Returns the value of attribute do_not_validate_line_items.
-
#force_shipping_cost_update ⇒ Object
Returns the value of attribute force_shipping_cost_update.
-
#ltl_exclusion_reasons ⇒ Object
Returns the value of attribute ltl_exclusion_reasons.
-
#master_tracking_number ⇒ Object
readonly
The number warehouse staff type when a label was bought outside Heatwave.
-
#override_carrier ⇒ Object
Returns the value of attribute override_carrier.
-
#override_future_release_date ⇒ Object
Returns the value of attribute override_future_release_date.
-
#payment_ids ⇒ Object
Returns the value of attribute payment_ids.
-
#skip_tracking_number_validation ⇒ Object
Returns the value of attribute skip_tracking_number_validation.
Attributes included from Models::Profitable
Belongs to collapse
-
#destination_address ⇒ Address?
The destination address this record belongs to.
-
#order ⇒ Order?
The order this record belongs to.
-
#origin_address ⇒ Address?
The origin address this record belongs to.
-
#prepack_requester ⇒ Party?
The prepack requester this record belongs to.
-
#quote ⇒ Quote?
The quote this record belongs to.
-
#selected_shipping_cost ⇒ ShippingCost?
The selected shipping cost this record belongs to.
-
#shipping_account_number ⇒ ShippingAccountNumber?
The shipping account number this record belongs to.
-
#shipping_option ⇒ ShippingOption?
The shipping option this record belongs to.
-
#supplier ⇒ Supplier?
The supplier this record belongs to.
Methods included from Models::Auditable
Has one collapse
-
#precreated_rma ⇒ Rma?
The associated precreated rma.
-
#rma_for_return ⇒ Rma?
The associated rma for return.
Has many collapse
-
#activities ⇒ ActiveRecord::Relation<Activity>
The associated activities.
-
#discounts ⇒ ActiveRecord::Relation<Discount>
The associated discounts.
-
#drop_ship_purchase_orders ⇒ ActiveRecord::Relation<PurchaseOrder>
The associated drop ship purchase orders.
-
#freight_events ⇒ ActiveRecord::Relation<FreightEvent>
CHR/Freightquote Navisphere events landed via the webhook pipeline; consumed by FreightEventStatusSummary for the warehouse-dashboard and delivery-show status icons, and by MissedFreightPickupSweep / FreightquoteVoidConfirmationWorker.
-
#invoices ⇒ ActiveRecord::Relation<Invoice>
The associated invoices.
-
#item_ledger_entries ⇒ ActiveRecord::Relation<ItemLedgerEntry>
The associated item ledger entries.
-
#ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
The associated ledger transactions.
-
#line_discounts ⇒ ActiveRecord::Relation<LineDiscount>
The associated line discounts.
-
#line_items ⇒ ActiveRecord::Relation<LineItem>
The associated line items.
-
#messaging_logs ⇒ ActiveRecord::Relation<MessagingLog>
The associated messaging logs.
-
#preset_jobs ⇒ ActiveRecord::Relation<PresetJob>
The associated preset jobs.
-
#reserved_serial_numbers ⇒ ActiveRecord::Relation<ReservedSerialNumber>
The associated reserved serial numbers.
-
#shipments ⇒ ActiveRecord::Relation<Shipment>
The associated shipments.
-
#shipping_costs ⇒ ActiveRecord::Relation<ShippingCost>
dependent destroy handled by trigger.
-
#uploads ⇒ ActiveRecord::Relation<Upload>
The associated uploads.
Methods included from Models::Payable
Delegated Instance Attributes collapse
-
#billing_entity ⇒ Object
Alias for Resource#billing_entity.
-
#catalog ⇒ Object
Alias for Customer#catalog.
-
#customer ⇒ Object
Alias for Resource_or_rma_for_delivery#customer.
-
#is_amazon_seller_central? ⇒ Object
Alias for Customer#is_amazon_seller_central?.
-
#po_number ⇒ Object
Alias for Order#po_number.
-
#primary_party ⇒ Object
Alias for Resource_or_rma_for_delivery#primary_party.
-
#store ⇒ Object
Alias for Resource#store.
Class Method Summary collapse
-
.active ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are active.
-
.all_at_warehouse ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are all at warehouse.
-
.auto_ship_confirm(logger: nil) ⇒ Object
Auto ship confirm.
-
.awaiting_po_fulfillment ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are awaiting po fulfillment.
-
.by_order_store_id ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are by order store id.
-
.by_quote_store_id ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are by quote store id.
-
.by_store_id ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are by store id.
-
.cancelable ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are cancelable.
-
.dropship ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are dropship.
-
.fedex_express ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are fedex express.
-
.fedex_ground ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are fedex ground.
-
.for_future_release ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are for future release.
-
.generate_super_pick_slip_pdf(deliveries, split_kits = false) ⇒ Object
Generate super pick slip pdf.
-
.invoice_shipped_deliveries(_logger = Rails.logger) ⇒ Object
Invoice shipped deliveries.
-
.invoiced ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are invoiced.
-
.limit_to_fba ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are limit to fba.
-
.locked_ids_among(delivery_ids) ⇒ Set<Integer>
Which of the given delivery ids are
locked(invoiced). -
.non_pickups ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are non pickups.
-
.non_quoting ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are non quoting.
-
.not_cancelable ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are not cancelable.
-
.not_ltl_freight ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are not ltl freight.
-
.pending_manifest_completion ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are pending manifest completion.
-
.pending_ship_confirm ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are pending ship confirm.
-
.pickups ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are pickups.
-
.processing_po_fulfillment ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are processing po fulfillment.
-
.quoting ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are quoting.
-
.quoting_or_pre_pack ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are quoting or pre pack.
-
.release_deliveries_past_release_date ⇒ Object
Cron entry — releases every
future_releasedelivery past its scheduled release date (excludingmanual_release_onlyones), then publishesEvents::DeliveryAutomaticallyReleasedorEvents::DeliveryAutomaticReleaseFailedper delivery so the async handler re-queries by id at email-send time. -
.sales_orders ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are sales orders.
-
.send_manual_release_due_notification ⇒ Object
Cron entry — for every
manual_release_onlyfuture-release delivery whose scheduled release date has arrived, publishesEvents::DeliveryManualReleaseDueso the async handler re-queries by id. -
.ship_labeled_before ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are ship labeled before.
-
.shipped ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are shipped.
-
.shipping ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are shipping.
-
.states_for_select ⇒ Object
States for select.
-
.with_active_parent ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with active parent.
-
.with_amz_bs_carrier ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with amz bs carrier.
-
.with_associations ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with associations.
-
.with_line_items ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with line items.
-
.with_shipment_carrier ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with shipment carrier.
Instance Method Summary collapse
-
#actual_shipping_cost_exceeds_threshold? ⇒ Boolean
Whether the record actual shipping cost exceeds threshold.
-
#actual_shipping_cost_to_show ⇒ Float
The actual shipping cost we report on screens and emails — zero when the customer pays the carrier on their own account, otherwise the carrier's billed amount rounded to cents.
-
#actual_shipping_cost_within_threshold? ⇒ Boolean
Whether the record actual shipping cost within threshold.
-
#actual_weight ⇒ BigDecimal
(also: #actual_shipment_weight)
Sum of measured weights across the delivery's top-level Shipments, preferring carrier-measured rows over packed estimates.
-
#actual_weight_discrepancy_exceeds_threshold? ⇒ Boolean
Whether the record actual weight discrepancy exceeds threshold.
-
#add_or_update_purchase_order_item(po, existing_po_items, item, li, unit_cost) ⇒ void
If a previously unlinked PurchaseOrderItem matches the new line item's item/quantity, reattaches it; otherwise builds a fresh PO item via #build_new_purchase_order_item.
-
#add_purchase_order_items(po, item, grouped_line_items, existing_po_items) ⇒ void
Adds PurchaseOrderItems to a PurchaseOrder for one item across one or more LineItems, using the supplier's tier price for the combined quantity.
-
#adjusted_actual_shipping_cost ⇒ BigDecimal
Shipping cost we actually bill the customer — zero when the SAN's owner is configured for third-party billing (the customer pays the carrier directly), otherwise the actual carrier-charged amount.
- #all_activities ⇒ ActiveRecord::Relation<Activity>
-
#all_completed_shipments_reported_tracking? ⇒ Boolean
True once every completed shipment can report a tracking number for the EDI ship confirm.
-
#all_dropship_items_fulfilled? ⇒ Boolean
Whether the record all dropship items fulfilled.
-
#all_intl_forms_pdf ⇒ Upload?
Most recent bundled international-forms Upload (CI + BOL + USMCA certificates).
-
#all_labels_pdf ⇒ Upload?
Most recent combined-labels PDF Upload attached to the delivery.
-
#all_lines_allocated_to_shipments? ⇒ Boolean
Whether the record all lines allocated to shipments.
-
#all_lines_allocated_to_shipments_and_shipments_have_weight? ⇒ Boolean
Whether the record all lines allocated to shipments and shipments have weight.
-
#all_lines_labeled_by_completed_shipments? ⇒ Boolean
True when every shippable line item is already fully covered, by quantity, by an active
label_completeshipment — i.e. -
#all_shipments_weights_match_expected ⇒ Hash{Symbol => Object}
Cross-checks entered weights against computed weights for pallets and item-bearing shipments.
-
#append_shipping_api_log_entry!(kind:, **fields) ⇒ void
Append a single ad-hoc entry to shipping_api_log.
-
#append_to_shipping_api_log!(kind:, shipping_result:) ⇒ Object
Append one or more entries to shipping_api_log per WyShipping label/void call.
-
#apply_cheapest_economy_shipping_method ⇒ Boolean
For "ships economy" deliveries currently sitting on the override placeholder, refreshes carrier rates and switches the selection to the cheapest ground option.
-
#apply_selected_shipping_cost!(shipping_cost_entry, previous_selected_id: nil, persist: false) ⇒ Object
Centralized application of the selected shipping cost to delivery and its shipping line — logic in ApplySelectedShippingCost (god-object decomposition).
-
#apply_shipping_match_for_economy_shipping ⇒ void
Adjusts the parent Order's discounts so the customer keeps paying the originally quoted economy shipping rate after the warehouse swaps in a real carrier.
-
#apply_warehouse_fee? ⇒ Boolean
Whether the record apply warehouse fee.
-
#at_least_one_shipment ⇒ Boolean
Validation guard: every shipping delivery (except service-only, warehouse pickup, and European fulfillment) must have at least one completed Shipment before we let it leave the warehouse.
-
#authoritative_packing_for_shipment_contents? ⇒ Boolean
True when a Packing row exists for this delivery with origin from_delivery (DeliveryMd5Extractor) or from_manual_entry (pre-pack).
-
#bol_pdf ⇒ Upload?
Latest bill-of-lading Upload attached to the delivery.
-
#build_new_purchase_order_item(item, li, quantity, unit_cost) ⇒ PurchaseOrderItem
Builds an unsaved PurchaseOrderItem for a dropship line item with supplier SKU/description, weights, costs, and auto-receive flag from the linked SupplierItem.
-
#build_purchase_order(supplier) ⇒ PurchaseOrder
Builds a new awaiting-transmission dropship PurchaseOrder for the given supplier on the catalog's company/store, ready to receive purchase order items.
-
#calculate_all_cogs ⇒ BigDecimal
Total cost of goods sold for non-shipping LineItems on this delivery, used by Models::Profitable margin computations and ledger entries.
-
#calculate_declared_value ⇒ BigDecimal
Aggregate declared/insured value for this delivery: for each goods LineItem,
quantity * unit_value_for_commercial_invoice. -
#calculate_grand_total ⇒ BigDecimal
Subtotal plus actual shipping cost — the value invoiced to the customer at ship time (excluding tax and discounts already factored into subtotal).
-
#calculate_shipping_options(options = {}) ⇒ Hash
Builds the option payload
WyShipping.calculate_shipping_from_optionsconsumes. -
#can_be_deleted? ⇒ Boolean
Whether the record can be deleted.
-
#can_print_carton_labels? ⇒ Boolean
Whether the record can print carton labels.
-
#can_update_tracking_info? ⇒ Boolean
Whether the record can update tracking info.
-
#can_void_rma_delivery? ⇒ Boolean
Whether the record can void rma delivery.
-
#canadian_tire_special_check? ⇒ Boolean
Canadian tire requires all packages are less than 67 lbs to ship Purolator otherwise Consolidated Fastfrate LTL.
-
#cancel ⇒ Boolean
Cancels the delivery in a transaction: releases reserved serial numbers and committed inventory, voids any in-flight marketplace labels (Walmart SWW, Amazon Buy Shipping), unpacks shipments, and cancels associated dropship PurchaseOrders and pre-created Rma.
-
#cancel_estimated_packaging ⇒ Boolean
Aborts an in-progress pre-pack and returns the delivery to
quoting. -
#cancelable?(current_user = nil) ⇒ Boolean
These methods above are from the model previously known as delivery_quote.
-
#cannot_ship_empty_delivery? ⇒ Boolean
Guard method for shipping state transitions.
-
#carrier_customs_email ⇒ String?
Customs-team email address for the reported carrier when manual customs handoff is required (Freightquote dispatches by SCAC, others by carrier name).
-
#carrier_icon ⇒ String
Asset path / icon class for the delivery's carrier, used in warehouse and customer dashboards.
-
#carrier_options_for_select ⇒ Array<Array(String, String)>
Carriers eligible for this delivery formatted for a
<select>helper — filtered by origin country, supplier capabilities, and override flags. -
#chosen_shipping_method ⇒ ShippingCost?
The ShippingCost the order is currently committed to — explicit
selected_shipping_costif present, otherwise the cost linked to the shipping LineItem. -
#chosen_shipping_method_carrier_cost ⇒ Float
Carrier-quoted cost for the chosen shipping method, falling back to the rate_data
actual_costwhen the row was zeroed out (e.g. customer third-party billing accounts where we don't bill the rate). -
#clear_shipping_costs_safely ⇒ Object
Safely clears shipping_costs by first nullifying shipping_cost_id on line_items to prevent FK violation when the line_items are later saved with stale references.
-
#commit_catalog_items ⇒ void
Decrements available inventory for all of this delivery's LineItems via Item::InventoryCommitter — invoked at ship time to convert reserved stock into shipped stock.
-
#commit_reserved_serial_numbers ⇒ void
Promotes all reserved SerialNumbers on the delivery's line items to "committed" so they're attached to the shipped units.
-
#complete_picked ⇒ void
End-of-pick / end-of-pre-pack handler invoked from the warehouse UI: transitions
pre_packdeliveries through the pre-packed flow with parent-order notifications, and transitionspicking/pending_ship_labelsthrough thepickedevent when allocation is complete. -
#completed_regular_delivery? ⇒ Boolean
Whether the record completed regular delivery.
-
#copy_shipments_if_drop_ship_po ⇒ Object
Copies shipments from associated drop ship purchase orders to this delivery's shipments.
-
#country ⇒ Country?
Country the delivery is associated with for currency, tax, and carrier defaults.
-
#create_invoice ⇒ Invoice
Builds the Invoice for this shipped delivery via Invoicing::CreateInvoiceFromDelivery, validating the delivery and its payments first.
-
#create_shipments_from_equivalent_delivery(equivalent_delivery, shipment_state = 'awaiting_label') ⇒ void
Clones Shipments and shipment contents from another delivery (e.g. when re-creating a voided shipment plan) onto this delivery, mapping items by id and respecting per-line allocation limits.
-
#create_st_ledger_entries ⇒ void
Records intra-company GL and item-ledger entries for an in-flight store-transfer delivery (one warehouse to another inside the same company).
-
#currency ⇒ String?
ISO-4217 currency code the delivery transacts in, falling through order → resource → RMA customer's catalog.
-
#currency_symbol ⇒ String
Glyph for #currency (e.g.
"$","€") for display formatting. - #custom_pack_list ⇒ Upload?
-
#declaration_232_pdf ⇒ Upload?
Most recent generated Section 232 declaration PDF Upload.
-
#default_container_type ⇒ String
Default packaging container for new Shipments on this delivery —
palletfor LTL freight, otherwisecarton. -
#delete_serial_number_reservations ⇒ void
Destroys every ReservedSerialNumber on this delivery's line items, releasing them for reuse.
-
#delta_weight_factor_remaining_to_allocate ⇒ Float
Fraction of the delivery's expected ship weight still represented by un-allocated LineItems.
-
#display_carrier_name ⇒ Object
When Freightquote is used as a broker, the human-readable carrier is stored in rate_data rather than on the delivery itself.
-
#do_not_ship_insure_via_carrier? ⇒ Boolean
Whether the record do not ship insure via carrier.
-
#do_not_validate_line_items? ⇒ Boolean
Whether the record do not validate line items.
-
#does_not_require_shipping_labeling? ⇒ Boolean
Whether the record does not require shipping labeling.
-
#economy_shipping_match_amount(order) ⇒ BigDecimal, ...
Signed credit for the economy shipping match, or nil when the match must not apply.
-
#edi_ship_confirm_already_sent? ⇒ Boolean
True when a ship confirm carrying the delivery's current tracking number has already been recorded for this order.
-
#electronic_ship_ci_pdf ⇒ Upload?
Most recent carrier-electronic commercial-invoice Upload (for UPS paperless invoicing and similar).
-
#estimated_delivery_date ⇒ Object
Trying to 'guess' the delivery date.
-
#estimated_tare_weight ⇒ Float
Memoized total tare weight across the delivery's top-level Shipments, preferring measured shipments, then packed, then any.
-
#european_shipment? ⇒ Boolean
Whether the record european shipment.
-
#existing_shipment_attributes=(shipment_attributes) ⇒ void
Nested-attributes setter for editing or removing already-persisted Shipments — rows missing from the hash are removed via the association.
-
#existing_shipping_cost_attributes=(shipping_cost_attributes) ⇒ void
Nested-attributes setter for editing or removing already-persisted ShippingCost rows from a delivery form.
-
#freightquote_carrier? ⇒ Boolean
Whether the record freightquote carrier.
-
#friendly_shipping_method(show_customer_pays_info = false, for_www = false, with_delivery_commitment = false) ⇒ String
Cached, fully decorated shipping method label (carrier, service, COD/insurance/account notes) suitable for emails, invoices, and confirmation pages.
-
#friendly_shipping_method_for_edi ⇒ String
EDI-flavored variant of #friendly_shipping_method — strips the FedEx signature notice and other consumer-facing decorations partner systems don't want.
-
#generate_all_international_forms_pdf ⇒ Boolean, Array<Upload>
Combines the commercial invoice, BOL, USMCA/FTA item certificates, and the latest steel/aluminum/copper declaration into a single
all_intl_forms_pdfUpload for international shipments. -
#generate_all_labels_pdf ⇒ Object
Generate all labels pdf.
-
#generate_asynch_labels ⇒ Object
Generate asynch labels.
-
#generate_barcode ⇒ String
Writes a Code 128B barcode of the parent order's reference number to
tmp/for embedding into pick-slip / packing-slip PDFs. -
#generate_bol_pdf(ship_bol_tmp_path = nil) ⇒ Array<Upload>
Produces the bill-of-lading PDF for an LTL freight shipment and attaches it as an Upload.
-
#generate_ci_pdf(ci_tmp_path = nil) ⇒ Array<Upload>
Produces the commercial invoice PDF (in triplicate) required for international shipments and attaches it as an Upload.
-
#generate_combined_pdf(split_kits: false, skip_plans: false) ⇒ Object
Generate combined pdf.
-
#generate_declaration_232_forms ⇒ Boolean
Generates the Section 232 declaration pair — the broker's macro-enabled xlsm (for electronic submission) and a printable PDF twin (folded into the combined international forms packet) — and attaches both as uploads.
-
#generate_dropship_po ⇒ Object
Generates dropship purchase orders and purchase order items for any dropship line items on this delivery that do not already have associated purchase orders.
-
#generate_labels ⇒ Hash{Symbol => Object}
Buys carrier labels for this delivery — advisory-locked guard-then-buy flow lives in GenerateLabels (god-object decomposition).
-
#generate_pick_slip_pdf(split_kits = false) ⇒ Upload
Renders and persists a fresh pick-slip Upload for the warehouse, writing the PDF through Pdf::Document::PackingSlip and
Upload.uploadify. -
#generate_serial_numbers_pdf ⇒ Upload?
Renders printable serial-number labels for every reserved SerialNumber on this delivery, attaches the PDF as an Upload, and marks the printed serials as such.
-
#get_address_hash_from_address(address) ⇒ Hash{Symbol => Object}
Flattens an Address into the plain hash carrier APIs and the
carrier_responsesJSONB column expect. -
#get_economy_shipping_cost_to_use ⇒ Float
Cost we charge when a "ships economy" delivery's override row needs a price.
-
#get_existing_po_items ⇒ Array<PurchaseOrderItem>
Existing dropship PurchaseOrderItems on this delivery that have not yet been linked to a specific LineItem — candidates for re-linking rather than creating duplicates.
-
#get_fallback_cost_to_use ⇒ Float
Sentinel cost used when carrier APIs return no rates so the order still has something to charge.
-
#get_or_generate_pick_slip_pdf(split_kits = false) ⇒ Upload
Returns the existing pick-slip Upload for this delivery or generates one on the fly when missing or its attachment was lost.
- #group_line_items_by_item(line_items) ⇒ Hash{Item => Array<LineItem>}
-
#group_unprocessed_dropship_line_items_by_supplier ⇒ Hash{Supplier => Array<LineItem>}
Dropship LineItems that still need a PurchaseOrder (no PO item yet, or the existing one was cancelled), grouped by supplier so each supplier gets its own PO.
-
#has_custom_products? ⇒ Boolean
Whether the record has custom products.
-
#has_custom_shipping_labels? ⇒ Boolean
Whether the record has custom shipping labels.
-
#has_destination_postal_code ⇒ Boolean
Validation helper for instant-quote deliveries: requires either a destination Address or an installation postal code on the parent quote so rate shopping has somewhere to ship to.
-
#has_dropship_items? ⇒ Boolean
Whether the record has dropship items.
-
#has_future_release_date? ⇒ Boolean
Whether the record has future release date.
-
#has_kits? ⇒ Boolean
Whether the record has kits.
-
#has_kits_or_serial_numbers? ⇒ Boolean
Whether the record has kits or serial numbers.
-
#has_not_changed_but_has_shipping_lines? ⇒ Boolean
Whether the record has not changed but has shipping lines.
-
#has_ready_to_print_amazon_fba_items? ⇒ Boolean
Whether the record has ready to print amazon fba items.
-
#has_serial_numbers? ⇒ Boolean
Whether the record has serial numbers.
-
#has_shippable_content? ⇒ Boolean
Check if this delivery has shippable content (non-shipping line items).
-
#has_shipping_line_linked_to_shipping_cost_that_doesnt_exist_in_delivery?(shipping_line) ⇒ Boolean
Whether the record has shipping line linked to shipping cost that doesnt exist in delivery.
-
#has_unfulfilled_dropship_items? ⇒ Boolean
Whether the record has unfulfilled dropship items.
-
#has_unprocessed_dropship_items? ⇒ Boolean
Whether the record has unprocessed dropship items.
-
#has_valid_shipments? ⇒ Boolean
Whether the record has valid shipments.
-
#incurs_oversized_penalty? ⇒ Boolean
Whether the record incurs oversized penalty.
- #index ⇒ Integer
-
#individual_auto_ship_confirm(logger: nil) ⇒ Object
Transitions a delivery to shipped state if ready.
-
#instant_quote? ⇒ Boolean
True when the parent opportunity originated in the web quote-builder ("Instant Quote") flow.
-
#instantiate_shipping_insurance ⇒ Shipping::LtlShippingInsurance, Shipping::PackageShippingInsurance
Returns the appropriate
Shipping::*ShippingInsurancestrategy object for the delivery's mode (LTL freight vs package). -
#insured_shipments ⇒ ActiveRecord::Relation<Shipment>
Subset of label-complete Shipments that opted into third-party shipping insurance — used by claim filing and billing reports.
-
#insured_value ⇒ BigDecimal?
Carrier-declared value the chosen rate was quoted with — null for carriers we don't insure through (Wayfair, Home Depot EDI, etc).
-
#is_amazon_seller_central_veeqo? ⇒ Boolean
Whether the record is amazon seller central veeqo.
-
#is_cross_border? ⇒ Boolean
Whether the record is cross border.
-
#is_default_ltl_freight? ⇒ Boolean
Whether the record is default ltl freight.
-
#is_domestic? ⇒ Boolean
Whether the record is domestic.
-
#is_international? ⇒ Boolean
Whether the record is international.
-
#is_international_and_ups_or_fedex? ⇒ Boolean
Whether the record is international and ups or fedex.
-
#is_part_of_manifest? ⇒ Boolean
Whether the record is part of manifest.
-
#is_part_of_transmitted_manifest? ⇒ Boolean
Whether this delivery sits on a manifest that was actually handed to the carrier and therefore can't be unwound.
-
#is_rma_return ⇒ Boolean
(also: #is_rma_return?)
Whether this delivery is the return half of an Rma (precreated by CRM agents to send return labels to customers).
-
#is_smart_service? ⇒ Boolean
Whether the record is smart service.
-
#is_sww_shipping_cost?(sc = nil) ⇒ Boolean
Check if the shipping cost is a Ship with Walmart rate.
-
#is_www? ⇒ Boolean
Whether the record is www.
-
#labeled_quantities_by_line_item ⇒ Hash{Integer => Numeric}
Quantity of each line item already covered by active
label_completeshipments, keyed byline_item_id. - #line_allocation_status_hash ⇒ Hash{Integer => Integer}
- #line_items_eligible_for_packing ⇒ ActiveRecord::Relation<LineItem>
- #line_items_requiring_serial_number ⇒ Array<LineItem>
-
#line_items_with_counters ⇒ ActiveRecord::Relation<LineItem>
LineItem relation eager-loaded with reserved/shipped serial-number counts so the warehouse UI can render allocation badges without N+1 queries.
-
#lines_overallocated? ⇒ Boolean
def delta_volume_factor_remaining_to_allocate (line_allocation_status_hash.sum{|k,v| LineItem.find(k).shipping_volume*v.abs}.to_f/ship_volume_from_shipments) end.
-
#link_serial_numbers_to_line_items ⇒ void
Records the shipped SerialNumbers on each LineItem, attaching carrier scan data to the historical line.
- #linked_return_deliveries ⇒ Array<Delivery>
-
#locked_for_fba? ⇒ Boolean
Whether the record locked for fba.
-
#ltl_freight_has_changed? ⇒ Boolean
Whether the record ltl freight has changed.
-
#manual_pickup_contact ⇒ Object
Manual pickup contact.
-
#mark_multi_shipments_manifested(manifest, shipment) ⇒ Integer
Tags every other label-complete Shipment on this delivery with the given manifest id so they ride along with the carrier manifest.
-
#matches?(existing_item, item, quantity) ⇒ Boolean
Whether an unlinked existing PurchaseOrderItem can be reattached to a new dropship line item instead of building a fresh one.
-
#md5_hash_items_from_packable ⇒ String
MD5 signature of the packed items, annotated when the shipping-relevant subset diverges from the full set.
-
#name(short = false, filename = false) ⇒ String
Human-friendly delivery name (e.g.
"SO Order #SO12345 Delivery 2") for UI titles and filenames. -
#new_shipment_attributes=(shipment_attributes) ⇒ void
Nested-attributes setter that builds new Shipments from a list of hashes (used by the manual-shipment form on the warehouse UI).
-
#no_empty_shipments ⇒ void
State-validation helper: rejects pending-label deliveries that have any packed-or-awaiting-labels Shipment with no contents and no child shipments.
-
#notify_store ⇒ void
Publishes
Events::DeliveryArrivedAtWarehousefromafter_all_transactions_commitso the asyncDeliveryArrivedAtWarehouseNotificationHandlerre-queries the delivery by id and emails the store's operations contacts. -
#open_activities_counter ⇒ Integer
Count of unresolved activities across delivery, parent order, and parent quote — drives the bell-icon badge on delivery summary screens.
-
#override_shipping_method? ⇒ Boolean
Whether the record override shipping method.
-
#packable_active_lines ⇒ ActiveRecord::Relation<LineItem>
All non-destroyed LineItems eligible for packaging, eager-loaded for the packing UI.
-
#packable_active_parent_lines_only(skip_spare_parts = false) ⇒ Array<LineItem>
Only the goods-bearing parent LineItems eligible for packaging (used when callers want to count packageable units without double-counting kit components).
-
#packable_item_hash ⇒ Hash{Item => Integer}
Aggregated
{item => quantity}hash for all packable lines, collapsing duplicates (same Item on multiple lines). -
#packable_parent_lines_item_hash(skip_spare_parts = false) ⇒ Hash{Item => Integer}
Parent-only variant of #packable_item_hash — does not include kit component items.
-
#packageable? ⇒ Boolean
Are there shipments that can have content specified?.
-
#pallet_weight_matching ⇒ Boolean
State-validation helper that surfaces pallet/carton weight mismatches detected by #all_shipments_weights_match_expected.
-
#paperless_return? ⇒ Boolean
True when this delivery is an RMA return AND the chosen shipping option is on the known-paperless-supporting list (
ShippingOption.paperless_eligible). -
#pick_slip_file_name(with_extension = true) ⇒ String
Filename for the warehouse pick slip PDF, dated to the current minute so concurrent regenerations don't clobber each other in
tmp/. -
#pick_slip_line_items(split_kits: false, sort_method: :location) ⇒ Object
Generates a line hash for the pick/pack slip pdf.
-
#pickup_alert_visible? ⇒ Boolean
True when a confirmed pickup is on the books and the delivery hasn't yet shipped/invoiced/cancelled — used by the order and delivery screens to surface a persistent banner showing the pickup window.
-
#preferred_shipping_option ⇒ Object
These methods below are from the model previously known as delivery_quote.
-
#print_container_label? ⇒ Boolean
Whether the record print container label.
-
#publish_delivery_label_complete_event ⇒ void
Publishes
Events::DeliveryLabelCompletesoShipping::DeliveryLabelCompleteHandlercan re-run the Packing-write asynchronously once labels are purchased and shipment_contents exist. -
#publish_delivery_ready_for_labeling_event ⇒ void
Publishes
Events::DeliveryReadyForLabelingsoShipping::DeliveryReadyForLabelingHandlercan refresh thefrom_deliveryPacking record asynchronously. -
#publish_order_acknowledged_event ⇒ void
Publishes
Events::OrderAcknowledged, which drives the EDI ship confirmation (Edi::OrderEventProcessor→ the partner'sconfirm_message_processor.acknowledge_order). -
#quantities_remaining_to_allocate ⇒ Integer
Total unit count still needing to be put into a Shipment across all packable line items — drives the packing progress indicator.
-
#ready_to_choose_ships_economy_carrier? ⇒ Boolean
Whether the record ready to choose ships economy carrier.
- #ready_to_print_amazon_fba_line_items ⇒ Array<LineItem>
-
#ready_to_ship! ⇒ void
Promotes a quoting delivery into the warehouse pipeline: routes to
awaiting_po_fulfillmentwhen dropship items are present, otherwise toat_warehouse. -
#reference_number ⇒ String
(also: #to_s)
Stable customer/warehouse-facing identifier (e.g.
DE12345) used on labels, packing slips, and Slack notifications. -
#reference_number_for_label ⇒ String
Short identifier embedded in the carrier's "reference number" label field — the parent Order's reference, the RMA number for returns, or the delivery reference as a fallback.
-
#rejoin_serial_numbers ⇒ void
Re-merges previously split serial-number LineItems back into a single multi-quantity row, used when cancelling a delivery so we don't leave one-unit rows behind.
-
#relevant_changes ⇒ HashWithIndifferentAccess
Subset of
changesthat materially affect rate shopping or carrier selection — excludes audit-only / display-only fields like packaging text, master tracking, BOL, and release-date metadata. -
#relink_payments ⇒ Integer
after_savehook that points all Payment rows from thepayment_idsaccessor at this delivery — used when payments are captured during a delivery edit before the delivery is persisted. -
#remap_legacy_shipping_options_if_any ⇒ void
Migrates any LEGACY_-prefixed ShippingOption service codes on this delivery and its ShippingCost rows to the current code, persisting the change.
-
#reported_carrier ⇒ String?
Carrier name to surface to customers and EDI partners — prefers the value set on the delivery (
carrier), then falls back to the first completed shipment's carrier or the override description for override selections (where the warehouse manually chose a carrier). -
#reported_master_tracking_number ⇒ String?
Tracking-style identifier reported externally — falls through master tracking number, LTL PRO number, carrier BOL, and finally the first completed shipment's tracking number (for override selections).
-
#requires_232_declaration? ⇒ Boolean
Whether this delivery needs a Section 232 steel/aluminum/copper declaration: US customs requires it for goods entering the USA, so only CAN→USA cross-border deliveries qualify.
-
#requires_manifest_completion? ⇒ Boolean
Whether the record requires manifest completion.
-
#requires_manual_pickup? ⇒ Boolean
Whether the record requires manual pickup.
-
#reset_early_label_flag_on_order ⇒ Object
Reset purchase_label_early flag on the order so next ship-label goes through normal flow.
-
#reset_shipping_cost ⇒ void
Clears all ShippingCost rows safely (nullifying line-item references first to avoid FK violations) and zeros out the cached
shipping_coston the delivery. -
#reset_ships_economy_if_unselected ⇒ Boolean
after_savecallback that clears the order'sships_economyflag when the warehouse swaps in a real shipping method that isn't a ground/economy match — so future deliveries don't keep treating the order as economy. - #resource ⇒ Order, ...
- #resource_or_rma_for_delivery ⇒ Order, ...
-
#resource_present ⇒ Boolean
Validation helper enforcing that every delivery has a parent Order or Quote (RMA returns are exempt because they fall back via #resource_or_rma_for_delivery).
- #resource_shipping_method ⇒ String?
-
#retrieve_friendly_shipping_method(show_customer_pays_info = false, for_www = false, sc = nil, with_delivery_commitment = false, for_edi = false) ⇒ Object
Retrieve friendly shipping method.
-
#retrieve_shipping_costs(rate_ship_date: nil) ⇒ Hash
Retrieve shipping costs from carriers and persist them as ShippingCost rows.
-
#retrieve_shipping_description_for_line_item(shipping_line) ⇒ String?
Convenience for invoice rendering: pulls the shipping-cost description from a shipping LineItem.
-
#retrieve_shipping_description_for_shipping_cost(sc = nil) ⇒ String?
Decorated description for a ShippingCost including COD note, third-party billing account hint, and the FedEx ≥ $500 signature warning.
-
#revert_to_override_economy_shipping_method(autosave = true) ⇒ void
Resets a "ships economy" delivery back to the override shipping option at the economy fallback cost — used when the previously selected real carrier becomes invalid (e.g. order returns to quoting after items change).
-
#same_day_pickup? ⇒ Boolean
Whether the carrier-confirmed pickup is today, in the sender's timezone.
-
#save_purchase_order_if_needed(po) ⇒ void
Persists a freshly built PurchaseOrder only if at least one PO item was added (so empty supplier groups don't create empty POs).
-
#schedule_pickup_if_necessary ⇒ void
Queues the FedEx Freight pickup-scheduling worker (US or CA variant) when this delivery has been ship-labeled through Heatwave.
-
#schedule_request_estimated_packaging ⇒ String
after_savecallback that re-queues the pre-pack worker when state drift between an order and its delivery puts them out of sync. -
#send_address_type_issue_notification ⇒ void
Notifies the team when a carrier reports an address-type issue (residential vs commercial) on this delivery's destination so it can be reclassified.
-
#send_canada_post_manual_void_email(tracking_numbers) ⇒ void
Emails Canada Post asking them to manually void labels we couldn't void via API (Canada Post has no programmatic void endpoint).
-
#send_commercial_invoice_to_carrier ⇒ void
Forwards the commercial invoice to the carrier's customs email when the carrier requires manual customs handoff (R+L Carriers, Freightquote / Polaris).
-
#send_delivery_pre_pack_cancelled_notification(cancelled_by: nil) ⇒ void
Publishes
Events::DeliveryPrePackCancelledsoDeliveryPrePackCancelledNotificationHandlercan re-query this delivery by id and email the warehouse / requester, naming the cancelling user. -
#send_delivery_pre_packed_notification ⇒ void
Publishes
Events::DeliveryPrePackedsoDeliveryPrePackedNotificationHandlercan re-query this delivery by id and email the team. -
#send_dropship_delivery_notification ⇒ void
Sends the internal "new dropship delivery" notification announcing that supplier purchase orders have been generated.
-
#send_purolator_manual_void_email(tracking_numbers) ⇒ void
Emails Purolator asking them to manually void labels we couldn't void via API.
-
#serial_numbers_file_name ⇒ String
Filename for the bundled serial-number labels PDF, dated to the current minute to avoid
tmp/collisions on regeneration. -
#serial_numbers_to_print ⇒ Array<SerialNumber>
Reserved serial numbers for this delivery, including the original/swapped numbers when an item was re-serialized.
-
#set_cogs ⇒ void
Stamps unit and total cost of goods sold onto every LineItem on this delivery.
-
#set_master_tracking_and_actual_shipping_cost_if_needed ⇒ void
Backfills the delivery's
master_tracking_number,ltl_pro_number, andactual_shipping_costfrom the first completed Shipment when they're missing — typically after dropship PO shipments are copied over. -
#set_override_shipping(autosave = true) ⇒ void
Forces this delivery onto the override ShippingOption at $0.00 — used by service-only / store-transfer flows where shipping isn't billed separately.
-
#set_packaged_items_md5_hash(options = {}) ⇒ void
Records the packed-items signature for this delivery via Shipping::DeliveryMd5Extractor so future shipping recalculations know not to wipe authoritative packing.
-
#set_proper_shipping_cost ⇒ Boolean?
before_savedriver that picks the right ShippingCost for the delivery and syncs it onto the delivery and the shipping LineItem. -
#set_shipped_date ⇒ void
Stamps the first ship event onto the delivery; idempotent so a re-shipment doesn't move the date.
-
#ship_ci_pdf ⇒ Upload?
Most recent commercial-invoice Upload attached to the delivery (printable triplicate variant).
-
#ship_from_attributes ⇒ Hash?
Address attributes used as the "ship from" block on labels and commercial invoices — driven by the delivery's origin warehouse for orders, or the RMA's ship-from for returns.
-
#ship_labeled_via_heatwave? ⇒ Boolean
Whether the record ship labeled via heatwave.
-
#ship_labeled_via_heatwave_or_manual_and_ship_insuring? ⇒ Boolean
Whether the record ship labeled via heatwave or manual and ship insuring.
-
#ship_natively_key ⇒ Symbol?
Account-number lookup key used to pick credentials when shipping on the customer's own carrier account ("ship natively"); nil when we ship on Heatwave's accounts.
- #ship_to_attributes ⇒ Hash?
-
#shipment_contents_editable?(current_user = nil) ⇒ Boolean
Whether the record shipment contents editable.
-
#shipment_event_tracking_numbers ⇒ Array<String>
Tracking numbers whose ShipmentEvent scans belong to this delivery: the per-package shipment tracking numbers PLUS the LTL freight PRO, which lives on the delivery (not its pallet shipments) for ShipEngine LTL.
-
#shipments_for_packing ⇒ ActiveRecord::Relation<Shipment>
Shipments currently visible to the packing UI — those still being built (suggested), already packed, or awaiting carrier labels.
-
#shipments_to_packages_hash(use_shipments = nil) ⇒ Object
Bridge method from Shipments to package hash model used by WyShipping.
-
#shipments_voidable? ⇒ Boolean
Whether the record shipments voidable.
-
#shipping? ⇒ Boolean
Whether the record shipping.
-
#shipping_line_item ⇒ LineItem?
Single shipping LineItem for the delivery (the row that bills the carrier cost).
-
#shipping_method_friendly ⇒ String
Compact human-readable shipping method label for delivery summary widgets — collapses overrides, warehouse pickups, and service-only deliveries to descriptive text rather than the raw shipping option name.
-
#shipping_methods_for_select(verbose = false, skip_override = false) ⇒ Array<Array(String, Integer)>
<select>payload of formatted carrier rate options, currency-aware and with optional commitment text and account hints. -
#shipping_option_matches?(so_name) ⇒ Boolean
Whether the record shipping option matches.
-
#ships_economy? ⇒ Boolean
(also: #ships_economy)
don't know why, but need to do it this way, can't use delegate.
-
#ships_economy_ltl? ⇒ Boolean
Whether the record ships economy ltl.
-
#ships_economy_package? ⇒ Boolean
Whether the record ships economy package.
-
#ships_ltl_freight? ⇒ Boolean
Whether the record ships ltl freight.
-
#should_have_electronic_commercial_invoice? ⇒ Boolean
Whether the record should have electronic commercial invoice.
-
#should_print_heating_element_labels? ⇒ Boolean
Whether the record should print heating element labels.
-
#should_send_commercial_invoice_to_carrier? ⇒ Boolean
Whether to auto-email the customs commercial invoice to the LTL carrier.
-
#should_ship_ltl_freight? ⇒ Boolean
Whether the record should ship ltl freight.
-
#show_packaging_on_pick_slip? ⇒ Boolean
Whether the record show packaging on pick slip.
-
#simple_shipping_description_for_shipping_cost(sc = nil) ⇒ String?
Plain-text label describing the ShippingCost (no COD/insurance badges).
-
#skip_destination_address_validation? ⇒ Boolean
Skip destination_address validation for instant quotes and shopping carts Carts don't have a shipping address until checkout.
-
#sorted_ground_shipping_costs(skip_override = false) ⇒ Array<ShippingCost>
Ground-tier ShippingCost options sorted cheapest-first, with the override row pushed to the bottom.
-
#sorted_shipping_costs(skip_override: false, uniq_by_shipping_option_id: false, filter_by_ltl_freight: nil) ⇒ Array<ShippingCost>
Full ShippingCost list for the delivery sorted cheapest-first with override last.
-
#sorted_shipping_costs_www_hash(sort_by_price: true) ⇒ Hash{Symbol => Array<ShippingCost>}
Service-level grouped ShippingCost buckets used by the WWW shipping picker:
:economy(override),:ground,:faster(expedited+rush),:freight. -
#split_serial_numbers ⇒ void
Splits each reservation-requiring LineItem into per-serial-number rows so each unit can carry its own serial.
-
#state_list ⇒ Array<Symbol>
Ordered list of states this delivery actually moves through, used to render the progress stepper in the UI.
-
#supported_shipping_carrier? ⇒ Boolean
Whether the record supported shipping carrier.
-
#third_party_billed? ⇒ Boolean
Single source of truth, at the delivery layer, for "is the carrier billed to the customer's own (attached) account?" — i.e.
-
#tracking_link ⇒ String?
Public tracking URL for the delivery, resolving marketplace carriers (Amazon, Walmart) to the underlying first-mile carrier when available.
-
#uncommit_catalog_items ⇒ void
Reverses #commit_catalog_items when a delivery is cancelled, returning quantity-available back to inventory.
-
#uncommit_reserved_serial_numbers ⇒ void
Reverses #commit_reserved_serial_numbers so the serials become available for another delivery.
-
#unlink_serial_numbers_to_line_items ⇒ void
Reverses #link_serial_numbers_to_line_items (e.g. when an invoiced delivery is reverted) so the serials become available again.
-
#update_line_items_qty_shipped ⇒ Object
Updates qty_shipped to match quantity for all line items in this delivery.
-
#update_serial_numbers_shipped_count ⇒ void
Bumps the per-SerialNumber shipped count after a successful ship event so analytics and warranty tracking stay current.
-
#valid_for_generating_return_labels? ⇒ Boolean
Whether the record valid for generating return labels.
-
#valid_for_generating_ship_labels? ⇒ Boolean
Whether the record valid for generating ship labels.
-
#valid_for_voiding_ship_labels? ⇒ Boolean
Whether the record valid for voiding ship labels.
- #validate_all_contents_allocated ⇒ void
-
#verify_payment_coverage! ⇒ void
Re-checks gateway payment authorization right before pickup-confirm, raising if authorizations have expired or been voided externally.
-
#versions_for_audit_trail(_params = {}) ⇒ ActiveRecord::Relation<RecordVersion>
RecordVersionrows for the audit trail tab — covers the delivery itself plus any line-item versions whose reference_data scopes them to this delivery (under either Order or Quote ownership). -
#void_early_label_on_order ⇒ Boolean
Void early-purchased label on the order if one exists and hasn't been transferred to a shipment yet Only called when there are no completed shipments (label not yet transferred).
-
#void_early_label_on_order_if_exists ⇒ Object
Void the early-purchased label on the order if one exists (regardless of whether it was transferred to a shipment).
-
#void_marketplace_labels ⇒ Object
Void marketplace labels (Walmart SWW, Amazon, etc.) for all shipments with labels.
-
#void_shipments ⇒ Hash{Symbol => Object}
Voids all completed Shipments on the delivery: walks the carrier-void path through WyShipping, falls back to manual void emails for Canadapost/Purolator, clears master tracking/BOL/cost, transitions back to
pending_ship_labelsand cleans up any early-purchased label on the parent Order.
Methods included from Models::LegacyRateRequest
#last_shipping_rate_request_result, #last_shipping_rate_request_result=
Methods included from Models::ShipMeasurable
#cartons_total, #crates_total, #pallets_total, #ship_freight_class_from_shipments, #ship_volume_from_shipments, #ship_volume_from_shipments_in_cubic_feet, #ship_weight_from_shipments, #shipment_set, #shipments_for_measure
Methods included from Models::Profitable
#default_sales_markup, #profit_margins_met?, #profitable_line_items, #profitable_status, #profitable_total_discounted, #profitable_total_estimated_cost, #profitable_total_estimated_line_cost, #profitable_total_profit, #profitable_total_profit_margin, #profitable_total_profit_markup, #track_profit?, #validate_min_profit_markup?
Methods included from Models::Notable
Methods included from Models::Packable
#calculate_actual_insured_value, #carrier, #carrier_fedex?, #delivery_description, #domestic?, #has_supported_carrier?, #is_goods_shipping?, #is_onsite_service_only?, #is_remote_service_only?, #is_service_only?, #is_warehouse_ca_pickup?, #is_warehouse_pickup?, #is_warehouse_us_pickup?, #is_zero_charge_dropship?, #must_be_insured?, #must_be_signature_confirmation?, #search_deliveries_for_equivalent_packaging, #ship_weight, #ships_from_text, #subtotal, #subtotal_cogs, #subtotal_for_commercial_invoice, #subtotal_for_insured_value, #subtotal_for_ltl_threshold, #subtotal_msrp
Methods included from Models::Payable
#balance, #funded_by_cod?, #total_payments_authorized
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::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#do_not_validate_line_items ⇒ Object
Returns the value of attribute do_not_validate_line_items.
95 96 97 |
# File 'app/models/delivery.rb', line 95 def do_not_validate_line_items @do_not_validate_line_items end |
#force_shipping_cost_update ⇒ Object
Returns the value of attribute force_shipping_cost_update.
95 96 97 |
# File 'app/models/delivery.rb', line 95 def force_shipping_cost_update @force_shipping_cost_update end |
#ltl_exclusion_reasons ⇒ Object
Returns the value of attribute ltl_exclusion_reasons.
95 96 97 |
# File 'app/models/delivery.rb', line 95 def ltl_exclusion_reasons @ltl_exclusion_reasons end |
#master_tracking_number ⇒ Object (readonly)
The number warehouse staff type when a label was bought outside
Heatwave. Checked against the delivery's carrier the same way each
shipment's own number is. @see TrackingNumberFormatValidator
Validations:
- Tracking_number_format ({ carrier_method: :reported_carrier })
209 |
# File 'app/models/delivery.rb', line 209 validates :master_tracking_number, tracking_number_format: { carrier_method: :reported_carrier } |
#override_carrier ⇒ Object
Returns the value of attribute override_carrier.
95 96 97 |
# File 'app/models/delivery.rb', line 95 def override_carrier @override_carrier end |
#override_future_release_date ⇒ Object
Returns the value of attribute override_future_release_date.
95 96 97 |
# File 'app/models/delivery.rb', line 95 def override_future_release_date @override_future_release_date end |
#payment_ids ⇒ Object
Returns the value of attribute payment_ids.
95 96 97 |
# File 'app/models/delivery.rb', line 95 def payment_ids @payment_ids end |
#skip_tracking_number_validation ⇒ Object
Returns the value of attribute skip_tracking_number_validation.
95 96 97 |
# File 'app/models/delivery.rb', line 95 def skip_tracking_number_validation @skip_tracking_number_validation end |
Class Method Details
.active ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are active. Active Record Scope
422 |
# File 'app/models/delivery.rb', line 422 scope :active, -> { where.not(state: 'cancelled') } |
.all_at_warehouse ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are all at warehouse. Active Record Scope
391 |
# File 'app/models/delivery.rb', line 391 scope :all_at_warehouse, -> { where(state: WAREHOUSE_STATES) } |
.auto_ship_confirm(logger: nil) ⇒ Object
Auto ship confirm.
3987 3988 3989 3990 3991 3992 3993 3994 3995 |
# File 'app/models/delivery.rb', line 3987 def self.auto_ship_confirm(logger: nil) logger ||= Rails.logger logger.info("#{Time.current}: Beginning auto_ship_confirm") deliveries = Delivery.where(state: %w[pending_ship_confirm]) logger.info("#{Time.current}: Deliveries found: #{deliveries.size}") deliveries.each do |d| DeliveryShipConfirmWorker.perform_async(delivery_id: d.id) end end |
.awaiting_po_fulfillment ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are awaiting po fulfillment. Active Record Scope
428 |
# File 'app/models/delivery.rb', line 428 scope :awaiting_po_fulfillment, -> { where(state: 'awaiting_po_fulfillment') } |
.by_order_store_id ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are by order store id. Active Record Scope
388 |
# File 'app/models/delivery.rb', line 388 scope :by_order_store_id, ->(store_id) { where(SUBQUERY_ORDER_STORE_ID, store_id:) } |
.by_quote_store_id ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are by quote store id. Active Record Scope
389 |
# File 'app/models/delivery.rb', line 389 scope :by_quote_store_id, ->(store_id) { where(SUBQUERY_QUOTE_STORE_ID, store_id:) } |
.by_store_id ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are by store id. Active Record Scope
387 |
# File 'app/models/delivery.rb', line 387 scope :by_store_id, ->(store_id) { by_order_store_id(store_id).or(by_quote_store_id(store_id)) } |
.cancelable ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are cancelable. Active Record Scope
423 |
# File 'app/models/delivery.rb', line 423 scope :cancelable, -> { where(state: CANCELABLE_STATES) } |
.dropship ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are dropship. Active Record Scope
427 |
# File 'app/models/delivery.rb', line 427 scope :dropship, -> { where(state: %w[awaiting_po_fulfillment processing_po_fulfillment]) } |
.fedex_express ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are fedex express. Active Record Scope
442 |
# File 'app/models/delivery.rb', line 442 scope :fedex_express, -> { joins(:shipments).where(shipments: { carrier: 'FedEx' }).joins(:selected_shipping_cost).where.not(shipping_costs: { shipping_option_id: FEDEX_GROUND_SHIPPING_OPTION_IDS }) } |
.fedex_ground ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are fedex ground. Active Record Scope
441 |
# File 'app/models/delivery.rb', line 441 scope :fedex_ground, -> { joins(:shipments).where(shipments: { carrier: 'FedEx' }).joins(:selected_shipping_cost).where(shipping_costs: { shipping_option_id: FEDEX_GROUND_SHIPPING_OPTION_IDS }) } |
.for_future_release ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are for future release. Active Record Scope
433 |
# File 'app/models/delivery.rb', line 433 scope :for_future_release, -> { where(state: 'future_release') } |
.generate_super_pick_slip_pdf(deliveries, split_kits = false) ⇒ Object
Generate super pick slip pdf.
1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 |
# File 'app/models/delivery.rb', line 1485 def self.generate_super_pick_slip_pdf(deliveries, split_kits = false) upload = nil files_to_combine = [] msg_arr = [] t = deliveries.length p = 0 deliveries.each do |d| pdf = (begin d.get_or_generate_pick_slip_pdf(split_kits) rescue StandardError nil end) if pdf d.picking files_to_combine << pdf p += 1 else msg_arr << d.name.to_s end end unless files_to_combine.empty? file_name = "super_pick_slip_#{Time.current.strftime('%m_%d_%Y_%I_%M%p')}.pdf".downcase output_file_path = Upload.temp_location(file_name) super_pick_slip_path = PdfTools.combine(files_to_combine, output_file_path:, orientation: :portrait) upload = Upload.uploadify(super_pick_slip_path, 'super_pick_slip_pdf') end msg = "Processed #{p} of #{t} deliveries. " msg += "Pick slip PDF could not generate for #{msg_arr.join(', ')}." unless msg_arr.empty? [upload, msg] end |
.invoice_shipped_deliveries(_logger = Rails.logger) ⇒ Object
Invoice shipped deliveries.
3975 3976 3977 3978 3979 3980 3981 3982 3983 |
# File 'app/models/delivery.rb', line 3975 def self.invoice_shipped_deliveries(_logger = Rails.logger) Rails.logger.info("#{Time.current}: Beginning capture_cc_payments") deliveries = Delivery.where(state: 'shipped') Rails.logger.info("#{Time.current}: Deliveries found: #{deliveries.length}") deliveries.each do |delivery| DeliveryInvoicingWorker.perform_in(15.seconds, delivery.id) end end |
.invoiced ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are invoiced. Active Record Scope
407 |
# File 'app/models/delivery.rb', line 407 scope :invoiced, -> { where(state: 'invoiced') } |
.limit_to_fba ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are limit to fba. Active Record Scope
432 |
# File 'app/models/delivery.rb', line 432 scope :limit_to_fba, ->(fba_only) { fba_only ? joins(order: { customer: :billing_address }).where('parties.id = ? OR addresses.party_id = ?', CustomerConstants::AMAZON_COM_ID, CustomerConstants::AMAZON_COM_ID) : where('1=1') } |
.locked_ids_among(delivery_ids) ⇒ Set<Integer>
Which of the given delivery ids are locked (invoiced). Line items on a
locked delivery are financially frozen — the deferred
locked_delivery_line_items_guard DB trigger rejects any write — so
order-wide recalcs (discount reset, re-pricing) use this to skip them.
417 418 419 420 421 |
# File 'app/models/delivery.rb', line 417 def self.locked_ids_among(delivery_ids) return Set.new if delivery_ids.blank? where(id: delivery_ids, locked: true).ids.to_set end |
.non_pickups ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are non pickups. Active Record Scope
431 |
# File 'app/models/delivery.rb', line 431 scope :non_pickups, -> { where.not(destination_address_id: WAREHOUSE_ADDRESS_IDS) } |
.non_quoting ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are non quoting. Active Record Scope
405 |
# File 'app/models/delivery.rb', line 405 scope :non_quoting, -> { where.not(state: 'quoting') } |
.not_cancelable ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are not cancelable. Active Record Scope
424 |
# File 'app/models/delivery.rb', line 424 scope :not_cancelable, -> { where.not(state: CANCELABLE_STATES) } |
.not_ltl_freight ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are not ltl freight. Active Record Scope
398 |
# File 'app/models/delivery.rb', line 398 scope :not_ltl_freight, -> { where(ltl_freight: [false, nil], ltl_freight_guaranteed: [false, nil]) } |
.pending_manifest_completion ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are pending manifest completion. Active Record Scope
443 |
# File 'app/models/delivery.rb', line 443 scope :pending_manifest_completion, -> { where(state: 'pending_manifest_completion') } |
.pending_ship_confirm ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are pending ship confirm. Active Record Scope
434 |
# File 'app/models/delivery.rb', line 434 scope :pending_ship_confirm, -> { where(state: 'pending_ship_confirm') } |
.pickups ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are pickups. Active Record Scope
430 |
# File 'app/models/delivery.rb', line 430 scope :pickups, -> { where.not(order_id: nil).where(destination_address_id: WAREHOUSE_ADDRESS_IDS) } |
.processing_po_fulfillment ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are processing po fulfillment. Active Record Scope
429 |
# File 'app/models/delivery.rb', line 429 scope :processing_po_fulfillment, -> { where(state: 'processing_po_fulfillment') } |
.quoting ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are quoting. Active Record Scope
403 |
# File 'app/models/delivery.rb', line 403 scope :quoting, -> { where(state: 'quoting') } |
.quoting_or_pre_pack ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are quoting or pre pack. Active Record Scope
404 |
# File 'app/models/delivery.rb', line 404 scope :quoting_or_pre_pack, -> { where(state: %w[quoting pre_pack]) } |
.release_deliveries_past_release_date ⇒ Object
Cron entry — releases every future_release delivery past its scheduled
release date (excluding manual_release_only ones), then publishes
Events::DeliveryAutomaticallyReleased or Events::DeliveryAutomaticReleaseFailed
per delivery so the async handler re-queries by id at email-send time.
Replaces direct InternalMailer.…deliver_later(d) calls whose Delivery
GlobalID arg was vulnerable to the AppSignal #4958 destroy race.
995 996 997 998 999 1000 1001 1002 1003 1004 |
# File 'app/models/delivery.rb', line 995 def self.release_deliveries_past_release_date Delivery.where("state = 'future_release' and future_release_date <= ? and manual_release_only is not true", Date.current).find_each do |d| d.release delivery_id = d.id event = d.at_warehouse? ? Events::DeliveryAutomaticallyReleased.new(data: { delivery_id: }) : Events::DeliveryAutomaticReleaseFailed.new(data: { delivery_id: }) Rails.configuration.event_store.publish(event, stream_name: "Delivery-#{delivery_id}") rescue StandardError => e ErrorReporting.error(e) end end |
.sales_orders ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are sales orders. Active Record Scope
425 |
# File 'app/models/delivery.rb', line 425 scope :sales_orders, -> { active.joins(:order).merge(Order.sales_orders) } |
.send_manual_release_due_notification ⇒ Object
Cron entry — for every manual_release_only future-release delivery whose
scheduled release date has arrived, publishes
Events::DeliveryManualReleaseDue so the async handler re-queries by id.
Same AppSignal #4958 race-removal as release_deliveries_past_release_date.
1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 |
# File 'app/models/delivery.rb', line 1010 def self.send_manual_release_due_notification Delivery.where("state = 'future_release' and future_release_date <= ? and manual_release_only is true", Date.current).find_each do |d| delivery_id = d.id Rails.configuration.event_store.publish( Events::DeliveryManualReleaseDue.new(data: { delivery_id: }), stream_name: "Delivery-#{delivery_id}" ) rescue StandardError => e ErrorReporting.error(e) end end |
.ship_labeled_before ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are ship labeled before. Active Record Scope
444 |
# File 'app/models/delivery.rb', line 444 scope :ship_labeled_before, ->(time) { where(Delivery[:ship_labeled_at].lteq(time)) } |
.shipped ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are shipped. Active Record Scope
406 |
# File 'app/models/delivery.rb', line 406 scope :shipped, -> { where(state: %w[shipped pending_ship_confirm pending_pickup_confirm]) } |
.shipping ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are shipping. Active Record Scope
426 |
# File 'app/models/delivery.rb', line 426 scope :shipping, -> { where(state: SHIPPING_STATES) } |
.states_for_select ⇒ Object
States for select.
3969 3970 3971 |
# File 'app/models/delivery.rb', line 3969 def self.states_for_select state_machine.states.sort_by(&:human_name).map { |s| [s.human_name, s.value] } end |
.with_active_parent ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with active parent. Active Record Scope
399 400 401 402 |
# File 'app/models/delivery.rb', line 399 scope :with_active_parent, -> { where(order_id: Order.where.not(state: :cancelled).select(:id)) .or(where(order_id: nil, quote_id: Quote.where.not(state: :cancelled).select(:id))) } |
.with_amz_bs_carrier ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with amz bs carrier. Active Record Scope
436 437 438 439 440 |
# File 'app/models/delivery.rb', line 436 scope :with_amz_bs_carrier, ->(carrier) { joins(:shipments) .where(shipments: { carrier: 'AmazonSeller' }) .where("shipments.amz_metadata->>'amz_carrier' = ?", carrier) } |
.with_associations ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with associations. Active Record Scope
390 |
# File 'app/models/delivery.rb', line 390 scope :with_associations, -> { includes(:shipments, :origin_address, :destination_address, { quote: [{ opportunity: [{ customer: [:buying_group, { catalog: :store }] }] }] }, order: [{ customer: [:buying_group, { catalog: :store }] }]) } |
.with_line_items ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with line items. Active Record Scope
408 |
# File 'app/models/delivery.rb', line 408 scope :with_line_items, -> { includes(line_items: { catalog_item: { store_item: :item } }) } |
.with_shipment_carrier ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are with shipment carrier. Active Record Scope
435 |
# File 'app/models/delivery.rb', line 435 scope :with_shipment_carrier, ->(carrier) { joins(:shipments).where(shipments: { carrier: }) } |
Instance Method Details
#activities ⇒ ActiveRecord::Relation<Activity>
Returns the associated activities.
156 |
# File 'app/models/delivery.rb', line 156 has_many :activities, as: :resource, dependent: :nullify |
#actual_shipping_cost_exceeds_threshold? ⇒ Boolean
Returns whether the record actual shipping cost exceeds threshold.
4092 4093 4094 4095 4096 4097 |
# File 'app/models/delivery.rb', line 4092 def actual_shipping_cost_exceeds_threshold? estimate_shipping_cost = line_items.shipping_only.to_a.sum(&:price).to_f (exceeds = (actual_shipping_cost_to_show > (1.0 + (Delivery::SHIP_LABEL_HOLD_PERCENT_THRESHOLD / 100).round(2)) * estimate_shipping_cost)) && ((actual_shipping_cost_to_show - estimate_shipping_cost) > Delivery::SHIP_LABEL_HOLD_DOLLAR_THRESHOLD) && !order.is_rma_return? # logger.debug "Delivery, ID: #{self.id}, actual_shipping_cost_exceeds_threshold?: #{exceeds}, actual_shipping_cost_to_show: #{actual_shipping_cost_to_show}, estimate_shipping_cost: #{estimate_shipping_cost}, self.order.is_rma_return?: #{self.order.is_rma_return?}" exceeds end |
#actual_shipping_cost_to_show ⇒ Float
The actual shipping cost we report on screens and emails — zero
when the customer pays the carrier on their own account, otherwise
the carrier's billed amount rounded to cents.
4078 4079 4080 4081 4082 |
# File 'app/models/delivery.rb', line 4078 def actual_shipping_cost_to_show actual_shipping_cost_to_use = 0.0 actual_shipping_cost_to_use = self.actual_shipping_cost.to_f unless third_party_billed? actual_shipping_cost_to_use.round(2) end |
#actual_shipping_cost_within_threshold? ⇒ Boolean
Returns whether the record actual shipping cost within threshold.
4085 4086 4087 4088 4089 |
# File 'app/models/delivery.rb', line 4085 def actual_shipping_cost_within_threshold? estimate_shipping_cost = line_items.shipping_only.to_a.sum(&:price).to_f.round(2) (actual_shipping_cost_to_show <= estimate_shipping_cost) && !order.is_rma_return? # puts "actual_shipping_cost_within_threshold?: #{within}, actual_shipping_cost_to_show: #{actual_shipping_cost_to_show}, estimate_shipping_cost: #{estimate_shipping_cost}, self.order.is_rma_return?: #{self.order.is_rma_return?}" end |
#actual_weight ⇒ BigDecimal Also known as: actual_shipment_weight
Sum of measured weights across the delivery's top-level Shipments,
preferring carrier-measured rows over packed estimates. Used for
weight-discrepancy thresholds and ship-label hold flags.
4104 4105 4106 4107 4108 4109 4110 4111 4112 |
# File 'app/models/delivery.rb', line 4104 def actual_weight # only if you have a weight do we care shipments_for_weight = shipments.where.not(weight: nil) # We only use top level shipments, ie shipments that are not contained in other shipments/pallets shipments_for_weight = shipments_for_weight.top_level # Measured shipments at an advanced stage take precedence over the packed one shipments_for_weight = shipments_for_weight.measured.presence || shipments_for_weight.packed shipments_for_weight.sum(:weight).round(1) end |
#actual_weight_discrepancy_exceeds_threshold? ⇒ Boolean
Returns whether the record actual weight discrepancy exceeds threshold.
4116 4117 4118 4119 4120 4121 4122 4123 |
# File 'app/models/delivery.rb', line 4116 def actual_weight_discrepancy_exceeds_threshold? expected_weight = ship_weight + estimated_tare_weight wt_diff_exceeds_percent_thresh = (actual_weight - expected_weight).abs > (Delivery::SHIP_LABEL_HOLD_WEIGHT_PERCENT_THRESHOLD / 100).round(2) * expected_weight wt_diff_exceeds_lbs_threshold = (actual_weight - expected_weight).abs > Delivery::SHIP_LABEL_HOLD_WEIGHT_THRESHOLD exceeds = wt_diff_exceeds_percent_thresh && wt_diff_exceeds_lbs_threshold && !(order && order.is_rma_return?) logger.debug "Delivery, ID: #{id}, actual_weight_discrepancy_exceeds_threshold?: #{exceeds}, wt_diff_exceeds_percent_thresh: #{wt_diff_exceeds_percent_thresh}, wt_diff_exceeds_lbs_threshold: #{wt_diff_exceeds_lbs_threshold}, actual_weight: #{actual_weight}, expected_weight: #{expected_weight} (ship_weight: #{ship_weight} + tare: #{estimated_tare_weight}), self.order.is_rma_return?: #{order&.is_rma_return?}" exceeds end |
#add_or_update_purchase_order_item(po, existing_po_items, item, li, unit_cost) ⇒ void
This method returns an undefined value.
If a previously unlinked PurchaseOrderItem matches the new
line item's item/quantity, reattaches it; otherwise builds a fresh
PO item via #build_new_purchase_order_item.
2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 |
# File 'app/models/delivery.rb', line 2656 def add_or_update_purchase_order_item(po, existing_po_items, item, li, unit_cost) quantity = li.quantity existing_item = existing_po_items.find { |poi| matches?(poi, item, quantity) } if existing_item existing_item.update(line_item: li) else po.purchase_order_items << build_new_purchase_order_item(item, li, quantity, unit_cost) end end |
#add_purchase_order_items(po, item, grouped_line_items, existing_po_items) ⇒ void
This method returns an undefined value.
Adds PurchaseOrderItems to a PurchaseOrder for one item across
one or more LineItems, using the supplier's tier price for the
combined quantity. Re-uses any matching unlinked existing PO item.
2637 2638 2639 2640 2641 2642 2643 2644 |
# File 'app/models/delivery.rb', line 2637 def add_purchase_order_items(po, item, grouped_line_items, existing_po_items) total_qty = grouped_line_items.sum(&:quantity) unit_cost = item.supplier_item.get_price_for_qty(total_qty) grouped_line_items.each do |li| add_or_update_purchase_order_item(po, existing_po_items, item, li, unit_cost) end end |
#adjusted_actual_shipping_cost ⇒ BigDecimal
Shipping cost we actually bill the customer — zero when the SAN's
owner is configured for third-party billing (the customer pays the
carrier directly), otherwise the actual carrier-charged amount.
Gates on the SAN OWNER's bill_shipping_to_customer? rather than
bare SAN presence so the customer invoice and the carrier label
(see WyShipping.classify_third_party_billing) read the same
predicate. Bare SAN presence used to zero the invoice while the
carrier was still billed P/P on WarmlyYours's account — silent
double leak (e.g. Ferguson Aurora #1983 SAN 3E049R).
3689 3690 3691 3692 3693 3694 3695 |
# File 'app/models/delivery.rb', line 3689 def adjusted_actual_shipping_cost if third_party_billed? BigDecimal(0) else actual_shipping_cost end end |
#all_activities ⇒ ActiveRecord::Relation<Activity>
895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 |
# File 'app/models/delivery.rb', line 895 def all_activities # Start with the initial condition for Delivery query = Activity.where( Activity.arel_table[:resource_type].eq('Delivery') .and(Activity.arel_table[:resource_id].eq(id)) ) # Add the Order condition if order_id is present if order_id.present? order_condition = Activity.where( Activity.arel_table[:resource_type].eq('Order') .and(Activity.arel_table[:resource_id].eq(order_id)) ) query = query.or(order_condition) end # Add the Quote condition if quote_id is present if quote_id.present? quote_condition = Activity.where( Activity.arel_table[:resource_type].eq('Quote') .and(Activity.arel_table[:resource_id].eq(quote_id)) ) query = query.or(quote_condition) end query end |
#all_completed_shipments_reported_tracking? ⇒ Boolean
True once every completed shipment can report a tracking number for the EDI
ship confirm. Shipment#display_tracking_number resolves the delivery-level
ltl_pro_number for ShipEngine LTL (whose per-pallet tracking_number is
blank) and the shipment's own tracking_number otherwise. This is the gate
for publishing the confirm — LTL carriers that assign the PRO asynchronously
read blank here until the PRO lands.
2831 2832 2833 2834 |
# File 'app/models/delivery.rb', line 2831 def all_completed_shipments_reported_tracking? completed = shipments.completed.to_a completed.any? && completed.all? { |s| s.display_tracking_number.present? } end |
#all_dropship_items_fulfilled? ⇒ Boolean
Returns whether the record all dropship items fulfilled.
2539 2540 2541 |
# File 'app/models/delivery.rb', line 2539 def all_dropship_items_fulfilled? line_items.none? { |li| li.dropship? && (li.purchase_order_item.nil? || !li.purchase_order_item.fully_receipted?) } end |
#all_intl_forms_pdf ⇒ Upload?
Most recent bundled international-forms Upload (CI + BOL + USMCA
certificates).
3427 3428 3429 |
# File 'app/models/delivery.rb', line 3427 def all_intl_forms_pdf uploads.order(:id).reverse_order.find_by(category: 'all_intl_forms_pdf') end |
#all_labels_pdf ⇒ Upload?
Most recent combined-labels PDF Upload attached to the delivery.
3358 3359 3360 |
# File 'app/models/delivery.rb', line 3358 def all_labels_pdf uploads.order(:id).reverse_order.find_by(category: 'all_labels_pdf') end |
#all_lines_allocated_to_shipments? ⇒ Boolean
Returns whether the record all lines allocated to shipments.
1078 1079 1080 |
# File 'app/models/delivery.rb', line 1078 def all_lines_allocated_to_shipments? line_allocation_status_hash.values.all?(&:zero?) end |
#all_lines_allocated_to_shipments_and_shipments_have_weight? ⇒ Boolean
Returns whether the record all lines allocated to shipments and shipments have weight.
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 |
# File 'app/models/delivery.rb', line 1083 def all_lines_allocated_to_shipments_and_shipments_have_weight? res = true unless all_lines_allocated_to_shipments? errors.add(:base, 'All items must be allocated to shipments.') res = false end # CB: Disabled for now, causing a lot of frictions with warehouse. Ramie when back will revisit. # unless shipments.all?{|s| s.weight > s.compute_tare_weight && s.entered_and_computed_weights_are_close?} # errors.add(:base, "All shipments must have a weight close the computed item weight plus the tare/packaging weight. Shipments weights: #{shipments.map{|s| s.weight.to_f.to_s + 'lbs'}.join(', ')}, computed weights: #{shipments.map{|s| (s.compute_tare_weight.to_f + s.compute_shipment_weight.to_f).to_s + 'lbs'}.join(', ')}") # res = false # end res end |
#all_lines_labeled_by_completed_shipments? ⇒ Boolean
True when every shippable line item is already fully covered, by quantity,
by an active label_complete shipment — i.e. the delivery's items already
have carrier labels, so buying more would just duplicate them. Idempotency
guard for #generate_labels (defect G / SO728077, where a re-pack cycle
bought 4 carrier labels for the same 2 items). Recovery is the existing
"Void Shipments" action.
Mirrors #line_allocation_status_hash but scopes coverage to bought labels
rather than packing-stage shipments.
1147 1148 1149 1150 1151 1152 |
# File 'app/models/delivery.rb', line 1147 def all_lines_labeled_by_completed_shipments? return false if shipments.label_complete.none? labeled = labeled_quantities_by_line_item line_items_eligible_for_packing.all? { |li| labeled[li.id].to_i >= li.quantity.abs } end |
#all_shipments_weights_match_expected ⇒ Hash{Symbol => Object}
Cross-checks entered weights against computed weights for pallets
and item-bearing shipments. Items with sustained mismatches are
flagged for weight review (review_product_weight_flag).
4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 |
# File 'app/models/delivery.rb', line 4266 def all_shipments_weights_match_expected # here, because we can't really trust item weight data, we want to return valid false only for pallets without items but with child containers whose weights mismatch # otherwise we flag items on shipments whose entered weights do not match computed weights from items for review/re-weighing res = {} res[:status] = true if (pallets = shipments.pallets).any? && (problematic_pallets = pallets.select { |p| p.shipment_contents.blank? && !p.entered_and_computed_weights_are_close? }).any? res[:status] = false err_msgs = [] problematic_pallets.each do |s| err_msgs << "#{s.container_type.to_s.titleize} #{s.reference_number} has entered weight of #{s.weight} LBS and computed weight of #{s.compute_shipment_weight} LBS from its cartons" end res[:error_message] = err_msgs.join('. ') end if (problematic_shipments_with_contents = shipments.select { |s| s.shipment_contents.present? && !s.entered_and_computed_weights_are_close? }).any? items_to_review = [] problematic_shipments_with_contents.each do |s| s.shipment_contents.each do |sc| if sc.line_item.item.condition_new? && sc.line_item.item.base_weight > Shipment::MIN_WEIGHT_DELTA_LBS && !sc.line_item.item.product_weight_flag_audited? # only new items not refurbished. that weight more than the minimum threshold, and don't redo item weights that have already been corrected items_to_review << sc.line_item.item end end end items_to_review.uniq! items_to_review.each do |item| item.update_column(:review_product_weight_flag, true) end res[:warning_message] = "The weights for the following items SKUs may need to be reviewed/re-measured: #{items_to_review.map(&:sku).join(', ')}" if items_to_review.any? end res end |
#append_shipping_api_log_entry!(kind:, **fields) ⇒ void
This method returns an undefined value.
Append a single ad-hoc entry to shipping_api_log. Used for polling-style
carrier calls (e.g. Freightquote events) that don't flow through
append_to_shipping_api_log!'s label/void wrappers. Persists immediately
so polling traces survive crashes mid-loop.
3296 3297 3298 3299 3300 3301 3302 |
# File 'app/models/delivery.rb', line 3296 def append_shipping_api_log_entry!(kind:, **fields) entry = build_shipping_api_log_entry(kind: kind, **fields) new_log = Array(shipping_api_log) + [entry] update_column(:shipping_api_log, new_log) rescue StandardError => e Rails.logger.error("[Delivery##{id}] append_shipping_api_log_entry! failed: #{e.class}: #{e.}") end |
#append_to_shipping_api_log!(kind:, shipping_result:) ⇒ Object
Append one or more entries to shipping_api_log per WyShipping label/void
call. Captures request/response payloads (Hash for JSON-native carriers,
String for legacy XML carriers), HTTP status code, and response headers so
we don't have to dig through rotated prod logs after the fact. Persists
immediately via update_column to survive crashes/rollbacks in the
surrounding flow. When the carrier's label flow includes an inline re-rate
call (Freightquote re-rates at label time to lock in a fresh quoteId), a
companion kind: 'rate' entry is appended automatically.
3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 |
# File 'app/models/delivery.rb', line 3245 def append_to_shipping_api_log!(kind:, shipping_result:) payload = shipping_result.is_a?(Hash) ? shipping_result.with_indifferent_access : {} shipment = payload[:shipment].is_a?(Hash) ? payload[:shipment].with_indifferent_access : payload request_key = kind == 'void' ? :void_request_xml : :ship_request_xml response_key = kind == 'void' ? :void_response_xml : :ship_reply_xml entries = [] entries << build_shipping_api_log_entry( kind: kind, status: payload[:status_code] == :error ? 'error' : 'success', status_message: payload[:status_message], request: shipment[request_key] || payload[request_key], response: shipment[response_key] || payload[response_key], response_status: shipment[:response_status] || payload[:response_status], response_headers: shipment[:response_headers] || payload[:response_headers] ) rate_request = shipment[:rate_request_xml] || payload[:rate_request_xml] rate_response = shipment[:rate_reply_xml] || payload[:rate_reply_xml] if rate_request.present? || rate_response.present? entries << build_shipping_api_log_entry( kind: 'rate', status: 'success', request: rate_request, response: rate_response, response_status: shipment[:rate_response_status] || payload[:rate_response_status], response_headers: shipment[:rate_response_headers] || payload[:rate_response_headers] ) end new_log = Array(shipping_api_log) + entries update_column(:shipping_api_log, new_log) rescue StandardError => e Rails.logger.error("[Delivery##{id}] append_to_shipping_api_log! failed: #{e.class}: #{e.}") end |
#apply_cheapest_economy_shipping_method ⇒ Boolean
For "ships economy" deliveries currently sitting on the override
placeholder, refreshes carrier rates and switches the selection to the
cheapest ground option. Removes the free-online-shipping coupon when
present and re-applies the economy shipping match coupon so the
customer still pays the original economy rate.
1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 |
# File 'app/models/delivery.rb', line 1744 def apply_cheapest_economy_shipping_method return true unless selected_shipping_cost&.is_override? # only applies to override ships economy retrieve_shipping_costs # get new shipping costs post-pack self.selected_shipping_cost = sorted_shipping_costs_www_hash[:ground]&.first # choose cheapest ground save # NOTE: free-online-shipping removal lives in # apply_shipping_match_for_economy_shipping so every caller (including the # CRM re-apply action) clears FS before crediting the match (defect C). order&.reload&.reset_discount(reset_item_pricing: false) # reset the discount apply_shipping_match_for_economy_shipping true end |
#apply_selected_shipping_cost!(shipping_cost_entry, previous_selected_id: nil, persist: false) ⇒ Object
Centralized application of the selected shipping cost to delivery and its
shipping line — logic in ApplySelectedShippingCost (god-object
decomposition). Ensures consistency whether the selection is auto-computed
or explicitly chosen elsewhere.
2461 2462 2463 2464 |
# File 'app/models/delivery.rb', line 2461 def apply_selected_shipping_cost!(shipping_cost_entry, previous_selected_id: nil, persist: false) Delivery::ApplySelectedShippingCost.new(self, shipping_cost_entry, previous_selected_id: previous_selected_id, persist: persist).process end |
#apply_shipping_match_for_economy_shipping ⇒ void
This method returns an undefined value.
Adjusts the parent Order's discounts so the customer keeps paying
the originally quoted economy shipping rate after the warehouse swaps
in a real carrier. Difference between checkout-time and current
shipping cost is applied as an economy_shipping_match_crm Discount.
1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 |
# File 'app/models/delivery.rb', line 1764 def apply_shipping_match_for_economy_shipping order.reload economy_shipping_match_crm_coupon = Coupon.find_by_code(Coupon::ECONOMY_SHIPPING_MATCH_CRM_COUPON_CODE) return unless economy_shipping_match_crm_coupon.present? && order.line_items.shipping_only.present? # only proceed if we have the shipping line, coupon # Free-online-shipping and the match must never both credit shipping # (SO728077 defect C). Clear every live FS discount before crediting the # match — all of them, not `.first`, and even zero-amount rows (the next # recalculation would re-credit those) — but never the blacklisted ones # (DeleteDiscount's un-blacklist branch would revive them). order.discounts.free_online_shipping.non_blacklisted.to_a.each do |fs_discount| Coupon::DeleteDiscount.new.perform(fs_discount, { skip_lock_check: true }) end if order.discounts.with_economy_shipping_match_crm.present? # if order had economy_shipping_match_crm_coupon, remove it now economy_shipping_match_crm_discount = order.discounts.with_economy_shipping_match_crm.first Coupon::DeleteDiscount.new.perform(economy_shipping_match_crm_discount, { skip_lock_check: true }) order.reload.reset_discount(reset_item_pricing: false) # reset the discount end amount = economy_shipping_match_amount(order) return if amount.nil? # snapshot missing, no drift, or would-be surcharge — see helper discount = Discount.new(itemizable: order, coupon_id: economy_shipping_match_crm_coupon.id, effective_date: Date.current) shipping_li = order.line_items.shipping_only.first return if shipping_li.nil? # the discount recalcs above can rebuild shipping lines; nothing left to credit against discount.line_discounts.build(coupon_id: economy_shipping_match_crm_coupon.id, amount:, line_item_id: shipping_li.id) if discount.line_discounts.any? discount.amount = discount.user_amount = discount.line_discounts.to_a.sum(&:amount) discount.save! end order.reload.calculate_tax_for_all_lines end |
#apply_warehouse_fee? ⇒ Boolean
Returns whether the record apply warehouse fee.
2412 2413 2414 2415 |
# File 'app/models/delivery.rb', line 2412 def apply_warehouse_fee? # only apply warehouse pickup to first warehouse pickup delivery, so total pickup fee is applied only once per ship_quotable is_warehouse_pickup? && (resource.deliveries.reload.first == self) end |
#at_least_one_shipment ⇒ Boolean
Validation guard: every shipping delivery (except service-only,
warehouse pickup, and European fulfillment) must have at least one
completed Shipment before we let it leave the warehouse.
2949 2950 2951 2952 2953 2954 2955 2956 |
# File 'app/models/delivery.rb', line 2949 def at_least_one_shipment ret = true if shipments.completed.empty? && !is_service_only? && !is_warehouse_pickup? && !european_shipment? ret = false errors.add(:base, 'at least one box/package must be defined') end ret end |
#authoritative_packing_for_shipment_contents? ⇒ Boolean
True when a Packing row exists for this delivery with origin from_delivery
(DeliveryMd5Extractor) or from_manual_entry (pre-pack). Shipment#unpack keeps
shipment_contents in that case so recalculate-shipping does not wipe allocations.
from_shipment is intentionally excluded (unused in production; legacy enum value).
1065 1066 1067 |
# File 'app/models/delivery.rb', line 1065 def Packing.where(delivery_id: id, origin: %i[from_delivery from_manual_entry]).exists? end |
#billing_entity ⇒ Object
Alias for Resource#billing_entity
221 |
# File 'app/models/delivery.rb', line 221 delegate :billing_entity, to: :resource |
#bol_pdf ⇒ Upload?
Latest bill-of-lading Upload attached to the delivery.
3365 3366 3367 |
# File 'app/models/delivery.rb', line 3365 def bol_pdf uploads.ship_bol_pdfs.first end |
#build_new_purchase_order_item(item, li, quantity, unit_cost) ⇒ PurchaseOrderItem
Builds an unsaved PurchaseOrderItem for a dropship line item with
supplier SKU/description, weights, costs, and auto-receive flag from
the linked SupplierItem.
2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 |
# File 'app/models/delivery.rb', line 2694 def build_new_purchase_order_item(item, li, quantity, unit_cost) PurchaseOrderItem.new( item:, sku: item.sku, description: item.name, quantity:, unit_weight: item.base_weight, total_weight: item.base_weight * quantity, unit_cost:, total_cost: unit_cost * quantity, uom: 'EA', unit_quantity: quantity, line_item: li, auto_receive: item.supplier_item.auto_receive, supplier_sku: item.supplier_item.supplier_sku, supplier_description: item.supplier_item.supplier_description ) end |
#build_purchase_order(supplier) ⇒ PurchaseOrder
Builds a new awaiting-transmission dropship PurchaseOrder for the
given supplier on the catalog's company/store, ready to receive
purchase order items.
2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 |
# File 'app/models/delivery.rb', line 2602 def build_purchase_order(supplier) PurchaseOrder.new( po_type: 'purchase', company: catalog.company, store: catalog.store, supplier:, terms: supplier.terms, state: 'awaiting_transmission', carrier: supplier, order_date: Date.current, request_date: Date.current, currency: supplier.currency, drop_ship: true, drop_ship_delivery: self ) end |
#calculate_all_cogs ⇒ BigDecimal
Total cost of goods sold for non-shipping LineItems on this delivery,
used by Models::Profitable margin computations and ledger entries.
943 944 945 |
# File 'app/models/delivery.rb', line 943 def calculate_all_cogs BigDecimal(line_items.non_shipping.sum('unit_cogs * quantity')) end |
#calculate_declared_value ⇒ BigDecimal
Aggregate declared/insured value for this delivery: for each goods
LineItem, quantity * unit_value_for_commercial_invoice. Eager
loads supplier-item prices to avoid N+1.
3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 |
# File 'app/models/delivery.rb', line 3781 def calculate_declared_value # Use the same logic as calculate_actual_insured_value to ensure declared value matches calculated value # Eager load item -> supplier_items -> supplier_item_prices to avoid N+1 queries # in unit_value_for_commercial_invoice -> unit_supplier_purchase_cost insured_value = line_items.goods.without_children .includes(item: { supplier_items: :supplier_item_prices }).sum do |li| unit_value = li.unit_value_for_commercial_invoice || 0 li.quantity * unit_value end insured_value end |
#calculate_grand_total ⇒ BigDecimal
Subtotal plus actual shipping cost — the value invoiced to the
customer at ship time (excluding tax and discounts already factored
into subtotal).
3772 3773 3774 |
# File 'app/models/delivery.rb', line 3772 def calculate_grand_total (actual_shipping_cost || 0.0) + subtotal end |
#calculate_shipping_options(options = {}) ⇒ Hash
Builds the option payload WyShipping.calculate_shipping_from_options
consumes. Extracted to CalculateShippingOptions (god-object
decomposition) — this stays as the public API for internal callers/tests.
1705 1706 1707 |
# File 'app/models/delivery.rb', line 1705 def ( = {}) Delivery::CalculateShippingOptions.new(self, ).process end |
#can_be_deleted? ⇒ Boolean
Returns whether the record can be deleted.
853 854 855 |
# File 'app/models/delivery.rb', line 853 def can_be_deleted? quoting? || pre_pack? || picking? || at_warehouse? || (order && order.order_type == Order::CREDIT_ORDER) end |
#can_print_carton_labels? ⇒ Boolean
Returns whether the record can print carton labels.
863 864 865 |
# File 'app/models/delivery.rb', line 863 def can_print_carton_labels? shipments.packed_or_measured.present? end |
#can_update_tracking_info? ⇒ Boolean
Returns whether the record can update tracking info.
858 859 860 |
# File 'app/models/delivery.rb', line 858 def can_update_tracking_info? shipments.completed.present? end |
#can_void_rma_delivery? ⇒ Boolean
Returns whether the record can void rma delivery.
977 978 979 |
# File 'app/models/delivery.rb', line 977 def can_void_rma_delivery? shipments.label_complete.any? && created_at > 30.days.ago # we implemented Shipengine for RMA carrier (UPS) on 2023-08-31 but there's a 30 day void deadline per: https://www.shipengine.com/docs/labels/voiding/#refund-process end |
#canadian_tire_special_check? ⇒ Boolean
Canadian tire requires all packages are less than 67 lbs to ship Purolator otherwise Consolidated Fastfrate LTL
2312 2313 2314 2315 2316 2317 2318 2319 |
# File 'app/models/delivery.rb', line 2312 def canadian_tire_special_check? customer&.billing_entity&.is_canadian_tire? && ( shipments.any? { |s| s.weight > CustomerConstants::CANADIAN_TIRE_LTL_FREIGHT_WEIGHT_THRESHOLD } || CustomerConstants::CANADIAN_TIRE_LTL_FREIGHT_REQUIRED_STORE_ADDRESS_IDS.include?(destination_address.id) || CustomerConstants::CANADIAN_TIRE_LTL_FREIGHT_REQUIRED_STORE_NUMBERS.include?(destination_address&.company_name&.split(' #')&.last) ) end |
#cancel ⇒ Boolean
Cancels the delivery in a transaction: releases reserved serial numbers
and committed inventory, voids any in-flight marketplace labels (Walmart
SWW, Amazon Buy Shipping), unpacks shipments, and cancels associated
dropship PurchaseOrders and pre-created Rma. No-op (and adds an
error) if the delivery is not in a cancelable state.
1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 |
# File 'app/models/delivery.rb', line 1183 def cancel Delivery.transaction do res = false if cancelable? logger.info 'Before delivery cancel' uncommit_reserved_serial_numbers rejoin_serial_numbers # Void marketplace labels (Walmart SWW, Amazon, etc.) before canceling shipments void_marketplace_labels shipments.awaiting_label.each(&:pack) unless pre_pack? uncommit_catalog_items shipments.packed.each(&:unpack) # This prevents shipments from changing when any conditions (like changing items) causes shipping to be recalculated, we should allow shipments to be resettable # unless all_lines_allocated_to_shipments? end # this is issued so that the qty_available goes back up drop_ship_purchase_orders.each(&:cancel_items_and_self) precreated_rma.void_all_items_and_self if precreated_rma.present? res = true else errors.add(:base, "Can't cancel delivery #{name}, ID: #{id} in state: #{state}, it is not in a cancelable state.") end res end end |
#cancel_estimated_packaging ⇒ Boolean
Aborts an in-progress pre-pack and returns the delivery to
quoting. Wraps the state machine event with the same name.
4457 4458 4459 |
# File 'app/models/delivery.rb', line 4457 def cancel_estimated_packaging back_to_quoting end |
#cancelable?(current_user = nil) ⇒ Boolean
These methods above are from the model previously known as delivery_quote
2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 |
# File 'app/models/delivery.rb', line 2496 def cancelable?(current_user = nil) if current_user # lock FBA based on criteria unless an admin return false if locked_for_fba? && !current_user.has_role?('admin') # here we are role sensitive so only let warehouse reps cancel certain states when role sensitive ANY_EMPLOYEE_CANCELABLE_STATES.include?(state.to_sym) || (WAREHOUSE_CANCELABLE_STATES.include?(state.to_sym) && current_user.has_role?('warehouse_rep')) else # here we are not role sensitive so let any cancelable states through CANCELABLE_STATES.include?(state.to_sym) end end |
#cannot_ship_empty_delivery? ⇒ Boolean
Guard method for shipping state transitions.
Returns true if shipping should be BLOCKED (used with :unless in state machine).
2980 2981 2982 |
# File 'app/models/delivery.rb', line 2980 def cannot_ship_empty_delivery? !has_shippable_content? end |
#carrier_customs_email ⇒ String?
Customs-team email address for the reported carrier when manual
customs handoff is required (Freightquote dispatches by SCAC, others
by carrier name).
2935 2936 2937 2938 2939 2940 2941 2942 |
# File 'app/models/delivery.rb', line 2935 def carrier_customs_email return unless should_send_commercial_invoice_to_carrier? # just return nil unless we qualify if reported_carrier == 'Freightquote' # Deal with Freightquote special case FREIGHTQUOTE_CARRIERS_TO_SEND_COMMERCIAL_INVOICES.detect{|fcarr| fcarr.dig(:scac) == selected_shipping_cost&.rate_data&.dig('scac')}&.dig(:customs_email) else CARRIERS_TO_SEND_COMMERCIAL_INVOICES.detect{|carr| carr.dig(:name) == reported_carrier}&.dig(:customs_email) end end |
#carrier_icon ⇒ String
Asset path / icon class for the delivery's carrier, used in
warehouse and customer dashboards.
3568 3569 3570 |
# File 'app/models/delivery.rb', line 3568 def carrier_icon Shipment.carrier_icon(carrier) end |
#carrier_options_for_select ⇒ Array<Array(String, String)>
Carriers eligible for this delivery formatted for a <select> helper —
filtered by origin country, supplier capabilities, and override flags.
1073 1074 1075 |
# File 'app/models/delivery.rb', line 1073 def Shipment.(self) end |
#catalog ⇒ Object
Alias for Customer#catalog
222 |
# File 'app/models/delivery.rb', line 222 delegate :catalog, :is_amazon_seller_central?, to: :customer |
#chosen_shipping_method ⇒ ShippingCost?
The ShippingCost the order is currently committed to — explicit
selected_shipping_cost if present, otherwise the cost linked to the
shipping LineItem. Service-only deliveries default to the first
available cost row.
2027 2028 2029 2030 2031 |
# File 'app/models/delivery.rb', line 2027 def chosen_shipping_method return shipping_costs.first if is_service_only? selected_shipping_cost || shipping_line_item.try(:shipping_cost) end |
#chosen_shipping_method_carrier_cost ⇒ Float
Carrier-quoted cost for the chosen shipping method, falling back to
the rate_data actual_cost when the row was zeroed out (e.g. customer
third-party billing accounts where we don't bill the rate).
1990 1991 1992 1993 1994 |
# File 'app/models/delivery.rb', line 1990 def chosen_shipping_method_carrier_cost c = chosen_shipping_method&.cost.to_f rd = chosen_shipping_method&.rate_data (c == 0.0 && rd.present? ? rd['actual_cost'].to_f : c) end |
#clear_shipping_costs_safely ⇒ Object
Safely clears shipping_costs by first nullifying shipping_cost_id on line_items
to prevent FK violation when the line_items are later saved with stale references.
The FK constraint is ON DELETE CASCADE, but delete_all bypasses Rails callbacks
and the in-memory line_items collection retains stale shipping_cost_id values.
2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 |
# File 'app/models/delivery.rb', line 2093 def clear_shipping_costs_safely # Nullify shipping_cost_id on in-memory line_items to prevent FK violation during save line_items.select(&:is_shipping?).each do |li| li.shipping_cost_id = nil if li.shipping_cost_id.present? end # Also update any persisted line_items in the database line_items.where.not(shipping_cost_id: nil).update_all(shipping_cost_id: nil) # Now safe to delete shipping_costs shipping_costs.delete_all end |
#commit_catalog_items ⇒ void
This method returns an undefined value.
Decrements available inventory for all of this delivery's
LineItems via Item::InventoryCommitter — invoked at ship time
to convert reserved stock into shipped stock.
3723 3724 3725 |
# File 'app/models/delivery.rb', line 3723 def commit_catalog_items Item::InventoryCommitter.crm_commit(line_items) end |
#commit_reserved_serial_numbers ⇒ void
This method returns an undefined value.
Promotes all reserved SerialNumbers on the delivery's line items
to "committed" so they're attached to the shipped units.
3731 3732 3733 |
# File 'app/models/delivery.rb', line 3731 def commit_reserved_serial_numbers line_items.each(&:commit_reserved_serial_numbers) end |
#complete_picked ⇒ void
This method returns an undefined value.
End-of-pick / end-of-pre-pack handler invoked from the warehouse UI:
transitions pre_pack deliveries through the pre-packed flow with
parent-order notifications, and transitions picking /
pending_ship_labels through the picked event when allocation is
complete.
4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 |
# File 'app/models/delivery.rb', line 4415 def complete_picked if pre_pack? pre_packed if all_lines_allocated_to_shipments_and_shipments_have_weight? && quoting? # this means it successfully completed pre_pack, all items allocated,we need this all_lines_allocated_to_shipments_and_shipments_have_weight? call to populate the errors shipments.suggested.each(&:pack!) # mark shipments as actually packed # now handle order and quote flow notifications and state flow # The two notification branches publish Events::DeliveryPrePacked via # send_delivery_pre_packed_notification; Shipping::DeliveryPrePackPackingHandler # subscribes to that event and writes the Packing row with # origin: :from_manual_entry, replacing the previous synchronous # set_packaged_items_md5_hash(origin: :from_manual_entry) call. # # The wasn4_or_wat0f branch does not publish DeliveryPrePacked # (intentionally — it has its own carrier-assignment notification # path), so it keeps the synchronous Packing write to preserve the # original "for all pre-packs, add to md5 Packing db" guarantee. if order&.pre_pack? # order pre_pack flow has its own notifications if order&.is_wasn4_or_wat0f? order.request_carrier_assignment set_packaged_items_md5_hash(origin: :from_manual_entry) else order.release_from_pre_pack_to_cr_hold send_delivery_pre_packed_notification end else quote.ready_to_transmit if quote&.pre_pack? send_delivery_pre_packed_notification end end elsif picking? || pending_ship_labels? # || processing_po_fulfillment?) # rb_any_ship_from here we are plan to ship-label the drop-ship delivery picked if all_lines_allocated_to_shipments_and_shipments_have_weight? && pending_ship_labels? # we need this all_lines_allocated_to_shipments_and_shipments_have_weight? call to populate the errors shipments.suggested.each(&:pack!) # mark shipments as actually packed end end end |
#completed_regular_delivery? ⇒ Boolean
Returns whether the record completed regular delivery.
1363 1364 1365 |
# File 'app/models/delivery.rb', line 1363 def completed_regular_delivery? (pending_pickup_confirm? || shipped? || invoiced?) && !is_service_only? end |
#copy_shipments_if_drop_ship_po ⇒ Object
Copies shipments from associated drop ship purchase orders
to this delivery's shipments. This is done after a drop ship
purchase order is fulfilled to copy the shipment details over.
4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 |
# File 'app/models/delivery.rb', line 4028 def copy_shipments_if_drop_ship_po return unless has_dropship_items? commit_catalog_items # this is issued so that the qty_available goes down # clear out suggested shipments shipments.suggested.destroy_all drop_ship_purchase_orders.each do |drop_ship_po| drop_ship_po.purchase_order_shipments.each do |pos| shipments.create( is_legacy: false, is_manual: true, state: 'manually_complete', delivery_id: id, order_id: order.id, tracking_number: pos.tracking_number, carrier: pos.drop_ship_carrier, container_type: pos.container_type, weight: pos.weight, width: pos.width, length: pos.length, height: pos.height, actual_total_charges: pos.actual_shipping_cost ) carrier_bol ||= pos.bill_of_lading if pos.bill_of_lading.present? # puts "Delivery#copy_shipments_if_drop_ship_po: delivery: #{self.name}, self.shipments: #{self.shipments.inspect}" end end set_master_tracking_and_actual_shipping_cost_if_needed end |
#country ⇒ Country?
Country the delivery is associated with for currency, tax, and
carrier defaults. RMA returns prefer the address country when
origin/destination match, otherwise the customer's country.
3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 |
# File 'app/models/delivery.rb', line 3798 def country if rma_for_return.present? # For RMA deliveries, determine country based on origin and destination addresses # If both addresses are in the same country, use that country # Otherwise, fall back to customer's country if origin_address && destination_address && origin_address.country_iso3 == destination_address.country_iso3 origin_address.country else rma_for_return.customer.country end else order&.country || resource&.country end end |
#create_invoice ⇒ Invoice
Builds the Invoice for this shipped delivery via
Invoicing::CreateInvoiceFromDelivery, validating the delivery and
its payments first. Service raises on any failure so callers can
surface the error.
3951 3952 3953 3954 3955 3956 3957 |
# File 'app/models/delivery.rb', line 3951 def create_invoice raise "Delivery ID: #{id} is not valid, errors #{errors_to_s}" unless valid? raise "A Payment on Delivery ID: #{id} is not valid" unless payments.all?(&:valid?) # Service handles all invoice creation, validation, and raises on any failure Invoicing::CreateInvoiceFromDelivery.new.process(self) end |
#create_shipments_from_equivalent_delivery(equivalent_delivery, shipment_state = 'awaiting_label') ⇒ void
This method returns an undefined value.
Clones Shipments and shipment contents from another delivery
(e.g. when re-creating a voided shipment plan) onto this delivery,
mapping items by id and respecting per-line allocation limits.
4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 |
# File 'app/models/delivery.rb', line 4333 def create_shipments_from_equivalent_delivery(equivalent_delivery, shipment_state = 'awaiting_label') Shipment.transaction do # here we want to copy the shipments and shipments contents from an equivalent delivery item_map = {} item_map = Shipping::CreateSuggestedShipment.new.build_item_map_from_line_items(line_items) if equivalent_delivery.shipments.where(state: shipment_state).all? { |s| s.shipment_contents.present? } equivalent_delivery.shipments.where(state: shipment_state).find_each do |s| suggested_shipment = shipments.create(weight: s.weight, length: s.length, width: s.width, height: s.height, is_legacy: false, is_manual: false, flat_rate_package_type: s.flat_rate_package_type, container_type: s.container_type, state: 'suggested') if suggested_shipment.errors.present? || !suggested_shipment.persisted? logger.error "Could not create suggested shipment: #{suggested_shipment.errors_to_s}" elsif item_map.present? && s.shipment_contents.present? s.shipment_contents.each do |shipment_content| # this returns an array of [line item id, quantities] qty_remaining_to_allocate = shipment_content.quantity # Sometimes garbage data or mismatch can cause an item to be fully allocated already break if item_map[shipment_content.line_item.item.id].nil? item_map[shipment_content.line_item.item.id].each do |(line_item_id, line_quantity)| allocatable_qty = [qty_remaining_to_allocate, line_quantity].min sc = suggested_shipment.shipment_contents.where(line_item_id:).first_or_initialize sc.quantity = allocatable_qty sc.save # Remove or reduce the quantity from this line item_map[shipment_content.line_item.item.id][line_item_id] -= allocatable_qty # If the line is fully allocated, we remove this entry item_map[shipment_content.line_item.item.id].delete(line_item_id) if item_map[shipment_content.line_item.item.id][line_item_id] <= 0 # and remove the item id if its all allocated item_map.delete(shipment_content.line_item.item.id) if item_map[shipment_content.line_item.item.id].empty? end # If we allocated everything in this box we move on break if qty_remaining_to_allocate.zero? end # map shipment contents end end end end |
#create_st_ledger_entries ⇒ void
This method returns an undefined value.
Records intra-company GL and item-ledger entries for an in-flight
store-transfer delivery (one warehouse to another inside the same
company).
3939 3940 3941 3942 |
# File 'app/models/delivery.rb', line 3939 def create_st_ledger_entries LedgerTransaction.process_intracompany_st_delivery(self) ItemLedgerEntry.process_intracompany_st_delivery(self) end |
#currency ⇒ String?
ISO-4217 currency code the delivery transacts in, falling through
order → resource → RMA customer's catalog.
3817 3818 3819 |
# File 'app/models/delivery.rb', line 3817 def currency order&.currency || resource&.currency || rma_for_return&.customer&.catalog&.currency end |
#currency_symbol ⇒ String
Glyph for #currency (e.g. "$", "€") for display formatting.
3824 3825 3826 |
# File 'app/models/delivery.rb', line 3824 def currency_symbol Money::Currency.new(currency).symbol end |
#custom_pack_list ⇒ Upload?
1451 1452 1453 1454 1455 1456 |
# File 'app/models/delivery.rb', line 1451 def custom_pack_list cpl = order.uploads.in_category('custom_packing_slip_pdf').first return cpl if cpl&.file_exists? # commenting out for now because this returns false though it actually does exist and can be downloaded combined etc. nil end |
#customer ⇒ Object
Alias for Resource_or_rma_for_delivery#customer
220 |
# File 'app/models/delivery.rb', line 220 delegate :customer, :primary_party, to: :resource_or_rma_for_delivery |
#declaration_232_pdf ⇒ Upload?
Most recent generated Section 232 declaration PDF Upload.
3398 3399 3400 |
# File 'app/models/delivery.rb', line 3398 def declaration_232_pdf uploads.order(:id).reverse_order.find_by(category: 'declaration_232_pdf') end |
#default_container_type ⇒ String
Default packaging container for new Shipments on this delivery —
pallet for LTL freight, otherwise carton.
4402 4403 4404 4405 4406 |
# File 'app/models/delivery.rb', line 4402 def default_container_type # Shipment.container_types.keys[1] # 'pallet' # Shipment.container_types.keys.first # 'carton' ships_ltl_freight? ? Shipment.container_types.keys[1] : Shipment.container_types.keys.first end |
#delete_serial_number_reservations ⇒ void
This method returns an undefined value.
Destroys every ReservedSerialNumber on this delivery's line
items, releasing them for reuse.
4195 4196 4197 |
# File 'app/models/delivery.rb', line 4195 def delete_serial_number_reservations line_items.each { |li| li.reserved_serial_numbers.destroy_all } end |
#delta_weight_factor_remaining_to_allocate ⇒ Float
Fraction of the delivery's expected ship weight still represented by
un-allocated LineItems. Used to gate "ready to ship-label" UX so the
warehouse doesn't try to label a partially packed shipment.
1110 1111 1112 |
# File 'app/models/delivery.rb', line 1110 def delta_weight_factor_remaining_to_allocate (line_allocation_status_hash.sum{|k,v| LineItem.find(k).shipping_weight*v.abs}.to_f/ship_weight) end |
#destination_address ⇒ Address?
Returns the destination address this record belongs to.
Validations (unless => #skip_destination_address_validation? ):
119 |
# File 'app/models/delivery.rb', line 119 belongs_to :destination_address, class_name: 'Address', validate: true, optional: true |
#discounts ⇒ ActiveRecord::Relation<Discount>
Returns the associated discounts.
142 |
# File 'app/models/delivery.rb', line 142 has_many :discounts, through: :line_discounts |
#display_carrier_name ⇒ Object
When Freightquote is used as a broker, the human-readable carrier is stored
in rate_data rather than on the delivery itself. Fall back to reported_carrier
for all other carriers, or when rate_data has no carrier_name.
2198 2199 2200 |
# File 'app/models/delivery.rb', line 2198 def display_carrier_name chosen_shipping_method&.rate_data&.[]('carrier_name') || reported_carrier end |
#do_not_ship_insure_via_carrier? ⇒ Boolean
Returns whether the record do not ship insure via carrier.
2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 |
# File 'app/models/delivery.rb', line 2005 def do_not_ship_insure_via_carrier? res = false # RMA returns are never carrier/ShipEngine ship-insured — coverage, if any, # is via the separate Shipsurance return path (ReturnShippingInsurance). # Carriers reject insurance on return labels outright (AppSignal #4508). # Short-circuits first so a return never falls through to the carrier # declared-value fallback below. res = true if is_rma_return? res = true if Shipping::ShippingInsurance.new.(self) res = true if order&.is_sales_order? && order.is_edi_order? && customer.is_wayfair? # we do not declare value for Wayfair EDI orders # we do not declare value for THD orders of any kind per MARIA_C_WASSER@homedepot.com and ALEX_T_LOVELL@homedepot.com 4/30/24 res = true if order&.is_sales_order? && (order&.is_home_depot_usa? || order&.is_home_depot_can?) res = true if ships_ltl_freight? && Shipping::LtlShippingInsurance::LTL_SELF_INSURED res end |
#do_not_validate_line_items? ⇒ Boolean
Returns whether the record do not validate line items.
2959 2960 2961 |
# File 'app/models/delivery.rb', line 2959 def do_not_validate_line_items? do_not_validate_line_items end |
#does_not_require_shipping_labeling? ⇒ Boolean
Returns whether the record does not require shipping labeling.
4505 4506 4507 4508 |
# File 'app/models/delivery.rb', line 4505 def does_not_require_shipping_labeling? order&.is_store_transfer? && customer&.id&.in?(CustomerConstants::TRANSFER_TO_WY_CUSTOMER_IDS) end |
#drop_ship_purchase_orders ⇒ ActiveRecord::Relation<PurchaseOrder>
Returns the associated drop ship purchase orders.
148 |
# File 'app/models/delivery.rb', line 148 has_many :drop_ship_purchase_orders, class_name: 'PurchaseOrder', foreign_key: 'drop_ship_delivery_id' |
#economy_shipping_match_amount(order) ⇒ BigDecimal, ...
Signed credit for the economy shipping match, or nil when the match must
not apply. A "match" only ever credits the customer the gap between the
live carrier rate and the economy rate they locked in at checkout, so a
valid amount is always <= 0 (checkout snapshot - live cost).
Guards the two ways this corrupted an order in SO728077:
- Missing snapshot (defect B): never fabricate the checkout price from
the live rate. A nilshipping_cost_at_time_of_checkoutmakes the match
inapplicable, not a $0 (or self-referential) match. - Positive amount (defect A): a snapshot above the live rate would
charge the customer. Skip and alert rather than surcharge.
1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 |
# File 'app/models/delivery.rb', line 1814 def economy_shipping_match_amount(order) snapshot = order.shipping_cost_at_time_of_checkout if snapshot.nil? ErrorReporting.warning('Economy shipping match skipped: shipping_cost_at_time_of_checkout is nil', order_id: order.id) return nil end # Compare against the RAW shipping-line price, not the `shipping_cost` # column: that column is the post-discount net, so any live shipping # credit (FS, or a previously applied match's own line discount) pollutes # it — re-applying the match then computed "no drift" against its own # credit, destroyed the old match, and silently stripped the customer's # locked-in economy rate (SO728077 defect C follow-on). live_cost = order.undiscounted_shipping_total amount = snapshot - live_cost return nil unless amount.abs > 0.0 # no drift between checkout and live rate if amount.positive? # snapshot exceeds live rate: matching would surcharge the customer ErrorReporting.warning('Economy shipping match would surcharge the customer; skipping', order_id: order.id, custom_data: { checkout_snapshot: snapshot.to_f, live_cost: live_cost.to_f, would_be_amount: amount.to_f }) return nil end amount end |
#edi_ship_confirm_already_sent? ⇒ Boolean
True when a ship confirm carrying the delivery's current tracking number
has already been recorded for this order. Guards the invoiced catch-up so
a confirm isn't sent twice for the same shipment — e.g. an LTL delivery
whose PRO was already confirmed at pending_ship_confirm, or a re-invoice.
The check is tracking-number-aware, not "ever sent," so a re-send stays
possible: after labels are voided and re-generated a fresh PRO is booked, so
the prior confirm's tracking won't match the new one and the catch-up
re-sends. (Manual re-sends via the EDI-log UI or console call the sender
directly and bypass this guard entirely.)
order_acknowledge is Amazon Seller Central's ship-confirm category — the
only EDI channel shipping LTL today — whose payload nests the tracking
number at packageDetail.trackingNumber.
2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 |
# File 'app/models/delivery.rb', line 2852 def edi_ship_confirm_already_sent? return false unless order tracking_numbers = shipments.completed.filter_map(&:display_tracking_number).uniq return false if tracking_numbers.empty? order.edi_communication_logs.where(category: :order_acknowledge).any? do |ecl| tracking_numbers.include?(ecl.data_as_hash&.dig('packageDetail', 'trackingNumber')) end end |
#electronic_ship_ci_pdf ⇒ Upload?
Most recent carrier-electronic commercial-invoice Upload (for UPS
paperless invoicing and similar).
3414 3415 3416 |
# File 'app/models/delivery.rb', line 3414 def electronic_ship_ci_pdf uploads.order(:id).reverse_order.find_by(category: 'electronic_ship_ci_pdf') end |
#estimated_delivery_date ⇒ Object
Trying to 'guess' the delivery date
845 846 847 848 849 850 |
# File 'app/models/delivery.rb', line 845 def estimated_delivery_date carrier_commitment = (selected_shipping_cost || shipping_option)&.days_commitment&.ceil || 7 ship_date = shipped_date || future_release_date || 0.working.day.from_now ship_date += carrier_commitment.days ship_date.to_date end |
#estimated_tare_weight ⇒ Float
Memoized total tare weight across the delivery's top-level
Shipments, preferring measured shipments, then packed, then any.
Used in weight-discrepancy threshold checks.
4130 4131 4132 4133 4134 4135 4136 |
# File 'app/models/delivery.rb', line 4130 def estimated_tare_weight return @estimated_tare_weight if instance_variable_defined?(:@estimated_tare_weight) top_level = shipments.top_level shipments_to_check = top_level.measured.presence || top_level.packed.presence || top_level @estimated_tare_weight = shipments_to_check.to_a.sum { |s| s.compute_tare_weight.to_f } end |
#european_shipment? ⇒ Boolean
Returns whether the record european shipment.
4511 4512 4513 |
# File 'app/models/delivery.rb', line 4511 def european_shipment? customer&.catalog&.store&.country&.eu_country? # This might be too loose but for now it works end |
#existing_shipment_attributes=(shipment_attributes) ⇒ void
This method returns an undefined value.
Nested-attributes setter for editing or removing already-persisted
Shipments — rows missing from the hash are removed via the
association.
3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 |
# File 'app/models/delivery.rb', line 3058 def existing_shipment_attributes=(shipment_attributes) shipments.reject(&:new_record?).each do |shipment| attributes = shipment_attributes[shipment.id.to_s] if attributes shipment.attributes = attributes else shipments.delete(shipment) end end end |
#existing_shipping_cost_attributes=(shipping_cost_attributes) ⇒ void
This method returns an undefined value.
Nested-attributes setter for editing or removing already-persisted
ShippingCost rows from a delivery form. Rows missing from the hash
are deleted via the association.
1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 |
# File 'app/models/delivery.rb', line 1972 def existing_shipping_cost_attributes=(shipping_cost_attributes) # puts "existing_shipping_cost_attributes= : shipping_cost_attributes: #{shipping_cost_attributes}" shipping_costs.reject(&:new_record?).each do |sc| attributes = shipping_cost_attributes[sc.id.to_s] if attributes sc.attributes = attributes sc.save # trying to fix itemizable before save prevention of saving these on order.update else shipping_costs.delete(sc) end end end |
#freight_events ⇒ ActiveRecord::Relation<FreightEvent>
CHR/Freightquote Navisphere events landed via the webhook pipeline; consumed by
FreightEventStatusSummary for the warehouse-dashboard and delivery-show
status icons, and by MissedFreightPickupSweep / FreightquoteVoidConfirmationWorker.
Ordered newest-first by emitted_at (the CHR webhook-gateway emission
timestamp from payload['time']) because CHR's per-subsystem eventTime
values aren't monotonically ordered across event types — LOAD BOOKED's
eventTime can precede the LOAD CREATED that conceptually came first.
emitted_at is CHR's single monotonic clock; event_time and id are
deterministic tiebreakers for the sub-millisecond races (ORDER CREATED +
ORDER UPDATED arriving in the same packet).
172 173 |
# File 'app/models/delivery.rb', line 172 has_many :freight_events, -> { order(emitted_at: :desc, event_time: :desc, id: :desc) }, inverse_of: :delivery, dependent: :nullify |
#freightquote_carrier? ⇒ Boolean
Returns whether the record freightquote carrier.
2191 2192 2193 |
# File 'app/models/delivery.rb', line 2191 def freightquote_carrier? reported_carrier.to_s.include?('Freightquote') end |
#friendly_shipping_method(show_customer_pays_info = false, for_www = false, with_delivery_commitment = false) ⇒ String
Cached, fully decorated shipping method label (carrier, service,
COD/insurance/account notes) suitable for emails, invoices, and
confirmation pages.
2112 2113 2114 2115 2116 2117 2118 |
# File 'app/models/delivery.rb', line 2112 def friendly_shipping_method(show_customer_pays_info = false, for_www = false, with_delivery_commitment = false) if show_customer_pays_info @friendly_shipping_method_with_pay_info ||= retrieve_friendly_shipping_method(show_customer_pays_info, for_www, nil, with_delivery_commitment) else @friendly_shipping_method ||= retrieve_friendly_shipping_method(show_customer_pays_info, for_www, nil, with_delivery_commitment) end end |
#friendly_shipping_method_for_edi ⇒ String
EDI-flavored variant of #friendly_shipping_method — strips the
FedEx signature notice and other consumer-facing decorations partner
systems don't want.
2170 2171 2172 |
# File 'app/models/delivery.rb', line 2170 def friendly_shipping_method_for_edi retrieve_friendly_shipping_method(false, false, nil, false, true) end |
#generate_all_international_forms_pdf ⇒ Boolean, Array<Upload>
Combines the commercial invoice, BOL, USMCA/FTA item certificates,
and the latest steel/aluminum/copper declaration into a single
all_intl_forms_pdf Upload for international shipments.
3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 |
# File 'app/models/delivery.rb', line 3155 def generate_all_international_forms_pdf res = true # puts "self.generate_all_international_forms_pdf, delivery #{self.id}: #{self.inspect}" all_forms = [ship_ci_pdf, bol_pdf].compact # Include USMCA/FTA certificates attached to items on this delivery begin item_ids = line_items.goods.pluck(:item_id) if item_ids.present? usmca_cert_uploads = Upload.where(category: 'usmca_certificate') .joins(:items) .where(items: { id: item_ids }) .to_a all_forms.concat(usmca_cert_uploads) if usmca_cert_uploads.present? end rescue StandardError => e Rails.logger.warn("generate_all_international_forms_pdf: USMCA cert include failed: #{e}") end # Dynamic per-delivery Section 232 declaration (replaces the stale global # `declaration_of_steel_aluminimum_copper` upload that stapled ST711716's # hand-filled form onto every international delivery until 2026-07 and got # ST728704 held at the border). Rescued so a 232 failure still ships the # rest of the packet — the broker can request the declaration separately. begin # Gate the attachment on the same predicate as generation — a delivery # that once qualified (and has an old declaration upload) but no longer # does must not re-attach the stale form. The declaration is normally # generated at the order's awaiting_deliveries transition (line items are # immutable past that point without a CR HOLD); this is only a backfill # for deliveries that slipped through without one. if requires_232_declaration? generate_declaration_232_forms if declaration_232_pdf.nil? all_forms << declaration_232_pdf if declaration_232_pdf end rescue StandardError => e Rails.logger.warn("generate_all_international_forms_pdf: 232 declaration failed: #{e}") end # Add Commercial Invoice if all_forms.present? file_name = "#{name(false, true)}_all_intl_forms_#{Time.current.strftime('%m_%d_%Y_%I_%M%p')}.pdf".downcase all_forms_path = Rails.application.config.x.temp_storage_path.join(file_name) # puts "self.generate_all_international_forms_pdf: all_forms_path: #{all_forms_path}, all_forms: #{all_forms.inspect}" PdfTools.combine(all_forms, output_file_path: all_forms_path) upload = Upload.uploadify(all_forms_path, 'all_intl_forms_pdf') raise 'Combo international forms was not generated' unless upload res = uploads << upload end res end |
#generate_all_labels_pdf ⇒ Object
Generate all labels pdf.
3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 |
# File 'app/models/delivery.rb', line 3108 def generate_all_labels_pdf all_shipments = shipments.completed.order(:id).includes(:uploads) # Computed once (single BOL per delivery); folded into the first batch only. ltl_bol = combined_labels_ltl_bol # Batching since the tmp folder will only reliably hold ~15 tmp files before GC, so use batches of 10 batch_size = 10 (all_shipments.size.to_f / batch_size.to_f).ceil.times do |i| all_shipments_batch = all_shipments[(i * batch_size)..((batch_size * (i + 1)) - 1)] all_labels = [] all_labels << ltl_bol if i.zero? && ltl_bol all_shipments_batch.each do |s| label = s.uploads.detect { |u| u.category == 'ship_label_pdf' } next unless label all_labels << label end rmas = [] rmas << order.rma if order&.is_rma_replacement? rmas << precreated_rma if precreated_rma.present? rmas.each do |rma| rma.credit_orders.each do |co| co.shipments.label_complete.order(:id).includes(:uploads).each do |s| label = s.uploads.detect { |u| u.category == 'ship_label_pdf' } all_labels << label if label end end end serial_numbers_pdf = generate_serial_numbers_pdf all_labels << serial_numbers_pdf unless serial_numbers_pdf.nil? all_labels.concat(order.uploads.in_category('master_carton_label')) if order # include any master carton labels file_name = "batch_#{i}_#{name(false, true)}_all_labels_#{Time.current.strftime('%m_%d_%Y_%I_%M%p')}.pdf".downcase all_labels_path = Rails.application.config.x.temp_storage_path.join(file_name) Rails.logger.debug("generate_all_labels_pdf", path: all_labels_path, label_count: all_labels&.size) PdfTools.combine(all_labels, output_file_path: all_labels_path) upload = Upload.uploadify(all_labels_path, 'all_labels_pdf') raise 'Combo label was not generated' unless upload uploads << upload end end |
#generate_asynch_labels ⇒ Object
Generate asynch labels.
3348 3349 3350 3351 3352 3353 |
# File 'app/models/delivery.rb', line 3348 def generate_asynch_labels generate_bol_pdf generate_ci_pdf generate_all_labels_pdf generate_all_international_forms_pdf end |
#generate_barcode ⇒ String
Writes a Code 128B barcode of the parent order's reference number to
tmp/ for embedding into pick-slip / packing-slip PDFs.
1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 |
# File 'app/models/delivery.rb', line 1520 def require 'zint' = Zint::Barcode.new(value: order.reference_number.to_s, symbology: Zint::Constants::Symbologies::BARCODE_CODE128) .height = 70 .show_hrt = 0 # barby rendered bars only; keep it that way path = File.join(Rails.application.config.x.temp_storage_path.to_s, "#{name(false, true)}_generated_barcode_#{Time.current.strftime('%m_%d_%Y_%I_%M%p')}.png") File.open(path, 'wb') do |file| file.write .to_memory_file(extension: '.png') file.flush file.fsync end path end |
#generate_bol_pdf(ship_bol_tmp_path = nil) ⇒ Array<Upload>
Produces the bill-of-lading PDF for an LTL freight shipment and
attaches it as an Upload. If the carrier supplied its own BOL we use
that file; otherwise we fall back to Shipping::BolGenerator. Two
copies are combined per warehouse policy.
1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 |
# File 'app/models/delivery.rb', line 1542 def generate_bol_pdf(ship_bol_tmp_path = nil) Rails.logger.info("generate_bol_pdf: ship_bol_tmp_path: #{ship_bol_tmp_path}") # if we have carrier generated BOL, use that if ship_bol_tmp_path files_to_combine = [ship_bol_tmp_path, ship_bol_tmp_path] # combine two copies into one per JJ @warehouse file_name = "#{reference_number}_BOL.pdf" ship_bol_pdf_path = Upload.temp_location(file_name) Rails.logger.info("generate_bol_pdf: ship_bol_pdf_path: #{ship_bol_pdf_path}") duplicate_bol_pdf_path = PdfTools.combine(files_to_combine, output_file_path: ship_bol_pdf_path, orientation: :portrait) Rails.logger.info("generate_bol_pdf: duplicate_bol_pdf_path: #{duplicate_bol_pdf_path}") upload = Upload.uploadify(duplicate_bol_pdf_path, 'ship_bol_pdf') else # otherwise try to generate it using our template from scratch res = Shipping::BolGenerator.new.process(self) combined_pdf = PdfCombinator.new 2.times do # combine two copies into one per JJ @warehouse combined_pdf << res.pdf end path = Upload.temp_location(res.file_name) File.open(path, 'wb') do |file| file.write(combined_pdf.to_pdf) file.flush file.fsync end upload = Upload.uploadify(path, 'ship_bol_pdf', nil, res.file_name) end raise 'BOL PDF was not generated' unless upload Rails.logger.debug("generate_bol_pdf complete", upload_id: upload&.id) uploads << upload end |
#generate_ci_pdf(ci_tmp_path = nil) ⇒ Array<Upload>
Produces the commercial invoice PDF (in triplicate) required for
international shipments and attaches it as an Upload. Uses the
carrier-supplied electronic CI when available, otherwise renders one
via Pdf::Document::CommercialInvoice.
1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 |
# File 'app/models/delivery.rb', line 1586 def generate_ci_pdf(ci_tmp_path = nil) # if we have carrier generated CI, use that # UPSFreight generates a CI in triplicate ie x 3 if ci_tmp_path # generate the pdf from the file path, note that this means it is a UPS **electronic** Commercial Invoice, not to be printed but stored for future reference. file_name = "order_#{order.reference_number}_delivery_#{index}_#{Time.current.strftime('%m_%d_%Y_%I_%M%p')}_ci.pdf" ci_pdf_path = Upload.temp_location(file_name) FileUtils.cp(ci_tmp_path, ci_pdf_path) # attach the finished CI pdf upload = Upload.uploadify(ci_pdf_path, 'electronic_ship_ci_pdf', nil, file_name) else # otherwise try to generate it using our template from scratch pdf_data = Pdf::Document::CommercialInvoice.new(self).call.pdf combined_pdf = PdfCombinator.new 3.times do combined_pdf << pdf_data end path = Upload.temp_location("#{reference_number}_CI.pdf") File.open(path, 'wb') do |file| file.write combined_pdf.to_pdf file.flush file.fsync end upload = Upload.uploadify(path, 'ship_ci_pdf', nil, "#{reference_number}_CI.pdf") end raise 'CI PDF was not generated' unless upload uploads << upload end |
#generate_combined_pdf(split_kits: false, skip_plans: false) ⇒ Object
Generate combined pdf.
1437 1438 1439 1440 1441 1442 1443 1444 |
# File 'app/models/delivery.rb', line 1437 def generate_combined_pdf(split_kits: false, skip_plans: false) # this must be here, even if just as a wrapper because the method above calls it! pdf_generator = Pdf::Document::PackingSlip.new(self, { split_kits:, skip_plans: }) pdf_data = pdf_generator.call pdf_data.pages.each { |p| p.orientation :portrait } pdf_data end |
#generate_declaration_232_forms ⇒ Boolean
Generates the Section 232 declaration pair — the broker's macro-enabled
xlsm (for electronic submission) and a printable PDF twin (folded into the
combined international forms packet) — and attaches both as uploads.
No-op for deliveries that don't require a declaration.
3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 |
# File 'app/models/delivery.rb', line 3382 def generate_declaration_232_forms return false unless requires_232_declaration? base = "#{name(false, true)}_232_declaration_#{Time.current.strftime('%m_%d_%Y_%I_%M%p')}".downcase uploads << Upload.uploadify_from_data(file_name: "#{base}.xlsm", data: Declaration232::XlsmWriter.new(self).call, category: 'declaration_232_xlsm') uploads << Upload.uploadify_from_data(file_name: "#{base}.pdf", data: Pdf::Document::Declaration232.new(self).call.pdf, category: 'declaration_232_pdf') true end |
#generate_dropship_po ⇒ Object
Generates dropship purchase orders and purchase order items for any dropship
line items on this delivery that do not already have associated purchase orders.
Groups line items by supplier, and line items for the same item. Calculates total
quantities and costs for each item group. Creates new purchase orders per supplier,
and adds purchase order items for each line item group. Associates the new
purchase order items with the delivery line items. Saves the purchase orders unless
they have no line items. Finally sends a dropship delivery notification email.
2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 |
# File 'app/models/delivery.rb', line 2560 def generate_dropship_po line_items_by_supplier = group_unprocessed_dropship_line_items_by_supplier existing_po_items = get_existing_po_items line_items_by_supplier.each do |supplier, line_items| po = build_purchase_order(supplier) group_line_items_by_item(line_items).each do |item, grouped_line_items| add_purchase_order_items(po, item, grouped_line_items, existing_po_items) end save_purchase_order_if_needed(po) end send_dropship_delivery_notification end |
#generate_labels ⇒ Hash{Symbol => Object}
Buys carrier labels for this delivery — advisory-locked guard-then-buy
flow lives in GenerateLabels (god-object decomposition).
3230 3231 3232 |
# File 'app/models/delivery.rb', line 3230 def generate_labels Delivery::GenerateLabels.new(self).process end |
#generate_pick_slip_pdf(split_kits = false) ⇒ Upload
Renders and persists a fresh pick-slip Upload for the warehouse,
writing the PDF through Pdf::Document::PackingSlip and Upload.uploadify.
1424 1425 1426 1427 1428 1429 1430 1431 1432 |
# File 'app/models/delivery.rb', line 1424 def generate_pick_slip_pdf(split_kits = false) cat = split_kits.to_b ? 'split_pick_slip_pdf' : 'pick_slip_pdf' combined_pdf = generate_combined_pdf(split_kits:) path = File.join(Rails.application.config.x.temp_storage_path.to_s, pick_slip_file_name) combined_pdf.save path upload = Upload.uploadify(path, cat, self, pick_slip_file_name) uploads << upload upload end |
#generate_serial_numbers_pdf ⇒ Upload?
Renders printable serial-number labels for every reserved
SerialNumber on this delivery, attaches the PDF as an Upload, and
marks the printed serials as such.
1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 |
# File 'app/models/delivery.rb', line 1469 def generate_serial_numbers_pdf return nil if serial_numbers_to_print.empty? file_name = serial_numbers_file_name pdf = Pdf::Label::SerialNumber.call(serial_numbers_to_print).pdf path = Upload.temp_location(file_name) File.open(path, 'wb') { |f| f.write(pdf); f.flush; f.fsync } upload = Upload.uploadify(path, 'serial_numbers_pdf', self, file_name) uploads << upload SerialNumber.where(id: serial_numbers_to_print.collect(&:id)).update_all(print_state: 'printed') upload end |
#get_address_hash_from_address(address) ⇒ Hash{Symbol => Object}
Flattens an Address into the plain hash carrier APIs and the
carrier_responses JSONB column expect.
1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 |
# File 'app/models/delivery.rb', line 1866 def get_address_hash_from_address(address) { street1: address.street1, street2: address.street2, city: address.city, state_code: address.state_code, country_iso: address.country_iso, zip: address.zip, is_residential: address.is_residential, require_signature_by_default: address.require_signature_by_default, has_loading_dock: address.has_loading_dock, is_construction_site: address.is_construction_site, requires_inside_delivery: address.requires_inside_delivery, is_trade_show: address.is_trade_show, requires_liftgate: address.requires_liftgate, limited_access: address.limited_access, requires_appointment: address.requires_appointment, timezone_name: address.timezone_name } end |
#get_economy_shipping_cost_to_use ⇒ Float
Cost we charge when a "ships economy" delivery's override row needs
a price. Locks to Order#shipping_cost_at_time_of_checkout when
available so cycling back through quoting (CR hold, address change,
pre-pack) never bumps the customer-facing override above what they
paid. Falls back to the cheapest WWW ground for orders with no
checkout snapshot (carts, instant quotes, CRM-built orders), and to
#get_fallback_cost_to_use when no rates are available.
4565 4566 4567 4568 4569 4570 |
# File 'app/models/delivery.rb', line 4565 def get_economy_shipping_cost_to_use locked = order&.shipping_cost_at_time_of_checkout return locked.to_f if locked.present? && locked.to_f > 0.0 sorted_shipping_costs_www_hash[:ground]&.first&.cost || get_fallback_cost_to_use end |
#get_existing_po_items ⇒ Array<PurchaseOrderItem>
Existing dropship PurchaseOrderItems on this delivery that have not
yet been linked to a specific LineItem — candidates for re-linking
rather than creating duplicates.
2592 2593 2594 |
# File 'app/models/delivery.rb', line 2592 def get_existing_po_items drop_ship_purchase_orders.flat_map(&:purchase_order_items).select { |poi| poi.line_item_id.nil? } end |
#get_fallback_cost_to_use ⇒ Float
Sentinel cost used when carrier APIs return no rates so the order
still has something to charge. Public WWW orders use a weight
× per-lb formula clamped between FALLBACK_MIN_OVERRIDE_COST_WWW
and a fraction of declared value; CRM uses the flat
FALLBACK_OVERRIDE_COST_CRM ($500) sentinel.
4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 |
# File 'app/models/delivery.rb', line 4579 def get_fallback_cost_to_use # here we separate out the usual $500 for override and instead come up with a reasonable WWW cost based on weight and/or calculated declared value, and $500 only for CRM override, to avoid the horrible customer experience we saw when all new Canadian customer checkouts were showing $500 shipping costs because of an error in country (fixed here: https://github.com/warmlyyours/heatwave/commit/fffed50980dec5f024cb01d957b2b42792cd1e37) if is_www? || ships_economy_package? fallback_cost_to_use = ship_weight * FALLBACK_PER_LB_OVERRIDE_COST_WWW fallback_cost_to_use = [fallback_cost_to_use, FALLBACK_MAX_OVERRIDE_COST_FRACTION_WWW * calculate_declared_value, FALLBACK_OVERRIDE_COST_CRM].min # here max out at the smallest of per lbs rate or a fraction of the calculated value or, finally, FALLBACK_OVERRIDE_COST_CRM fallback_cost_to_use = [fallback_cost_to_use, FALLBACK_MIN_OVERRIDE_COST_WWW].max # here minimum of above or nominal FALLBACK_MIN_OVERRIDE_COST_WWW starting cost else fallback_cost_to_use = FALLBACK_OVERRIDE_COST_CRM end fallback_cost_to_use end |
#get_or_generate_pick_slip_pdf(split_kits = false) ⇒ Upload
Returns the existing pick-slip Upload for this delivery or generates
one on the fly when missing or its attachment was lost.
1355 1356 1357 1358 1359 1360 |
# File 'app/models/delivery.rb', line 1355 def get_or_generate_pick_slip_pdf(split_kits = false) cat = split_kits ? 'split_pick_slip_pdf' : 'pick_slip_pdf' pdf = uploads.in_category(cat).first pdf = generate_pick_slip_pdf(split_kits) if pdf.nil? || (pdf && pdf..blank?) pdf end |
#group_line_items_by_item(line_items) ⇒ Hash{Item => Array<LineItem>}
2624 2625 2626 |
# File 'app/models/delivery.rb', line 2624 def group_line_items_by_item(line_items) line_items.group_by(&:item) end |
#group_unprocessed_dropship_line_items_by_supplier ⇒ Hash{Supplier => Array<LineItem>}
Dropship LineItems that still need a PurchaseOrder (no PO item
yet, or the existing one was cancelled), grouped by supplier so each
supplier gets its own PO.
2582 2583 2584 2585 |
# File 'app/models/delivery.rb', line 2582 def group_unprocessed_dropship_line_items_by_supplier # here we want the dropship line items that have yet to be processed, i.e. no linked dropship PO or the dropship PO item is cancelled, all group by supplier line_items.dropship.includes(:item).select { |li| li.purchase_order_item.nil? || li.purchase_order_item&.cancelled? }.group_by { |li| li.item.supplier_item.supplier } end |
#has_custom_products? ⇒ Boolean
Returns whether the record has custom products.
1574 1575 1576 |
# File 'app/models/delivery.rb', line 1574 def has_custom_products? line_items.joins(:item).where(items: { product_category_id: ProductCategory.custom_product_ids }).exists? end |
#has_custom_shipping_labels? ⇒ Boolean
Returns whether the record has custom shipping labels.
868 869 870 |
# File 'app/models/delivery.rb', line 868 def has_custom_shipping_labels? order.custom_shipping_labels.present? end |
#has_destination_postal_code ⇒ Boolean
Validation helper for instant-quote deliveries: requires either a
destination Address or an installation postal code on the parent
quote so rate shopping has somewhere to ship to.
2489 2490 2491 |
# File 'app/models/delivery.rb', line 2489 def has_destination_postal_code destination_address || resource.installation_postal_code.present? end |
#has_dropship_items? ⇒ Boolean
Returns whether the record has dropship items.
2544 2545 2546 |
# File 'app/models/delivery.rb', line 2544 def has_dropship_items? line_items.joins(:item).where(items: { dropship: true }).present? end |
#has_future_release_date? ⇒ Boolean
Returns whether the record has future release date.
948 949 950 |
# File 'app/models/delivery.rb', line 948 def has_future_release_date? future_release_date && (future_release_date > Date.current) end |
#has_kits? ⇒ Boolean
Returns whether the record has kits.
967 968 969 |
# File 'app/models/delivery.rb', line 967 def has_kits? line_items_with_counters.any? { |li| li.children_count.to_i.positive? } end |
#has_kits_or_serial_numbers? ⇒ Boolean
Returns whether the record has kits or serial numbers.
972 973 974 |
# File 'app/models/delivery.rb', line 972 def has_kits_or_serial_numbers? has_serial_numbers? || has_kits? end |
#has_not_changed_but_has_shipping_lines? ⇒ Boolean
Returns whether the record has not changed but has shipping lines.
2418 2419 2420 |
# File 'app/models/delivery.rb', line 2418 def has_not_changed_but_has_shipping_lines? relevant_changes.empty? && line_items.select(&:is_shipping?).any? end |
#has_ready_to_print_amazon_fba_items? ⇒ Boolean
Returns whether the record has ready to print amazon fba items.
4226 4227 4228 |
# File 'app/models/delivery.rb', line 4226 def has_ready_to_print_amazon_fba_items? line_items.goods.any? { |li| li.item.ready_to_print_amazon_fba_labels? } end |
#has_serial_numbers? ⇒ Boolean
Returns whether the record has serial numbers.
962 963 964 |
# File 'app/models/delivery.rb', line 962 def has_serial_numbers? line_items_with_counters.any? { |li| li.reserved_serial_numbers_count.positive? || li.serial_numbers_count.positive? } end |
#has_shippable_content? ⇒ Boolean
Check if this delivery has shippable content (non-shipping line items).
A delivery should not be shipped if it only contains shipping line items
and no actual goods or services. This prevents empty/shipping-only deliveries
from being invoiced, which creates €0.00 invoices.
Returns true if:
- The delivery has at least one non-shipping line item (goods or services), OR
- The delivery is a service-only delivery (handled via service_ready_to_fulfill), OR
- The delivery is an RMA return (special handling)
2972 2973 2974 2975 2976 |
# File 'app/models/delivery.rb', line 2972 def has_shippable_content? return true if is_rma_return? line_items.non_shipping.exists? end |
#has_shipping_line_linked_to_shipping_cost_that_doesnt_exist_in_delivery?(shipping_line) ⇒ Boolean
Returns whether the record has shipping line linked to shipping cost that doesnt exist in delivery.
2424 2425 2426 |
# File 'app/models/delivery.rb', line 2424 def has_shipping_line_linked_to_shipping_cost_that_doesnt_exist_in_delivery?(shipping_line) shipping_line && shipping_cost_ids.any? && shipping_cost_ids.index(shipping_line.shipping_cost_id).nil? end |
#has_unfulfilled_dropship_items? ⇒ Boolean
Returns whether the record has unfulfilled dropship items.
2534 2535 2536 |
# File 'app/models/delivery.rb', line 2534 def has_unfulfilled_dropship_items? line_items.any? { |li| li.dropship? && (li.purchase_order_item.nil? || !li.purchase_order_item.fully_receipted?) } end |
#has_unprocessed_dropship_items? ⇒ Boolean
Returns whether the record has unprocessed dropship items.
2549 2550 2551 |
# File 'app/models/delivery.rb', line 2549 def has_unprocessed_dropship_items? line_items.any? { |li| li.dropship? && (li.purchase_order_item.nil? || li.purchase_order_item&.cancelled?) } end |
#has_valid_shipments? ⇒ Boolean
Returns whether the record has valid shipments.
3573 3574 3575 |
# File 'app/models/delivery.rb', line 3573 def has_valid_shipments? shipments.packed_or_measured.present? && shipments.packed_or_measured.all?(&:has_dimensions?) end |
#incurs_oversized_penalty? ⇒ Boolean
Returns whether the record incurs oversized penalty.
4298 4299 4300 |
# File 'app/models/delivery.rb', line 4298 def incurs_oversized_penalty? (ships_ltl_freight? != true) && (is_warehouse_pickup? != true) && shipments.packed_or_measured.any?(&:incurs_oversized_penalty?) end |
#index ⇒ Integer
1646 1647 1648 |
# File 'app/models/delivery.rb', line 1646 def index resource&.deliveries&.active&.map(&:id)&.index(id) || 0 end |
#individual_auto_ship_confirm(logger: nil) ⇒ Object
Transitions a delivery to shipped state if ready.
Invoice queuing is handled automatically by ToShippedHandler in the
after_transition callback.
4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 |
# File 'app/models/delivery.rb', line 4003 def individual_auto_ship_confirm(logger: nil) logger ||= Rails.logger ErrorReporting.scoped({ delivery_id: id }) do ready = !(is_warehouse_pickup? || shipments.completed.empty?) return unless ready logger.info "#{Time.current}: Processing delivery id: #{id}, ref/name: #{name}" begin shipped! # NOTE: Do NOT queue DeliveryInvoicingWorker here! # The after_transition callback in the state machine already queues it # via ToShippedHandler.queue_invoicing_worker. Queueing it here causes # race conditions where two workers try to create the same invoice. rescue StandardError => e = "#{Time.current}: Exception!!! Processing delivery id: #{id}, ref/name: #{name}: #{e}" logger.error logger.error e end end end |
#instant_quote? ⇒ Boolean
True when the parent opportunity originated in the web quote-builder
("Instant Quote") flow. Drives the "no full shipping address yet,
just need a zip to estimate" UX exception — the
destination_address / has_destination_postal_code rules above bypass
the usual presence check for these deliveries.
2473 2474 2475 2476 |
# File 'app/models/delivery.rb', line 2473 def instant_quote? resource.respond_to?(:opportunity) && resource.opportunity && resource.opportunity.opportunity_reception_type == 'IQ' end |
#instantiate_shipping_insurance ⇒ Shipping::LtlShippingInsurance, Shipping::PackageShippingInsurance
Returns the appropriate Shipping::*ShippingInsurance strategy
object for the delivery's mode (LTL freight vs package).
4595 4596 4597 4598 4599 4600 4601 |
# File 'app/models/delivery.rb', line 4595 def instantiate_shipping_insurance if ships_ltl_freight? Shipping::LtlShippingInsurance.new else Shipping::PackageShippingInsurance.new end end |
#insured_shipments ⇒ ActiveRecord::Relation<Shipment>
Subset of label-complete Shipments that opted into third-party shipping
insurance — used by claim filing and billing reports.
985 986 987 |
# File 'app/models/delivery.rb', line 985 def insured_shipments shipments.label_complete.where(is_ship_insured: true) end |
#insured_value ⇒ BigDecimal?
Carrier-declared value the chosen rate was quoted with — null for
carriers we don't insure through (Wayfair, Home Depot EDI, etc).
2000 2001 2002 |
# File 'app/models/delivery.rb', line 2000 def insured_value chosen_shipping_method&.insured_value end |
#invoices ⇒ ActiveRecord::Relation<Invoice>
Returns the associated invoices.
150 |
# File 'app/models/delivery.rb', line 150 has_many :invoices |
#is_amazon_seller_central? ⇒ Object
Alias for Customer#is_amazon_seller_central?
222 |
# File 'app/models/delivery.rb', line 222 delegate :catalog, :is_amazon_seller_central?, to: :customer |
#is_amazon_seller_central_veeqo? ⇒ Boolean
Returns whether the record is amazon seller central veeqo.
2322 2323 2324 |
# File 'app/models/delivery.rb', line 2322 def is_amazon_seller_central_veeqo? is_amazon_seller_central? && override_shipping_method? end |
#is_cross_border? ⇒ Boolean
Returns whether the record is cross border.
3902 3903 3904 |
# File 'app/models/delivery.rb', line 3902 def is_cross_border? origin_address && destination_address && (origin_address.country_iso3 != destination_address.country_iso3) && %w[USA CAN].include?(origin_address.country_iso3) && %w[USA CAN].include?(destination_address.country_iso3) end |
#is_default_ltl_freight? ⇒ Boolean
Returns whether the record is default ltl freight.
2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 |
# File 'app/models/delivery.rb', line 2327 def is_default_ltl_freight? # country_iso3 = "USA" # address = self.shipping_address or self.customer.first_address # country_iso3 = address.country_iso3 if address if order&.is_store_transfer? && order.shipping_method&.index('freight') && order.shipping_method.index('ltl') true elsif subtotal_for_ltl_threshold > LTL_FREIGHT_DOLLAR_THRESHOLD true elsif ship_weight > LTL_FREIGHT_WEIGHT_THRESHOLD true elsif line_items.parents_only.any? { |li| li.item.ships_via_freight? } true elsif canadian_tire_special_check? true elsif customer&.billing_entity&.is_build_com? && order&.shipping_method&.index('freight') true else false end # for now we only have USA freight implemented but allow PurolatorFreight as an unsupported carrier end |
#is_domestic? ⇒ Boolean
Returns whether the record is domestic.
3892 3893 3894 |
# File 'app/models/delivery.rb', line 3892 def is_domestic? origin_address && destination_address && origin_address.country_iso3 == destination_address.country_iso3 end |
#is_international? ⇒ Boolean
Returns whether the record is international.
3897 3898 3899 |
# File 'app/models/delivery.rb', line 3897 def is_international? !is_domestic? end |
#is_international_and_ups_or_fedex? ⇒ Boolean
Returns whether the record is international and ups or fedex.
3907 3908 3909 |
# File 'app/models/delivery.rb', line 3907 def is_international_and_ups_or_fedex? is_international? && %w[UPS FedEx].include?(carrier) end |
#is_part_of_manifest? ⇒ Boolean
Returns whether the record is part of manifest.
3578 3579 3580 |
# File 'app/models/delivery.rb', line 3578 def is_part_of_manifest? shipments.any?(&:manifest) || shipments.any?(&:speedee_manifest_shipment) end |
#is_part_of_transmitted_manifest? ⇒ Boolean
Whether this delivery sits on a manifest that was actually handed to the
carrier and therefore can't be unwound.
Only the legacy Manifest qualifies. A SpeedeeManifest is never
transmitted — the CSV/FTP handoff is gone for good, and the sheet exists
solely so the driver's paper matches Spee-Dee's dashboard and the parcels on
the truck. Voiding after close is therefore safe, provided the shipment also
comes off the sheet. See SpeedeeManifest.drop_voided_shipments! and
SpeedeeManifest#cancel_manifest.
3593 3594 3595 |
# File 'app/models/delivery.rb', line 3593 def is_part_of_transmitted_manifest? shipments.any?(&:manifest) end |
#is_rma_return ⇒ Boolean Also known as: is_rma_return?
Whether this delivery is the return half of an Rma (precreated by
CRM agents to send return labels to customers).
2290 2291 2292 |
# File 'app/models/delivery.rb', line 2290 def is_rma_return rma_for_return.present? end |
#is_smart_service? ⇒ Boolean
Returns whether the record is smart service.
2307 2308 2309 |
# File 'app/models/delivery.rb', line 2307 def is_smart_service? line_items.non_shipping.all?(&:is_smart_service?) end |
#is_sww_shipping_cost?(sc = nil) ⇒ Boolean
Check if the shipping cost is a Ship with Walmart rate
2241 2242 2243 2244 2245 2246 |
# File 'app/models/delivery.rb', line 2241 def is_sww_shipping_cost?(sc = nil) sc ||= selected_shipping_cost return false unless sc&.rate_data.is_a?(Hash) sc.rate_data['sww_carrier_id'].present? || sc.rate_data[:sww_carrier_id].present? end |
#is_www? ⇒ Boolean
Returns whether the record is www.
4532 4533 4534 |
# File 'app/models/delivery.rb', line 4532 def is_www? resource&.try(:cart?) || resource&.try(:is_www) # unfortunately we do not have a rock-solid single source of truth method to determine what is a WWW vs CRM delivery end |
#item_ledger_entries ⇒ ActiveRecord::Relation<ItemLedgerEntry>
Returns the associated item ledger entries.
160 |
# File 'app/models/delivery.rb', line 160 has_many :item_ledger_entries |
#labeled_quantities_by_line_item ⇒ Hash{Integer => Numeric}
Quantity of each line item already covered by active label_complete
shipments, keyed by line_item_id.
1158 1159 1160 1161 1162 1163 1164 |
# File 'app/models/delivery.rb', line 1158 def labeled_quantities_by_line_item shipments.label_complete .reorder(nil) # drop Shipment's default created_at order — illegal under GROUP BY .joins(:shipment_contents) .group('shipment_contents.line_item_id') .sum('shipment_contents.quantity') end |
#ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
Returns the associated ledger transactions.
158 |
# File 'app/models/delivery.rb', line 158 has_many :ledger_transactions |
#line_allocation_status_hash ⇒ Hash{Integer => Integer}
1127 1128 1129 1130 1131 1132 1133 |
# File 'app/models/delivery.rb', line 1127 def line_allocation_status_hash line_items_eligible_for_packing.each_with_object({}) do |li, hsh| allocated = shipments_for_packing.joins(:shipment_contents).where(shipment_contents: { line_item_id: li.id }).sum(:quantity) remaining_to_allocate = li.quantity.abs - allocated hsh[li.id] = remaining_to_allocate end end |
#line_discounts ⇒ ActiveRecord::Relation<LineDiscount>
Returns the associated line discounts.
138 |
# File 'app/models/delivery.rb', line 138 has_many :line_discounts, through: :line_items |
#line_items ⇒ ActiveRecord::Relation<LineItem>
Returns the associated line items.
136 |
# File 'app/models/delivery.rb', line 136 has_many :line_items, extend: LineItemExtension, autosave: true, dependent: :nullify |
#line_items_eligible_for_packing ⇒ ActiveRecord::Relation<LineItem>
1049 1050 1051 |
# File 'app/models/delivery.rb', line 1049 def line_items_eligible_for_packing line_items.eager_load(:item).includes(parent: :item).order(Item[:sku]).active_lines_for_packaging end |
#line_items_requiring_serial_number ⇒ Array<LineItem>
3763 3764 3765 |
# File 'app/models/delivery.rb', line 3763 def line_items_requiring_serial_number line_items.reject(&:marked_for_destruction?).select(&:require_serial_number?) end |
#line_items_with_counters ⇒ ActiveRecord::Relation<LineItem>
LineItem relation eager-loaded with reserved/shipped serial-number
counts so the warehouse UI can render allocation badges without N+1
queries.
957 958 959 |
# File 'app/models/delivery.rb', line 957 def line_items_with_counters line_items.with_reserved_serial_numbers_count.with_serial_numbers_count end |
#lines_overallocated? ⇒ Boolean
def delta_volume_factor_remaining_to_allocate
(line_allocation_status_hash.sum{|k,v| LineItem.find(k).shipping_volume*v.abs}.to_f/ship_volume_from_shipments)
end
1118 1119 1120 |
# File 'app/models/delivery.rb', line 1118 def lines_overallocated? line_allocation_status_hash.values.any?(&:negative?) end |
#link_serial_numbers_to_line_items ⇒ void
This method returns an undefined value.
Records the shipped SerialNumbers on each LineItem, attaching
carrier scan data to the historical line.
4179 4180 4181 |
# File 'app/models/delivery.rb', line 4179 def link_serial_numbers_to_line_items line_items.each(&:link_serial_numbers) end |
#linked_return_deliveries ⇒ Array<Delivery>
3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 |
# File 'app/models/delivery.rb', line 3074 def linked_return_deliveries return_deliveries = [] rmas = [] rmas << order.rma if order&.is_rma_replacement? rmas << precreated_rma if precreated_rma.present? rmas.each do |rma| rma.credit_orders.each do |co| co.deliveries.each do |d| return_deliveries << d end end end return_deliveries end |
#locked_for_fba? ⇒ Boolean
Returns whether the record locked for fba.
2392 2393 2394 |
# File 'app/models/delivery.rb', line 2392 def locked_for_fba? order&.is_fba? && order&.shipment_reference_number =~ CustomerConstants::AMAZON_FBA_ID_REGEX && shipments.any? && shipments.all?(&:locked_for_fba?) end |
#ltl_freight_has_changed? ⇒ Boolean
Returns whether the record ltl freight has changed.
2438 2439 2440 2441 |
# File 'app/models/delivery.rb', line 2438 def ltl_freight_has_changed? rc = relevant_changes rc.keys.include?('ltl_freight') && (rc['ltl_freight'] == [nil, true] || rc['ltl_freight'] == [false, true] || rc['ltl_freight'] == [true, false] || rc['ltl_freight'] == [true, nil]) end |
#manual_pickup_contact ⇒ Object
Manual pickup contact.
2377 2378 2379 |
# File 'app/models/delivery.rb', line 2377 def manual_pickup_contact effective_shipping_option&.manual_pickup_contact end |
#mark_multi_shipments_manifested(manifest, shipment) ⇒ Integer
Tags every other label-complete Shipment on this delivery with
the given manifest id so they ride along with the carrier manifest.
4213 4214 4215 |
# File 'app/models/delivery.rb', line 4213 def mark_multi_shipments_manifested(manifest, shipment) shipments.label_complete.where.not('shipments.id' => shipment.id).update_all(manifest_id: manifest.id) end |
#matches?(existing_item, item, quantity) ⇒ Boolean
Whether an unlinked existing PurchaseOrderItem can be reattached to a new
dropship line item instead of building a fresh one. A cancelled PO item
is never reusable: LineItem#cancel_linked_po cancels + unlinks it, so it
lands here unlinked, and reattaching it (leaving it cancelled) would strand
the line with a dead PO item and transmit nothing to the supplier — the
SO728077 dropship failure (defect E). Skip it so a live PO item is built.
2678 2679 2680 2681 2682 2683 |
# File 'app/models/delivery.rb', line 2678 def matches?(existing_item, item, quantity) !existing_item.cancelled? && existing_item.line_item.nil? && existing_item.item == item && existing_item.unit_quantity == quantity end |
#md5_hash_items_from_packable ⇒ String
MD5 signature of the packed items, annotated when the shipping-relevant
subset diverges from the full set.
3629 3630 3631 3632 3633 3634 3635 3636 |
# File 'app/models/delivery.rb', line 3629 def md5_hash_items_from_packable md5_res = Shipping::Md5HashItem.process(packable_item_hash) if md5_res.md5 == md5_res.relevant_md5 md5_res.md5 else "#{md5_res.md5} (relevant items md5: #{md5_res.relevant_md5})" end end |
#messaging_logs ⇒ ActiveRecord::Relation<MessagingLog>
Returns the associated messaging logs.
140 |
# File 'app/models/delivery.rb', line 140 has_many :messaging_logs, dependent: :destroy, as: :resource |
#name(short = false, filename = false) ⇒ String
Human-friendly delivery name (e.g. "SO Order #SO12345 Delivery 2")
for UI titles and filenames. The two flags strip the prefix/parent and
turn the result into a filesystem-safe slug.
1657 1658 1659 1660 1661 1662 1663 1664 1665 |
# File 'app/models/delivery.rb', line 1657 def name(short = false, filename = false) ot = +'' ot = order.order_type.to_s.upcase if order && [Order::SALES_ORDER, Order::MARKETING_ORDER, Order::TECH_ORDER].exclude?(ot) root_name = "#{ot} " root_name = "#{ot} #{resource_or_rma_for_delivery.class.name} ##{resource_or_rma_for_delivery.reference_number} " unless short final_name = "#{root_name}Delivery #{index.to_i + 1}" final_name = final_name.tr(' ', '_').gsub(/[^0-9a-z_]/i, '').underscore if filename final_name end |
#new_shipment_attributes=(shipment_attributes) ⇒ void
This method returns an undefined value.
Nested-attributes setter that builds new Shipments from a list of
hashes (used by the manual-shipment form on the warehouse UI).
3046 3047 3048 3049 3050 |
# File 'app/models/delivery.rb', line 3046 def new_shipment_attributes=(shipment_attributes) shipment_attributes.each do |attributes| shipments.build(attributes) end end |
#no_empty_shipments ⇒ void
This method returns an undefined value.
State-validation helper: rejects pending-label deliveries that have
any packed-or-awaiting-labels Shipment with no contents and no
child shipments.
4245 4246 4247 4248 4249 |
# File 'app/models/delivery.rb', line 4245 def no_empty_shipments return unless shipments.packed_or_awaiting_labels.any? { |shp| shp.shipment_contents.empty? && shp.child_shipments.empty? } errors.add(:base, 'Empty containers are present') end |
#notify_store ⇒ void
This method returns an undefined value.
Publishes Events::DeliveryArrivedAtWarehouse from
after_all_transactions_commit so the async
DeliveryArrivedAtWarehouseNotificationHandler re-queries the delivery by
id and emails the store's operations contacts. The async re-query
tolerates a destroy between the at_warehouse transition and the email
send (e.g. an order cancelled within the original 1-minute mailer delay
cascading dependent: :destroy on its deliveries) — replacing
ActiveJob::DeserializationError (AppSignal #4958) with a clean no-op.
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 |
# File 'app/models/delivery.rb', line 1032 def notify_store delivery_id = id ActiveRecord.after_all_transactions_commit do Rails.configuration.event_store.publish( Events::DeliveryArrivedAtWarehouse.new(data: { delivery_id: }), stream_name: "Delivery-#{delivery_id}" ) rescue StandardError => e ErrorReporting.error(e) end end |
#open_activities_counter ⇒ Integer
Count of unresolved activities across delivery, parent order, and parent
quote — drives the bell-icon badge on delivery summary screens.
926 927 928 |
# File 'app/models/delivery.rb', line 926 def open_activities_counter all_activities.open_activities.count end |
#order ⇒ Order?
Returns the order this record belongs to.
107 |
# File 'app/models/delivery.rb', line 107 belongs_to :order, inverse_of: :deliveries, optional: true |
#origin_address ⇒ Address?
Returns the origin address this record belongs to.
Validations:
117 |
# File 'app/models/delivery.rb', line 117 belongs_to :origin_address, class_name: 'Address', validate: true, optional: true |
#override_shipping_method? ⇒ Boolean
Returns whether the record override shipping method.
2175 2176 2177 |
# File 'app/models/delivery.rb', line 2175 def override_shipping_method? chosen_shipping_method&.is_override? end |
#packable_active_lines ⇒ ActiveRecord::Relation<LineItem>
All non-destroyed LineItems eligible for packaging, eager-loaded
for the packing UI. Includes kit children.
3601 3602 3603 |
# File 'app/models/delivery.rb', line 3601 def packable_active_lines line_items.includes(:item, :catalog_item, parent: :catalog_item).active_lines_for_packaging end |
#packable_active_parent_lines_only(skip_spare_parts = false) ⇒ Array<LineItem>
Only the goods-bearing parent LineItems eligible for packaging
(used when callers want to count packageable units without
double-counting kit components).
3611 3612 3613 3614 3615 |
# File 'app/models/delivery.rb', line 3611 def packable_active_parent_lines_only(skip_spare_parts=false) lines = line_items.active_parent_lines.select(&:is_goods?) lines = lines.reject(&:is_spare_parts?) if skip_spare_parts lines end |
#packable_item_hash ⇒ Hash{Item => Integer}
Aggregated {item => quantity} hash for all packable lines,
collapsing duplicates (same Item on multiple lines).
3621 3622 3623 |
# File 'app/models/delivery.rb', line 3621 def packable_item_hash packable_active_lines.each_with_object({}) { |li, hsh| hsh[li.item] = (hsh[li.item].nil? ? li.quantity : hsh[li.item] + li.quantity) } end |
#packable_parent_lines_item_hash(skip_spare_parts = false) ⇒ Hash{Item => Integer}
Parent-only variant of #packable_item_hash — does not include kit
component items.
3643 3644 3645 |
# File 'app/models/delivery.rb', line 3643 def packable_parent_lines_item_hash(skip_spare_parts=false) packable_active_parent_lines_only(skip_spare_parts).each_with_object({}) { |li, hsh| hsh[li.item] = (hsh[li.item].nil? ? li.quantity : hsh[li.item] + li.quantity) } end |
#packageable? ⇒ Boolean
Are there shipments that can have content specified?
2282 2283 2284 |
# File 'app/models/delivery.rb', line 2282 def packageable? shipments.packageable.present? end |
#pallet_weight_matching ⇒ Boolean
State-validation helper that surfaces pallet/carton weight
mismatches detected by #all_shipments_weights_match_expected.
4255 4256 4257 4258 4259 |
# File 'app/models/delivery.rb', line 4255 def pallet_weight_matching res = all_shipments_weights_match_expected errors.add(:base, "Weights for shipments don't add up to what is expected. #{res[:error_message]}") if res[:status] != true res[:status] end |
#paperless_return? ⇒ Boolean
True when this delivery is an RMA return AND the chosen shipping option
is on the known-paperless-supporting list (ShippingOption.paperless_eligible).
Drives Shipping::ShipengineBase#get_label_options_hash to set
display_scheme: 'label_and_paperless' so the customer's emailed
PDF carries both a printable label and a QR/barcode. Only USPS
is seeded eligible; other carriers stay false until we have direct
evidence they support paperless.
2302 2303 2304 |
# File 'app/models/delivery.rb', line 2302 def paperless_return? is_rma_return? && rma_for_return&.shipping_option&.paperless_eligible? end |
#pick_slip_file_name(with_extension = true) ⇒ String
Filename for the warehouse pick slip PDF, dated to the current minute
so concurrent regenerations don't clobber each other in tmp/.
1241 1242 1243 1244 1245 1246 1247 1248 |
# File 'app/models/delivery.rb', line 1241 def pick_slip_file_name(with_extension = true) s = [] s << name(false, true) s << '_generated_pick_slip' s << Time.current.strftime('%m_%d_%Y_%I_%M%p') s << '.pdf' if with_extension s.join end |
#pick_slip_line_items(split_kits: false, sort_method: :location) ⇒ Object
Generates a line hash for the pick/pack slip pdf
1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 |
# File 'app/models/delivery.rb', line 1253 def pick_slip_line_items(split_kits: false, sort_method: :location) # Start with our items lines = line_items.non_shipping.parents_only.joins(:item) .includes(:reserved_serial_numbers) .includes(:direct_store_item, catalog_item: { store_items: :storage_locations }) .includes(item: :supplier_items) .includes(:children) # Start grouping lines_hash = lines.each_with_object({}) do |li, hsh| li_hsh = hsh[li.sku] || {} li_hsh[:quantity] ||= 0 li_hsh[:quantity] += li.quantity li_hsh[:name] ||= li.name li_hsh[:is_kit] = li.is_kit? if li_hsh[:is_kit].nil? li_hsh[:supplier_skus] ||= li.item.supplier_items.select(&:active).map { |si| si.supplier_sku.presence }.compact # Only need to add locations once since they're the same at the item level li_hsh[:locations] ||= li.store_item.storage_locations.map(&:reference_number) li_hsh[:oj_wifi_notes] = true if li.item.requires_distributor_id_code? li_hsh[:serial_numbers] ||= [] li_hsh[:serial_numbers] += li.reserved_serial_numbers.map { |rsn| { serial_number: rsn.serial_number.number, quantity: rsn.qty } } li_hsh[:quantity_on_hand] ||= li.store_item.qty_on_hand li_hsh[:quantity_available] ||= li.store_item.qty_available li_hsh[:third_party_part_number] ||= li.catalog_item&.third_party_part_number li_hsh[:packed_shipments] ||= {} packed_shipment_contents = li.shipment_contents.joins(:shipment).where(shipments: { state: 'packed' }) packed_shipment_contents.each do |sc| li_hsh[:packed_shipments][sc.shipment_id] ||= 0 li_hsh[:packed_shipments][sc.shipment_id] += sc.quantity end # If we don't split kits but some components have locations or serial numbers, we will # aggregate them at the parent level if !split_kits && li.children.present? li_hsh[:locations] |= li.children.flat_map { |lic| lic.store_item.storage_locations.map(&:reference_number) } li_hsh[:serial_numbers] += li.children.flat_map { |lic| lic.reserved_serial_numbers.map { |rsn| { serial_number: rsn.serial_number.number, quantity: rsn.qty } } } end if (split_kits && li.children.present?) || li.children.any? { |lc| lc.reserved_serial_numbers.present? } li_hsh[:children] ||= {} li.children.each do |lic| lic_hsh = li_hsh[:children][lic.sku] || {} lic_hsh[:quantity] ||= 0 lic_hsh[:quantity] += lic.quantity lic_hsh[:supplier_skus] = lic.item.supplier_items.select(&:active).map { |si| si.supplier_sku.presence }.compact lic_hsh[:name] = lic.name lic_hsh[:locations] ||= lic.store_item.storage_locations.map(&:reference_number) lic_hsh[:serial_numbers] ||= [] lic_hsh[:serial_numbers] += lic.reserved_serial_numbers.map { |rsn| { serial_number: rsn.serial_number.number, quantity: rsn.qty } } lic_hsh[:quantity_on_hand] ||= lic.store_item.qty_on_hand lic_hsh[:quantity_available] ||= lic.store_item.qty_available lic_hsh[:packed_shipments] ||= {} packed_shipment_contents = lic.shipment_contents.joins(:shipment).where(shipments: { state: 'packed' }) packed_shipment_contents.each do |sc| lic_hsh[:packed_shipments][sc.shipment_id] ||= 0 lic_hsh[:packed_shipments][sc.shipment_id] += sc.quantity end li_hsh[:oj_wifi_notes] = true if lic.item.requires_distributor_id_code? li_hsh[:children][lic.sku] = lic_hsh end end hsh[li.sku] = li_hsh end # Resort the hash by location so that items are picked in location order case sort_method when :location Hash[lines_hash.sort_by { |sku, line_props| line_props[:locations]&.first || sku }] when :quantity_desc Hash[lines_hash.sort_by { |_sku, line_props| -line_props[:quantity] }] else Hash[lines_hash.sort_by { |sku, _line_props| sku }] end end |
#pickup_alert_visible? ⇒ Boolean
True when a confirmed pickup is on the books and the delivery hasn't yet
shipped/invoiced/cancelled — used by the order and delivery screens to
surface a persistent banner showing the pickup window.
3333 3334 3335 |
# File 'app/models/delivery.rb', line 3333 def pickup_alert_visible? confirmed_pickup_date.present? && %w[shipped invoiced cancelled].exclude?(state) end |
#po_number ⇒ Object
Alias for Order#po_number
224 |
# File 'app/models/delivery.rb', line 224 delegate :po_number, to: :order, allow_nil: true |
#precreated_rma ⇒ Rma?
Returns the associated precreated rma.
130 |
# File 'app/models/delivery.rb', line 130 has_one :precreated_rma, class_name: 'Rma', foreign_key: 'precreate_from_delivery_id', dependent: :nullify |
#preferred_shipping_option ⇒ Object
These methods below are from the model previously known as delivery_quote
1669 1670 1671 1672 1673 1674 1675 1676 |
# File 'app/models/delivery.rb', line 1669 def preferred_shipping_option # choose based on resource's shipping_method first preferred_shipping_option = nil preferred_shipping_option = ShippingOption.active.for_country_iso(resource.country&.iso).where(name: resource_shipping_method).first || ShippingOption.for_country_iso(resource.country&.iso).where(name: resource_shipping_method).first if resource && resource_shipping_method.present? # # if not use customer's preferred_shipping_method preferred_shipping_option ||= ShippingOption.active.where(name: customer.preferred_shipping_method).first || ShippingOption.where(name: customer.preferred_shipping_method).first preferred_shipping_option end |
#prepack_requester ⇒ Party?
Returns the prepack requester this record belongs to.
175 |
# File 'app/models/delivery.rb', line 175 belongs_to :prepack_requester, class_name: 'Party', optional: true |
#preset_jobs ⇒ ActiveRecord::Relation<PresetJob>
Returns the associated preset jobs.
152 |
# File 'app/models/delivery.rb', line 152 has_many :preset_jobs, inverse_of: :order |
#primary_party ⇒ Object
Alias for Resource_or_rma_for_delivery#primary_party
220 |
# File 'app/models/delivery.rb', line 220 delegate :customer, :primary_party, to: :resource_or_rma_for_delivery |
#print_container_label? ⇒ Boolean
Returns whether the record print container label.
813 814 815 816 817 818 819 820 821 822 823 824 825 826 |
# File 'app/models/delivery.rb', line 813 def print_container_label? cr = customer.customer_record # If specified that we always print (e.g. Amazon) then true if has_custom_shipping_labels? false elsif cr&.always_container_label? true # If never, or we have a custom pack list, or we have a homeowner, then false elsif cr&.never_container_label? || custom_pack_list || customer.is_homeowner? false else # Trade, Dealers, etc. true by default true end end |
#publish_delivery_label_complete_event ⇒ void
This method returns an undefined value.
Publishes Events::DeliveryLabelComplete so
Shipping::DeliveryLabelCompleteHandler can re-run the Packing-write
asynchronously once labels are purchased and shipment_contents exist.
Captures per-box packdim_contents that weren't yet known at
picking → pending_ship_labels time (notably Amazon Buy Shipping).
2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 |
# File 'app/models/delivery.rb', line 2787 def publish_delivery_label_complete_event delivery_id = id ActiveRecord.after_all_transactions_commit do Rails.configuration.event_store.publish( Events::DeliveryLabelComplete.new(data: { delivery_id: }), stream_name: "Delivery-#{delivery_id}" ) rescue StandardError => e ErrorReporting.error(e) end end |
#publish_delivery_ready_for_labeling_event ⇒ void
This method returns an undefined value.
Publishes Events::DeliveryReadyForLabeling so
Shipping::DeliveryReadyForLabelingHandler can refresh the
from_delivery Packing record asynchronously. Replaces the synchronous
set_packaged_items_md5_hash call in the
picking → pending_ship_labels after_transition block — the
DeliveryMd5Extractor write is bookkeeping, not transactional state,
so deferring it speeds up the warehouse UI's "ready to label" click.
2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 |
# File 'app/models/delivery.rb', line 2768 def publish_delivery_ready_for_labeling_event delivery_id = id ActiveRecord.after_all_transactions_commit do Rails.configuration.event_store.publish( Events::DeliveryReadyForLabeling.new(data: { delivery_id: }), stream_name: "Delivery-#{delivery_id}" ) rescue StandardError => e ErrorReporting.error(e) end end |
#publish_order_acknowledged_event ⇒ void
This method returns an undefined value.
Publishes Events::OrderAcknowledged, which drives the EDI ship
confirmation (Edi::OrderEventProcessor → the partner's
confirm_message_processor.acknowledge_order). Guarded to EDI orders that
expect a ship-confirm push: no-op for non-EDI, early-label, and Amazon Buy
Shipping deliveries (Buy Shipping's purchaseShipment already confirms).
Callers gate on all_completed_shipments_reported_tracking? so the confirm
never carries a blank trackingNumber: fired at pending_ship_confirm when
tracking is already present (parcel, synchronously-assigned LTL PROs) and at
invoiced as a catch-up for LTL PROs assigned asynchronously.
2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 |
# File 'app/models/delivery.rb', line 2811 def publish_order_acknowledged_event return unless order&.edi_transaction_id.present? && !order.has_early_purchased_label? && carrier != 'AmazonSeller' order_id = order.id ActiveRecord.after_all_transactions_commit do Rails.configuration.event_store.publish( Events::OrderAcknowledged.new(data: { order_id: }), stream_name: "Order-#{order_id}" ) end end |
#quantities_remaining_to_allocate ⇒ Integer
Total unit count still needing to be put into a Shipment across all
packable line items — drives the packing progress indicator.
1101 1102 1103 |
# File 'app/models/delivery.rb', line 1101 def quantities_remaining_to_allocate line_allocation_status_hash.values.sum end |
#quote ⇒ Quote?
Returns the quote this record belongs to.
105 |
# File 'app/models/delivery.rb', line 105 belongs_to :quote, inverse_of: :deliveries, optional: true |
#ready_to_choose_ships_economy_carrier? ⇒ Boolean
Returns whether the record ready to choose ships economy carrier.
4552 4553 4554 |
# File 'app/models/delivery.rb', line 4552 def ready_to_choose_ships_economy_carrier? ships_economy_package? && pending_ship_labels? end |
#ready_to_print_amazon_fba_line_items ⇒ Array<LineItem>
4221 4222 4223 |
# File 'app/models/delivery.rb', line 4221 def ready_to_print_amazon_fba_line_items line_items.goods.select { |li| li.item.ready_to_print_amazon_fba_labels? } end |
#ready_to_ship! ⇒ void
This method returns an undefined value.
Promotes a quoting delivery into the warehouse pipeline: routes to
awaiting_po_fulfillment when dropship items are present, otherwise to
at_warehouse. Wrapped in an advisory lock to prevent concurrent
transitions on the same delivery from racing.
1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 |
# File 'app/models/delivery.rb', line 1623 def ready_to_ship! return unless quoting? # Use advisory lock to prevent deadlocks from concurrent state transitions lock_key = "delivery|#{id}|ready_to_ship" self.class.with_advisory_lock(lock_key, timeout_seconds: 10) do reload # Refresh state after acquiring lock return unless quoting? # Re-check state after reload dropship = has_unprocessed_dropship_items? dropship = false if is_warehouse_pickup? || order.single_origin if dropship awaiting_po_fulfillment! else at_warehouse! end end end |
#reference_number ⇒ String Also known as: to_s
Stable customer/warehouse-facing identifier (e.g. DE12345) used on
labels, packing slips, and Slack notifications.
1170 1171 1172 |
# File 'app/models/delivery.rb', line 1170 def reference_number "DE#{id}" end |
#reference_number_for_label ⇒ String
Short identifier embedded in the carrier's "reference number" label
field — the parent Order's reference, the RMA number for returns,
or the delivery reference as a fallback.
3850 3851 3852 3853 3854 3855 3856 |
# File 'app/models/delivery.rb', line 3850 def reference_number_for_label if order.present? "ORD: #{order.reference_number}" else rma_for_return.present? ? "RMA: #{rma_for_return.rma_number}" : reference_number end end |
#rejoin_serial_numbers ⇒ void
This method returns an undefined value.
Re-merges previously split serial-number LineItems back into a
single multi-quantity row, used when cancelling a delivery so we
don't leave one-unit rows behind.
4171 4172 4173 |
# File 'app/models/delivery.rb', line 4171 def rejoin_serial_numbers line_items.select(&:require_reservation?).each(&:rejoin_serial_numbers) end |
#relevant_changes ⇒ HashWithIndifferentAccess
Subset of changes that materially affect rate shopping or carrier
selection — excludes audit-only / display-only fields like packaging
text, master tracking, BOL, and release-date metadata.
2433 2434 2435 |
# File 'app/models/delivery.rb', line 2433 def relevant_changes changes.except('suggested_packaging_text', 'master_tracking_number', 'ltl_pro_number', 'actual_shipping_cost', 'future_release_date', 'manual_release_only', 'do_not_reserve_stock', 'shipment_instructions', 'carrier_bol') end |
#relink_payments ⇒ Integer
after_save hook that points all Payment rows from the
payment_ids accessor at this delivery — used when payments are
captured during a delivery edit before the delivery is persisted.
3964 3965 3966 |
# File 'app/models/delivery.rb', line 3964 def relink_payments Payment.where(id: payment_ids).update_all(delivery_id: id) end |
#remap_legacy_shipping_options_if_any ⇒ void
This method returns an undefined value.
Migrates any LEGACY_-prefixed ShippingOption service codes on this
delivery and its ShippingCost rows to the current code, persisting
the change. One-shot data migration helper called on touch.
4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 |
# File 'app/models/delivery.rb', line 4143 def shipping_costs.select { |sc| sc.shipping_option&.service_code.to_s.starts_with?('LEGACY_') }.each do |sc| updated_serv_code = sc.shipping_option.service_code.split('LEGACY_').last sc.update({ shipping_option_id: ShippingOption.active.where(service_code: updated_serv_code, country: sc.shipping_option.country).first&.id || ShippingOption.where(service_code: updated_serv_code, country: sc.shipping_option.country).first&.id }) end return unless (serv_code = shipping_option&.service_code).to_s.starts_with?('LEGACY_') updated_serv_code = serv_code.split('LEGACY_').last self.shipping_option_id = (ShippingOption.active.where(service_code: updated_serv_code, country: shipping_option.country).first&.id || ShippingOption.where(service_code: updated_serv_code, country: shipping_option.country).first&.id) save end |
#reported_carrier ⇒ String?
Carrier name to surface to customers and EDI partners — prefers the
value set on the delivery (carrier), then falls back to the first
completed shipment's carrier or the override description for override
selections (where the warehouse manually chose a carrier).
2185 2186 2187 |
# File 'app/models/delivery.rb', line 2185 def reported_carrier (carrier || (override_shipping_method? && (shipments&.completed&.top_level&.first&.carrier || chosen_shipping_method&.description_override))).presence end |
#reported_master_tracking_number ⇒ String?
Tracking-style identifier reported externally — falls through master
tracking number, LTL PRO number, carrier BOL, and finally the first
completed shipment's tracking number (for override selections).
2207 2208 2209 |
# File 'app/models/delivery.rb', line 2207 def reported_master_tracking_number (master_tracking_number || (ltl_pro_number || carrier_bol || (override_shipping_method? && shipments&.completed&.top_level&.first&.tracking_number))).presence end |
#requires_232_declaration? ⇒ Boolean
Whether this delivery needs a Section 232 steel/aluminum/copper
declaration: US customs requires it for goods entering the USA, so only
CAN→USA cross-border deliveries qualify.
3372 3373 3374 |
# File 'app/models/delivery.rb', line 3372 def requires_232_declaration? is_cross_border? && destination_address&.country_iso3 == 'USA' end |
#requires_manifest_completion? ⇒ Boolean
Returns whether the record requires manifest completion.
4394 4395 4396 |
# File 'app/models/delivery.rb', line 4394 def requires_manifest_completion? CARRIERS_REQUIRING_MANIFEST_COMPLETION.include?(carrier) end |
#requires_manual_pickup? ⇒ Boolean
Returns whether the record requires manual pickup.
2372 2373 2374 |
# File 'app/models/delivery.rb', line 2372 def requires_manual_pickup? effective_shipping_option&.requires_manual_pickup? || false end |
#reserved_serial_numbers ⇒ ActiveRecord::Relation<ReservedSerialNumber>
Returns the associated reserved serial numbers.
154 |
# File 'app/models/delivery.rb', line 154 has_many :reserved_serial_numbers, through: :line_items |
#reset_early_label_flag_on_order ⇒ Object
Reset purchase_label_early flag on the order so next ship-label goes through normal flow
3535 3536 3537 3538 3539 3540 3541 |
# File 'app/models/delivery.rb', line 3535 def reset_early_label_flag_on_order return unless resource.is_a?(Order) return unless resource.purchase_label_early? Rails.logger.info("[Delivery] Resetting purchase_label_early flag on order #{resource.reference_number}") resource.update!(purchase_label_early: false) end |
#reset_shipping_cost ⇒ void
This method returns an undefined value.
Clears all ShippingCost rows safely (nullifying line-item references
first to avoid FK violations) and zeros out the cached shipping_cost
on the delivery.
2038 2039 2040 2041 |
# File 'app/models/delivery.rb', line 2038 def reset_shipping_cost clear_shipping_costs_safely self.shipping_cost = 0 if respond_to? :shipping_cost # REFACTOR end |
#reset_ships_economy_if_unselected ⇒ Boolean
after_save callback that clears the order's ships_economy flag
when the warehouse swaps in a real shipping method that isn't a
ground/economy match — so future deliveries don't keep treating the
order as economy.
4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 |
# File 'app/models/delivery.rb', line 4475 def reset_ships_economy_if_unselected Rails.logger.debug do "reset_ships_economy_if_unselected, ships_economy?: #{ships_economy?}, saved_change_to_selected_shipping_cost_id: #{saved_change_to_selected_shipping_cost_id}, selected_shipping_cost.present?: #{selected_shipping_cost.present?}" end # this is designed to reset the whole ships economy flag when a ships economy order's selected shipping method is changed to use another shipping method, and not HW or the warehouse choosing an equivalent economy shipping/ground return unless ships_economy? return unless saved_change_to_selected_shipping_cost_id? && selected_shipping_cost.present? reset_ships_economy = true # don't touch if ships_economy (not LTL) AND pending labels and HW or warehouse is choosing an equivalent economy shipping/ground method OR economy shipping override is set Rails.logger.debug { "reset_ships_economy_if_unselected, ships_economy_package?: #{ships_economy_package?}" } Rails.logger.debug { "reset_ships_economy_if_unselected, ready_to_choose_ships_economy_carrier?: #{ready_to_choose_ships_economy_carrier?}" } Rails.logger.debug { "reset_ships_economy_if_unselected, selected_shipping_cost&.name: #{selected_shipping_cost&.name}" } Rails.logger.debug do "reset_ships_economy_if_unselected, sorted_ground_shipping_costs(skip_override=true).map{|sc| sc.name}.include?(selected_shipping_cost&.name): #{sorted_ground_shipping_costs(true).map(&:name).include?(selected_shipping_cost&.name)}" end Rails.logger.debug { "reset_ships_economy_if_unselected, selected_shipping_cost&.is_override?: #{selected_shipping_cost&.is_override?}" } if ships_economy_package? && ((ready_to_choose_ships_economy_carrier? && sorted_ground_shipping_costs(true).map(&:name).include?(selected_shipping_cost&.name)) || selected_shipping_cost&.is_override? || rma_for_return.present?) # don't touch if ships_economy (not LTL) and we have the economy shipping override set reset_ships_economy = false end Rails.logger.debug { "reset_ships_economy_if_unselected, reset_ships_economy: #{reset_ships_economy}" } if reset_ships_economy order.ships_economy = false order.save end true end |
#resource ⇒ Order, ...
Parent record this delivery belongs to: either the Order (sales/store-transfer
workflow) or the Quote (pre-sale rate-shopping workflow). RMA returns use
#resource_or_rma_for_delivery since they are not tied to an order/quote.
877 878 879 |
# File 'app/models/delivery.rb', line 877 def resource order || quote end |
#resource_or_rma_for_delivery ⇒ Order, ...
886 887 888 |
# File 'app/models/delivery.rb', line 886 def resource_or_rma_for_delivery resource || rma_for_return end |
#resource_present ⇒ Boolean
Validation helper enforcing that every delivery has a parent Order or
Quote (RMA returns are exempt because they fall back via
#resource_or_rma_for_delivery).
935 936 937 |
# File 'app/models/delivery.rb', line 935 def resource_present resource.present? end |
#resource_shipping_method ⇒ String?
Shipping method preference inherited from the parent Order or
Quote; RMA returns hard-code to "ground". Drives
#preferred_shipping_option lookup.
4608 4609 4610 4611 4612 4613 4614 4615 |
# File 'app/models/delivery.rb', line 4608 def resource_shipping_method if resource.present? resource.shipping_method elsif is_rma_return? && rma_for_return.present? # For RMA returns, always use 'ground' as the shipping method 'ground' end end |
#retrieve_friendly_shipping_method(show_customer_pays_info = false, for_www = false, sc = nil, with_delivery_commitment = false, for_edi = false) ⇒ Object
Retrieve friendly shipping method.
2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 |
# File 'app/models/delivery.rb', line 2126 def retrieve_friendly_shipping_method(show_customer_pays_info = false, for_www = false, sc = nil, with_delivery_commitment = false, for_edi = false) # rescue "n/a" shipping_method_name = +'' mps = +'' method_cod = +'' customer_pays = +'' ret = +'' notes = +'' if is_service_only? shipping_method_name = 'Service' else sc ||= chosen_shipping_method if sc shipping_method_name = simple_shipping_description_for_shipping_cost(sc) shipping_method_name += " (#{sc.delivery_commitment})" if with_delivery_commitment num_shipments = (begin shipments.label_complete.length rescue StandardError 0 end) mps = ", #{num_shipments} containers" if num_shipments > 1 method_cod = ' (inc. COD charge)' if sc.cod # Surface the customer's account number whenever a SAN is attached # (= we bill that account third-party). Same predicate as the # label and the invoice, so the displayed account always matches # what is actually billed. if sc.third_party_billed? && show_customer_pays_info customer_pays = " (cust. acct.: #{sc.shipping_account_number.account_number})" unless for_www customer_pays = " (using your linked #{carrier} account: #{sc.shipping_account_number.account_number})" if for_www end if for_edi == false && sc&.shipping_option&.carrier == 'FedEx' && sc&.insured_value.to_f >= 500.0 && !signature_confirmation # flag this unless we already have signature_confirmation set notes += ' (FedEx automatically requires Direct Signature for all declared value shipments of $500 or more)' end elsif shipping_line_item shipping_method_name = shipping_line_item.name end end "#{shipping_method_name}#{mps}#{method_cod}#{customer_pays}#{ret}#{notes}".strip end |
#retrieve_shipping_costs(rate_ship_date: nil) ⇒ Hash
Retrieve shipping costs from carriers and persist them as ShippingCost
rows. Extracted to RetrieveShippingCosts (god-object
decomposition) — this stays as the public API for the ~35 callers.
1694 1695 1696 |
# File 'app/models/delivery.rb', line 1694 def retrieve_shipping_costs(rate_ship_date: nil) Delivery::RetrieveShippingCosts.new(self, rate_ship_date: rate_ship_date).process end |
#retrieve_shipping_description_for_line_item(shipping_line) ⇒ String?
Convenience for invoice rendering: pulls the shipping-cost description
from a shipping LineItem.
2253 2254 2255 |
# File 'app/models/delivery.rb', line 2253 def retrieve_shipping_description_for_line_item(shipping_line) retrieve_shipping_description_for_shipping_cost(shipping_line&.shipping_cost) end |
#retrieve_shipping_description_for_shipping_cost(sc = nil) ⇒ String?
Decorated description for a ShippingCost including COD note,
third-party billing account hint, and the FedEx ≥ $500 signature
warning. Override rows return their plain description.
2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 |
# File 'app/models/delivery.rb', line 2263 def retrieve_shipping_description_for_shipping_cost(sc = nil) sc ||= selected_shipping_cost description = simple_shipping_description_for_shipping_cost(sc) # nil description means line item name will default to linked item shipping option name, otherwise it's an override unless sc&.is_override? # only override if there is a COD, customer shipping account, or special services - shipping cost description includes special services descriptions too, - shipping option name does not sc&.description notes = +'' notes << ' (inc. COD charge)' if sc&.cod notes << " (cust. acct.: #{sc&.shipping_account_number&.account_number})" if sc&.third_party_billed? if sc&.shipping_option&.carrier == 'FedEx' && sc&.insured_value.to_f >= 500.0 && !signature_confirmation # flag this unless we already have signature_confirmation set notes << ' (FedEx automatically requires Direct Signature for all declared value shipments of $500 or more)' end description = "#{description}#{notes}" end description end |
#revert_to_override_economy_shipping_method(autosave = true) ⇒ void
This method returns an undefined value.
Resets a "ships economy" delivery back to the override shipping option
at the economy fallback cost — used when the previously selected real
carrier becomes invalid (e.g. order returns to quoting after items
change).
1852 1853 1854 1855 1856 1857 1858 1859 |
# File 'app/models/delivery.rb', line 1852 def revert_to_override_economy_shipping_method(autosave = true) so = ShippingOption.where(name: 'override', country: resource.store.country.iso).first self.shipping_option_id = so.id self.selected_shipping_cost = shipping_costs.detect { |sc| sc.shipping_option_id == shipping_option_id } || shipping_costs.new(shipping_option: so, cost: get_economy_shipping_cost_to_use) selected_shipping_cost.cost = get_economy_shipping_cost_to_use save if autosave order&.reload&.reset_discount(reset_item_pricing: false) end |
#rma_for_return ⇒ Rma?
Returns the associated rma for return.
132 |
# File 'app/models/delivery.rb', line 132 has_one :rma_for_return, class_name: 'Rma', foreign_key: 'return_delivery_id', dependent: :nullify |
#same_day_pickup? ⇒ Boolean
Whether the carrier-confirmed pickup is today, in the sender's timezone.
The send happens at sender-local midnight boundaries, so compare dates in
that zone rather than UTC.
3340 3341 3342 3343 3344 3345 |
# File 'app/models/delivery.rb', line 3340 def same_day_pickup? return false unless confirmed_pickup_date.present? tz = origin_address&.timezone_name.presence || 'America/Chicago' confirmed_pickup_date == Time.current.in_time_zone(tz).to_date end |
#save_purchase_order_if_needed(po) ⇒ void
This method returns an undefined value.
Persists a freshly built PurchaseOrder only if at least one PO
item was added (so empty supplier groups don't create empty POs).
2718 2719 2720 |
# File 'app/models/delivery.rb', line 2718 def save_purchase_order_if_needed(po) po.save! unless po.purchase_order_items.empty? end |
#schedule_pickup_if_necessary ⇒ void
This method returns an undefined value.
Queues the FedEx Freight pickup-scheduling worker (US or CA variant)
when this delivery has been ship-labeled through Heatwave. No-op for
non-FedEx-Freight carriers.
4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 |
# File 'app/models/delivery.rb', line 4520 def schedule_pickup_if_necessary # Only schedule pickups for FedExFreight (US and CA). return unless ship_labeled_via_heatwave? && pending_ship_confirm? if origin_address&.country&.iso == 'US' && carrier.index('FedEx') && carrier.index('Freight') FedExFreightUsSchedulePickupWorker.new.perform elsif origin_address&.country&.iso == 'CA' && carrier.index('FedEx') && carrier.index('Freight') FedExFreightCaSchedulePickupWorker.new.perform end end |
#schedule_request_estimated_packaging ⇒ String
after_save callback that re-queues the pre-pack worker when state
drift between an order and its delivery puts them out of sync.
4465 4466 4467 |
# File 'app/models/delivery.rb', line 4465 def schedule_request_estimated_packaging DeliveryRequestPrePackWorker.perform_in(5.seconds, id) end |
#selected_shipping_cost ⇒ ShippingCost?
Returns the selected shipping cost this record belongs to.
113 |
# File 'app/models/delivery.rb', line 113 belongs_to :selected_shipping_cost, class_name: 'ShippingCost', optional: true |
#send_address_type_issue_notification ⇒ void
This method returns an undefined value.
Notifies the team when a carrier reports an address-type issue
(residential vs commercial) on this delivery's destination so it can
be reclassified.
2727 2728 2729 |
# File 'app/models/delivery.rb', line 2727 def send_address_type_issue_notification DeliveryMailer.address_type_issue_notification(self).deliver end |
#send_canada_post_manual_void_email(tracking_numbers) ⇒ void
This method returns an undefined value.
Emails Canada Post asking them to manually void labels we couldn't
void via API (Canada Post has no programmatic void endpoint).
2888 2889 2890 |
# File 'app/models/delivery.rb', line 2888 def send_canada_post_manual_void_email(tracking_numbers) Mailer.canada_post_manual_void_email(self, tracking_numbers).deliver end |
#send_commercial_invoice_to_carrier ⇒ void
This method returns an undefined value.
Forwards the commercial invoice to the carrier's customs email when
the carrier requires manual customs handoff (R+L Carriers,
Freightquote / Polaris). No-op when carrier is e-CI-capable.
2906 2907 2908 |
# File 'app/models/delivery.rb', line 2906 def send_commercial_invoice_to_carrier DeliveryMailer.commercial_invoice_to_carrier(self).deliver if should_send_commercial_invoice_to_carrier? end |
#send_delivery_pre_pack_cancelled_notification(cancelled_by: nil) ⇒ void
This method returns an undefined value.
Publishes Events::DeliveryPrePackCancelled so
DeliveryPrePackCancelledNotificationHandler can re-query this delivery
by id and email the warehouse / requester, naming the cancelling user.
Same-transaction-destroy safe (see send_delivery_pre_packed_notification).
2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 |
# File 'app/models/delivery.rb', line 2870 def send_delivery_pre_pack_cancelled_notification(cancelled_by: nil) delivery_id = id cancelled_by_id = cancelled_by&.id ActiveRecord.after_all_transactions_commit do Rails.configuration.event_store.publish( Events::DeliveryPrePackCancelled.new(data: { delivery_id:, cancelled_by_id: }), stream_name: "Delivery-#{delivery_id}" ) rescue StandardError => e ErrorReporting.error(e) end end |
#send_delivery_pre_packed_notification ⇒ void
This method returns an undefined value.
Publishes Events::DeliveryPrePacked so
DeliveryPrePackedNotificationHandler can re-query this delivery by id
and email the team. The async re-query tolerates a same-transaction
destroy (purge_empty_quoting_deliveries) that would otherwise leave
the mailer's GlobalID arg pointing at a phantom row (AppSignal #4958).
The handler also clears suggested_packaging_text.
2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 |
# File 'app/models/delivery.rb', line 2747 def send_delivery_pre_packed_notification delivery_id = id ActiveRecord.after_all_transactions_commit do Rails.configuration.event_store.publish( Events::DeliveryPrePacked.new(data: { delivery_id: }), stream_name: "Delivery-#{delivery_id}" ) rescue StandardError => e ErrorReporting.error(e) end end |
#send_dropship_delivery_notification ⇒ void
This method returns an undefined value.
Sends the internal "new dropship delivery" notification announcing
that supplier purchase orders have been generated.
2735 2736 2737 |
# File 'app/models/delivery.rb', line 2735 def send_dropship_delivery_notification DeliveryMailer.dropship_delivery_notification(self).deliver end |
#send_purolator_manual_void_email(tracking_numbers) ⇒ void
This method returns an undefined value.
Emails Purolator asking them to manually void labels we couldn't
void via API.
2897 2898 2899 |
# File 'app/models/delivery.rb', line 2897 def send_purolator_manual_void_email(tracking_numbers) Mailer.purolator_manual_void_email(self, tracking_numbers).deliver end |
#serial_numbers_file_name ⇒ String
Filename for the bundled serial-number labels PDF, dated to the current
minute to avoid tmp/ collisions on regeneration.
1332 1333 1334 |
# File 'app/models/delivery.rb', line 1332 def serial_numbers_file_name "#{name(false, true)}_generated_serial_numbers_#{Time.current.strftime('%m_%d_%Y_%I_%M%p')}.pdf" end |
#serial_numbers_to_print ⇒ Array<SerialNumber>
Reserved serial numbers for this delivery, including the original/swapped
numbers when an item was re-serialized. De-duplicated so each number
prints once on the labels PDF.
1341 1342 1343 1344 1345 1346 1347 1348 |
# File 'app/models/delivery.rb', line 1341 def serial_numbers_to_print serial_numbers = [] reserved_serial_numbers.each do |rsn| serial_numbers << rsn.serial_number serial_numbers << rsn.original_serial_number if rsn.original_serial_number.present? end serial_numbers.uniq end |
#set_cogs ⇒ void
This method returns an undefined value.
Stamps unit and total cost of goods sold onto every LineItem on
this delivery. Pulls per-store COGS for store transfers, the
catalog/store item COGS for normal orders, and the carrier-actual
shipping cost for the shipping line.
2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 |
# File 'app/models/delivery.rb', line 2516 def set_cogs is_st = order && order.order_type == Order::STORE_TRANSFER line_items.non_shipping.each do |li| cogs = if li.parent_id.present? 0.0 elsif is_st li.item.store_item_for(order.from_store_id, 'AVAILABLE').unit_cogs else li.catalog_item.store_item.unit_cogs end li.update(unit_cogs: cogs, total_cogs: cogs * li.quantity) end line_items.shipping_only.each do |li| li.update(unit_cogs: actual_shipping_cost, total_cogs: actual_shipping_cost) end end |
#set_master_tracking_and_actual_shipping_cost_if_needed ⇒ void
This method returns an undefined value.
Backfills the delivery's master_tracking_number, ltl_pro_number,
and actual_shipping_cost from the first completed Shipment when
they're missing — typically after dropship PO shipments are copied
over.
4064 4065 4066 4067 4068 4069 4070 4071 |
# File 'app/models/delivery.rb', line 4064 def set_master_tracking_and_actual_shipping_cost_if_needed return unless s = shipments.completed.first self.master_tracking_number ||= s.tracking_number self.ltl_pro_number ||= s.tracking_number if ships_ltl_freight? self.actual_shipping_cost ||= s.actual_total_charges save if master_tracking_number_changed? || ltl_pro_number_changed? || actual_shipping_cost_changed? end |
#set_override_shipping(autosave = true) ⇒ void
This method returns an undefined value.
Forces this delivery onto the override ShippingOption at $0.00 —
used by service-only / store-transfer flows where shipping isn't
billed separately.
4385 4386 4387 4388 4389 4390 4391 |
# File 'app/models/delivery.rb', line 4385 def set_override_shipping(autosave = true) so = ShippingOption.where(name: 'override', country: resource.store.country.iso).first self.shipping_option_id = so.id self.selected_shipping_cost = shipping_costs.detect { |sc| sc.shipping_option_id == shipping_option_id } || shipping_costs.new(shipping_option: so, cost: 0.0) selected_shipping_cost.cost = 0.0 save if autosave end |
#set_packaged_items_md5_hash(options = {}) ⇒ void
This method returns an undefined value.
Records the packed-items signature for this delivery via
Shipping::DeliveryMd5Extractor so future shipping recalculations
know not to wipe authoritative packing.
3655 3656 3657 |
# File 'app/models/delivery.rb', line 3655 def set_packaged_items_md5_hash( = {}) Shipping::DeliveryMd5Extractor.new().process(self) end |
#set_proper_shipping_cost ⇒ Boolean?
before_save driver that picks the right ShippingCost for the
delivery and syncs it onto the delivery and the shipping LineItem.
The carrier-selection policy lives in SetProperShippingCost
(god-object decomposition) — this delegator keeps the callback wiring
and public API on the model.
2450 2451 2452 |
# File 'app/models/delivery.rb', line 2450 def set_proper_shipping_cost Delivery::SetProperShippingCost.new(self).process end |
#set_shipped_date ⇒ void
This method returns an undefined value.
Stamps the first ship event onto the delivery; idempotent so a
re-shipment doesn't move the date.
3714 3715 3716 |
# File 'app/models/delivery.rb', line 3714 def set_shipped_date update_attribute(:shipped_date, Time.current) if shipped_date.blank? end |
#ship_ci_pdf ⇒ Upload?
Most recent commercial-invoice Upload attached to the delivery
(printable triplicate variant).
3406 3407 3408 |
# File 'app/models/delivery.rb', line 3406 def ship_ci_pdf uploads.order(:id).reverse_order.find_by(category: 'ship_ci_pdf') end |
#ship_from_attributes ⇒ Hash?
Address attributes used as the "ship from" block on labels and
commercial invoices — driven by the delivery's origin warehouse for
orders, or the RMA's ship-from for returns.
3841 3842 3843 |
# File 'app/models/delivery.rb', line 3841 def ship_from_attributes order&.ship_from_attributes(self) || rma_for_return&.ship_from_attributes end |
#ship_labeled_via_heatwave? ⇒ Boolean
Returns whether the record ship labeled via heatwave.
829 830 831 |
# File 'app/models/delivery.rb', line 829 def ship_labeled_via_heatwave? supported_shipping_carrier? && shipments.completed.any? && shipments.completed.all? { |s| s.state == 'label_complete' } end |
#ship_labeled_via_heatwave_or_manual_and_ship_insuring? ⇒ Boolean
Returns whether the record ship labeled via heatwave or manual and ship insuring.
834 835 836 |
# File 'app/models/delivery.rb', line 834 def ship_labeled_via_heatwave_or_manual_and_ship_insuring? ship_labeled_via_heatwave? || is_amazon_seller_central_veeqo? end |
#ship_natively_key ⇒ Symbol?
Account-number lookup key used to pick credentials when shipping on
the customer's own carrier account ("ship natively"); nil when we
ship on Heatwave's accounts.
3702 3703 3704 3705 3706 3707 3708 |
# File 'app/models/delivery.rb', line 3702 def ship_natively_key key = nil if chosen_shipping_method&.shipping_account_number && chosen_shipping_method.shipping_account_number.ship_natively? && chosen_shipping_method.shipping_account_number.account_number.present? key = chosen_shipping_method.shipping_account_number.account_number.to_sym end key end |
#ship_to_attributes ⇒ Hash?
3832 3833 3834 |
# File 'app/models/delivery.rb', line 3832 def ship_to_attributes order&.ship_to_attributes || rma_for_return&.ship_to_attributes end |
#shipment_contents_editable?(current_user = nil) ⇒ Boolean
Returns whether the record shipment contents editable.
3025 3026 3027 3028 3029 |
# File 'app/models/delivery.rb', line 3025 def shipment_contents_editable?(current_user = nil) return false if locked_for_fba? && !current_user&.has_role?('admin') picking? || pending_ship_labels? || pre_pack? # rb_any_ship_from || processing_po_fulfillment? end |
#shipment_event_tracking_numbers ⇒ Array<String>
Tracking numbers whose ShipmentEvent scans belong to this delivery: the
per-package shipment tracking numbers PLUS the LTL freight PRO, which lives
on the delivery (not its pallet shipments) for ShipEngine LTL. Single
source for the Tracking Events tab's nav counter and content query so the
two can't drift — parcel scans key off shipment tracking_numbers, ShipEngine
LTL scans off ltl_pro_number.
2363 2364 2365 2366 2367 2368 2369 |
# File 'app/models/delivery.rb', line 2363 def shipment_event_tracking_numbers # Use the loaded association in-memory when the caller preloaded shipments # (list views) so this doesn't fire a pluck query per delivery; fall back to # pluck for the single-delivery (show-page) path where nothing's preloaded. numbers = shipments.loaded? ? shipments.map(&:tracking_number) : shipments.pluck(:tracking_number) (numbers + [ltl_pro_number]).compact_blank.uniq end |
#shipments ⇒ ActiveRecord::Relation<Shipment>
Returns the associated shipments.
144 |
# File 'app/models/delivery.rb', line 144 has_many :shipments, -> { order(:created_at) }, autosave: true, dependent: :destroy |
#shipments_for_packing ⇒ ActiveRecord::Relation<Shipment>
Shipments currently visible to the packing UI — those still being
built (suggested), already packed, or awaiting carrier labels.
1057 1058 1059 |
# File 'app/models/delivery.rb', line 1057 def shipments_for_packing shipments.suggested_packed_or_awaiting_labels.order(:created_at) end |
#shipments_to_packages_hash(use_shipments = nil) ⇒ Object
Bridge method from Shipments to package hash model used by WyShipping
1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 |
# File 'app/models/delivery.rb', line 1711 def shipments_to_packages_hash(use_shipments = nil) # Packed packages are used first, top level, ie not cartons packed on pallets, etc... if none present, we look at suggested use_shipments ||= shipments.top_level.where(state: 'packed').presence use_shipments ||= shipments.top_level.where(state: 'suggested') shipping_weights = [] shipping_dimensions = [] flat_rate_package_types = [] container_types = [] package_values = [] use_shipments.each do |shp| # Convert BigDecimal to float to prevent JSON serialization as strings in carrier_responses jsonb shipping_weights << shp.weight.to_f shipping_dimensions << [shp.length.to_f, shp.width.to_f, shp.height.to_f] flat_rate_package_types << shp.flat_rate_package_type container_types << shp.container_type package_values << shp.compute_shipment_declared_value end { shipping_weights:, shipping_dimensions:, flat_rate_package_types:, container_types:, package_values: } end |
#shipments_voidable? ⇒ Boolean
Returns whether the record shipments voidable.
3032 3033 3034 |
# File 'app/models/delivery.rb', line 3032 def shipments_voidable? SHIPPING_STATES.include?(state.to_sym) end |
#shipping? ⇒ Boolean
Returns whether the record shipping.
3037 3038 3039 |
# File 'app/models/delivery.rb', line 3037 def shipping? %i[pending_ship_confirm shipped].include?(state.to_sym) end |
#shipping_account_number ⇒ ShippingAccountNumber?
Returns the shipping account number this record belongs to.
109 |
# File 'app/models/delivery.rb', line 109 belongs_to :shipping_account_number, optional: true |
#shipping_costs ⇒ ActiveRecord::Relation<ShippingCost>
dependent destroy handled by trigger
134 |
# File 'app/models/delivery.rb', line 134 has_many :shipping_costs, -> { order(:cost) }, autosave: true |
#shipping_line_item ⇒ LineItem?
Single shipping LineItem for the delivery (the row that bills the
carrier cost). Each delivery has at most one — extra ones are
cleaned up by #apply_selected_shipping_cost!.
1683 1684 1685 |
# File 'app/models/delivery.rb', line 1683 def shipping_line_item line_items.shipping_only.first end |
#shipping_method_friendly ⇒ String
Compact human-readable shipping method label for delivery summary
widgets — collapses overrides, warehouse pickups, and service-only
deliveries to descriptive text rather than the raw shipping option name.
1372 1373 1374 1375 1376 1377 1378 1379 |
# File 'app/models/delivery.rb', line 1372 def shipping_method_friendly return 'Service' if is_service_only? return 'Unknown' unless so = shipping_option return 'Pickup' if destination_address&.is_warehouse return line_items.shipping_only.map(&:shipping_cost).compact.first&.description if so.is_override? so.description end |
#shipping_methods_for_select(verbose = false, skip_override = false) ⇒ Array<Array(String, Integer)>
<select> payload of formatted carrier rate options, currency-aware
and with optional commitment text and account hints.
2402 2403 2404 2405 2406 2407 2408 2409 |
# File 'app/models/delivery.rb', line 2402 def shipping_methods_for_select(verbose = false, skip_override = false) sorted_shipping_costs(skip_override).map do |sc| [ "#{ActionController::Base.helpers.number_to_currency(sc.cost.round(2), unit: currency_symbol)}: #{sc.shipping_option.description} #{sc.third_party_billed? ? " (using your linked account: #{sc.shipping_account_number.account_number})" : ''} #{verbose ? "(#{sc.shipping_option.delivery_commitment})" : ''} ", sc.shipping_option.id ] end end |
#shipping_option ⇒ ShippingOption?
Returns the shipping option this record belongs to.
111 |
# File 'app/models/delivery.rb', line 111 belongs_to :shipping_option, optional: true |
#shipping_option_matches?(so_name) ⇒ Boolean
Returns whether the record shipping option matches.
1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 |
# File 'app/models/delivery.rb', line 1383 def shipping_option_matches?(so_name) return false if shipping_option.blank? # If our shipping option match straight we can return true right here return true if shipping_option.name == so_name # For Walmart orders, any Ship with Walmart (SWW) option is valid # 'sww' or 'override' as edi_shipping_option_name indicates any WalmartSeller option is acceptable if order&.edi_orchestrator_partner&.start_with?('walmart_seller') && shipping_option.carrier == 'WalmartSeller' return true end # 'sww' or any 'sww_*' pattern allows any SWW shipping option # This handles both the generic 'sww' marker and specific options like 'sww_fedex_smartpost' return true if (so_name == 'sww' || so_name&.start_with?('sww_')) && shipping_option.name.start_with?('sww_') # For Amazon Buy Shipping orders, any AmazonSeller shipping option is valid if order&.edi_orchestrator_partner&.start_with?('amazon_seller') && shipping_option.carrier == 'AmazonSeller' return true end # 'amzbs' or any 'amzbs_*' pattern allows any Amazon Buy Shipping option return true if (so_name == 'amzbs' || so_name&.start_with?('amzbs_')) && shipping_option.name.start_with?('amzbs_') # deal with non-exact matching ie fedex ground vs fedex ground residential if so_name.match?('fedex_ground') shipping_option.name.match?('fedex_ground') elsif customer.is_wayfair? && so_name.match?('nextdayair') # deal with Wayfair's option to use UPS second day air when UPS next day air or next day air saver is not available, see: https://partners.wayfair.com/help/2/article/323 shipping_option.name.match?('secondayair') elsif customer.is_wayfair? && so_name.match?('fedex_standard_overnight') # deal with Wayfair's option to use FedEx two day when FedEx standard overnight is not available, see: https://partners.wayfair.com/help/2/article/323 shipping_option.name.match?('fedex_twoday') else false end end |
#ships_economy? ⇒ Boolean Also known as: ships_economy
don't know why, but need to do it this way, can't use delegate
4536 4537 4538 |
# File 'app/models/delivery.rb', line 4536 def ships_economy? # don't know why, but need to do it this way, can't use delegate resource&.ships_economy? || false end |
#ships_economy_ltl? ⇒ Boolean
Returns whether the record ships economy ltl.
4547 4548 4549 |
# File 'app/models/delivery.rb', line 4547 def ships_economy_ltl? ships_economy && ships_ltl_freight? end |
#ships_economy_package? ⇒ Boolean
Returns whether the record ships economy package.
4542 4543 4544 |
# File 'app/models/delivery.rb', line 4542 def ships_economy_package? ships_economy && !ships_ltl_freight? end |
#ships_ltl_freight? ⇒ Boolean
Returns whether the record ships ltl freight.
2351 2352 2353 |
# File 'app/models/delivery.rb', line 2351 def ships_ltl_freight? !is_warehouse_pickup? && (ltl_freight.present? || ltl_freight_guaranteed.present?) end |
#should_have_electronic_commercial_invoice? ⇒ Boolean
Returns whether the record should have electronic commercial invoice.
3419 3420 3421 |
# File 'app/models/delivery.rb', line 3419 def should_have_electronic_commercial_invoice? is_international? && (carrier == 'UPS' || carrier == 'FedEx') && shipments_voidable? # only return true on international deliveries using UPS when in shipping states, i.e. not quoting invoiced, etc end |
#should_print_heating_element_labels? ⇒ Boolean
Returns whether the record should print heating element labels.
839 840 841 842 |
# File 'app/models/delivery.rb', line 839 def should_print_heating_element_labels? line_items.any? { |li| li.item.controllable? } && line_items.any? { |li| li.item.is_thermostat? || li.item.is_towel_warmer_hardwired_control? || li.item.is_power? } end |
#should_send_commercial_invoice_to_carrier? ⇒ Boolean
Whether to auto-email the customs commercial invoice to the LTL carrier.
The commented-out ltl_pro_number != master_tracking_number guard below is
disabled ON PURPOSE: Polaris runs eBOL off, so the real carrier PRO never
arrives via CHR's events API and ltl_pro_number falls back to the CHR
tracking number. We deliberately send anyway — the load number on the BOL is
the cross-reference. Do NOT re-enable the guard; it would suppress the CI
forever. Full rationale + workaround:
doc/development/freightquote_events_pipeline.md (Customs commercial-invoice
email — the async-PRO / Polaris eBOL gap).
2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 |
# File 'app/models/delivery.rb', line 2919 def should_send_commercial_invoice_to_carrier? carrier_qualifies = false if reported_carrier == 'Freightquote' carrier_qualifies = true if FREIGHTQUOTE_CARRIER_SCACS_TO_SEND_COMMERCIAL_INVOICES.include?(selected_shipping_cost&.rate_data&.dig('scac')) # eBol functionality does not work for Polaris, so just send it using load number which hopefully works && ltl_pro_number != master_tracking_number) # We ensure that the Freqightquote carrier SCAC is included and also that ltl_pro_number is not the same as master_tracking_number, which is a sign that the warehouse did not update the ltl_pro_number from the carrier. For Freightquote this is populated via the events API, so we let the automated GetFreightquoteLoadNumber job send the commercial_invoice, when the pro number is populated else carrier_qualifies = true if CARRIERS_NAMES_TO_SEND_COMMERCIAL_INVOICES.include?(reported_carrier) # Otherwise we just trust the ltl_pro_number as entered by the warehouse or Freightquote events API end is_international? && ship_ci_pdf&..present? && carrier_qualifies && ltl_pro_number.present? # we must have an international delivery, with a CI attached, with a qualifying carrier and an LTL Pro number end |
#should_ship_ltl_freight? ⇒ Boolean
Returns whether the record should ship ltl freight.
2387 2388 2389 |
# File 'app/models/delivery.rb', line 2387 def should_ship_ltl_freight? is_default_ltl_freight? || ltl_freight.present? || ltl_freight_guaranteed.present? end |
#show_packaging_on_pick_slip? ⇒ Boolean
Returns whether the record show packaging on pick slip.
1460 1461 1462 |
# File 'app/models/delivery.rb', line 1460 def show_packaging_on_pick_slip? destination_address&.is_amazon? end |
#simple_shipping_description_for_shipping_cost(sc = nil) ⇒ String?
Plain-text label describing the ShippingCost (no COD/insurance
badges). Recognises override variants — service, warehouse pickup,
zero-charge dropship, economy — and Ship-with-Walmart rates whose
descriptions are pulled from rate_data.
2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 |
# File 'app/models/delivery.rb', line 2218 def simple_shipping_description_for_shipping_cost(sc = nil) sc ||= selected_shipping_cost # nil description means line item name will default to linked item shipping option name, otherwise it's an override if sc&.is_override? && is_service_only? 'Service' elsif sc&.is_override? && is_warehouse_pickup? 'Warehouse Pickup' elsif sc&.is_override? && is_zero_charge_dropship? 'Zero charge dropship' elsif sc&.is_override? && ships_economy_package? 'Economy Shipping (up to 7-9 business days)' elsif is_sww_shipping_cost?(sc) # Ship with Walmart rate - use the shipping option description which is already formatted as "FedEx Ground Economy" # The sww_service_name already includes the carrier name, so we don't need to add it again sww_description = sc.shipping_option&.description || sc.rate_data&.dig('sww_service_name') || sc.rate_data&.dig(:sww_service_name) || 'Unknown Service' "Ship with Walmart: #{sww_description}".strip else sc&.description end end |
#skip_destination_address_validation? ⇒ Boolean
Skip destination_address validation for instant quotes and shopping carts
Carts don't have a shipping address until checkout
2480 2481 2482 |
# File 'app/models/delivery.rb', line 2480 def skip_destination_address_validation? instant_quote? || resource.try(:cart?) end |
#sorted_ground_shipping_costs(skip_override = false) ⇒ Array<ShippingCost>
Ground-tier ShippingCost options sorted cheapest-first, with the
override row pushed to the bottom. Service level is taken from the
underlying shipping_option.days_commitment so dynamic carrier ETAs
don't bleed into categorization.
1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 |
# File 'app/models/delivery.rb', line 1894 def sorted_ground_shipping_costs(skip_override = false) res = shipping_costs # res = shipping_costs.skip_3rd_party # we ignore skip_3rd_party res = res.skip_override if skip_override # sort by cost then days committment, override options last if any # Use shipping_option.days_commitment for service level categorization (ground vs expedited vs rush) # NOT sc.days_commitment which now includes dynamic carrier estimates that vary by shipment res.select { |sc| sc.shipping_option.days_commitment >= 3.5 || sc.shipping_option.carrier == 'SpeedeeDelivery' || sc.is_override? }.sort_by do |sc| sk1 = (sc.shipping_option.name == 'override' ? 9999 : 1) sk2 = begin (sc.rate_data['actual_cost'] || sc.cost).to_f.round(2) rescue StandardError 9999.0 end sk3 = begin (-1.0 * sc.days_commitment.to_f) rescue StandardError 9999.0 end [sk1, sk2, sk3] end end |
#sorted_shipping_costs(skip_override: false, uniq_by_shipping_option_id: false, filter_by_ltl_freight: nil) ⇒ Array<ShippingCost>
Full ShippingCost list for the delivery sorted cheapest-first with
override last. Optionally filters by LTL/package context and
de-duplicates rows that share a shipping_option_id (favoring the
latest insert so before-save churn doesn't pick a soon-to-be-deleted
row).
1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 |
# File 'app/models/delivery.rb', line 1927 def sorted_shipping_costs(skip_override: false, uniq_by_shipping_option_id: false, filter_by_ltl_freight: nil) res = shipping_costs.includes(:shipping_option) # res = shipping_costs.skip_3rd_party # we ignore skip_3rd_party res = res.skip_override if skip_override # keep default behavior for nil, otherwise look for matching is_freightflag on the linked shipping option unless filter_by_ltl_freight.nil? res = res.select{|sc| sc.shipping_option.is_freight == filter_by_ltl_freight || sc.is_override?} # always include override since the scope above will handle skipping it end if uniq_by_shipping_option_id res = res.sort_by(&:id).reverse.uniq(&:shipping_option_id).reverse # here we favor the higher IDs since this can be called in a before)save context where the lower IDs will get deleted end # sort by cost then days committment, override options last if any res.sort_by do |sc| sk1 = (sc.shipping_option.name == 'override' ? 9999 : 1) sk2 = begin (sc.rate_data['actual_cost'] || sc.cost).to_f.round(2) rescue StandardError 9999.0 end sk3 = begin (-1.0 * sc.days_commitment.to_f) rescue StandardError 9999.0 end [sk1, sk2, sk3] end end |
#sorted_shipping_costs_www_hash(sort_by_price: true) ⇒ Hash{Symbol => Array<ShippingCost>}
Service-level grouped ShippingCost buckets used by the WWW shipping
picker: :economy (override), :ground, :faster (expedited+rush),
:freight. Extracted to GroupWwwShippingCosts (god-object
decomposition).
1962 1963 1964 |
# File 'app/models/delivery.rb', line 1962 def sorted_shipping_costs_www_hash(sort_by_price: true) Delivery::GroupWwwShippingCosts.new(self, sort_by_price: sort_by_price).process end |
#split_serial_numbers ⇒ void
This method returns an undefined value.
Splits each reservation-requiring LineItem into per-serial-number
rows so each unit can carry its own serial. Inverse of
#rejoin_serial_numbers.
4162 4163 4164 |
# File 'app/models/delivery.rb', line 4162 def split_serial_numbers line_items.select(&:require_reservation?).each(&:split_serial_numbers) end |
#state_list ⇒ Array<Symbol>
Ordered list of states this delivery actually moves through, used
to render the progress stepper in the UI. Varies by delivery flavor:
warehouse pickup, dropship, RMA replacement, service-only, or
standard shipping.
3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 |
# File 'app/models/delivery.rb', line 3917 def state_list if order && (order.order_type == Order::CREDIT_ORDER) %i[pending_ship_labels return_labels_complete] elsif is_warehouse_pickup? %i[at_warehouse picking pending_pickup_confirm shipped invoiced] elsif has_dropship_items? && !order.single_origin %i[awaiting_po_fulfillment processing_po_fulfillment shipped invoiced] elsif order && (order.is_rma_replacement? || order.precreate_rma?) %i[at_warehouse picking pending_ship_labels pending_ship_confirm shipped invoiced] elsif is_service_only? %i[service_ready_to_fulfill shipped invoiced cancelled] else %i[at_warehouse picking pending_ship_labels pending_ship_confirm shipped invoiced] end end |
#store ⇒ Object
Alias for Resource#store
223 |
# File 'app/models/delivery.rb', line 223 delegate :store, to: :resource |
#supplier ⇒ Supplier?
Returns the supplier this record belongs to.
115 |
# File 'app/models/delivery.rb', line 115 belongs_to :supplier, optional: true |
#supported_shipping_carrier? ⇒ Boolean
Returns whether the record supported shipping carrier.
3859 3860 3861 3862 3863 3864 3865 3866 3867 |
# File 'app/models/delivery.rb', line 3859 def supported_shipping_carrier? return false if override_shipping_method? # Standard carriers (FedEx, UPS, USPS, etc.) return true if (SUPPORTED_SHIPPING_CARRIERS[country&.iso3&.to_sym] || []).include?(carrier) # Marketplace carriers (WalmartSeller, etc.) - label purchase via marketplace API Edi::MarketplaceLabelPurchaser.marketplace_carrier?(carrier) end |
#third_party_billed? ⇒ Boolean
Single source of truth, at the delivery layer, for "is the carrier
billed to the customer's own (attached) account?" — i.e. a
ShippingAccountNumber is attached to the chosen shipping method.
Selecting a SAN (auto-default OR manually from the rate-shop dropdown,
which is allowed even when the owner's bill_shipping_to_customer is
off) IS the instruction to bill it; the attached SAN carries the
account number. The carrier label
(WyShipping.classify_third_party_billing), the customer invoice
(#adjusted_actual_shipping_cost, ShippingCost#calculated_cost),
and the on-screen account labels all gate on this one predicate so
they cannot drift apart (the drift is what billed WarmlyYours's
account while zeroing the customer invoice — SO723955, SO725940).
3673 3674 3675 |
# File 'app/models/delivery.rb', line 3673 def third_party_billed? chosen_shipping_method&.third_party_billed? || false end |
#tracking_link ⇒ String?
Public tracking URL for the delivery, resolving marketplace carriers
(Amazon, Walmart) to the underlying first-mile carrier when available.
3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 |
# File 'app/models/delivery.rb', line 3547 def tracking_link resolved_carrier = case carrier when 'AmazonSeller' shipments.detect { |s| s.amz_carrier.present? }&.amz_carrier || carrier when 'WalmartSeller' shipments.detect { |s| s.sww_carrier.present? }&.sww_carrier || carrier else carrier end # ShipEngine LTL freight tracks by its PRO (ltl_pro_number), not the # master_tracking_number — for some carriers the latter is a non-trackable # booking number (e.g. R&L returns WB…/WC… at booking while the trackable # PRO arrives asynchronously into ltl_pro_number). number = shipping_option&.is_shipengine_ltl? ? ltl_pro_number.presence : master_tracking_number Shipment.tracking_link(resolved_carrier, number) end |
#uncommit_catalog_items ⇒ void
This method returns an undefined value.
Reverses #commit_catalog_items when a delivery is cancelled,
returning quantity-available back to inventory.
3739 3740 3741 |
# File 'app/models/delivery.rb', line 3739 def uncommit_catalog_items Item::InventoryCommitter.crm_uncommit(line_items) end |
#uncommit_reserved_serial_numbers ⇒ void
This method returns an undefined value.
Reverses #commit_reserved_serial_numbers so the serials become
available for another delivery.
3747 3748 3749 |
# File 'app/models/delivery.rb', line 3747 def uncommit_reserved_serial_numbers line_items.each(&:uncommit_reserved_serial_numbers) end |
#unlink_serial_numbers_to_line_items ⇒ void
This method returns an undefined value.
Reverses #link_serial_numbers_to_line_items (e.g. when an invoiced
delivery is reverted) so the serials become available again.
4187 4188 4189 |
# File 'app/models/delivery.rb', line 4187 def unlink_serial_numbers_to_line_items line_items.each(&:unlink_serial_numbers) end |
#update_line_items_qty_shipped ⇒ Object
Updates qty_shipped to match quantity for all line items in this delivery.
Uses update_all for atomicity and to prevent deadlocks when multiple
processes ship deliveries concurrently (see AppSignal #1981).
3754 3755 3756 |
# File 'app/models/delivery.rb', line 3754 def update_line_items_qty_shipped line_items.update_all('qty_shipped = quantity') end |
#update_serial_numbers_shipped_count ⇒ void
This method returns an undefined value.
Bumps the per-SerialNumber shipped count after a successful ship
event so analytics and warranty tracking stay current.
4203 4204 4205 |
# File 'app/models/delivery.rb', line 4203 def update_serial_numbers_shipped_count line_items.each(&:update_serial_numbers_shipped_count) end |
#uploads ⇒ ActiveRecord::Relation<Upload>
Returns the associated uploads.
146 |
# File 'app/models/delivery.rb', line 146 has_many :uploads, as: :resource, dependent: :destroy |
#valid_for_generating_return_labels? ⇒ Boolean
Returns whether the record valid for generating return labels.
3884 3885 3886 3887 3888 3889 |
# File 'app/models/delivery.rb', line 3884 def return true if pending_ship_labels? && supported_shipping_carrier? && is_domestic? && shipments.packed_or_awaiting_labels.any? errors.add(:base, 'needs be in state pending ship labels, shipping domestically, and with a supported carrier and shipments ready to label') false end |
#valid_for_generating_ship_labels? ⇒ Boolean
Returns whether the record valid for generating ship labels.
3870 3871 3872 3873 3874 3875 3876 |
# File 'app/models/delivery.rb', line 3870 def return false unless pending_ship_labels? && supported_shipping_carrier? return true if is_domestic? return true if shipping_option.supported_for_st && order&.is_store_transfer? false end |
#valid_for_voiding_ship_labels? ⇒ Boolean
Returns whether the record valid for voiding ship labels.
3879 3880 3881 |
# File 'app/models/delivery.rb', line 3879 def valid_for_voiding_ship_labels? pending_ship_confirm? && shipments.label_complete.any? && !is_part_of_transmitted_manifest? end |
#validate_all_contents_allocated ⇒ void
4234 4235 4236 4237 4238 |
# File 'app/models/delivery.rb', line 4234 def validate_all_contents_allocated return if all_lines_allocated_to_shipments? errors.add(:base, 'Not all lines are allocated properly to containers') end |
#verify_payment_coverage! ⇒ void
This method returns an undefined value.
Re-checks gateway payment authorization right before pickup-confirm,
raising if authorizations have expired or been voided externally.
Skipped for non-gateway payment types (PO, Check, Wire, Store Credit).
3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 |
# File 'app/models/delivery.rb', line 3006 def verify_payment_coverage! return unless order return unless order.payments.any? { |p| p.category.in?(GATEWAY_PAYMENT_TYPES) } order.check_payments_status order.reload if order.persisted? unless order.all_funds_available? detail = order.funds_shortfall_report msg = "Cannot ship delivery #{id}: order #{order.id} does not have sufficient " \ "authorized or captured funds (balance: $#{'%.2f' % order.balance})." msg += " Uncovered — #{detail}." if detail.present? msg += ' Resolve payment before shipping.' raise msg end end |
#versions_for_audit_trail(_params = {}) ⇒ ActiveRecord::Relation<RecordVersion>
RecordVersion rows for the audit trail tab — covers the delivery
itself plus any line-item versions whose reference_data scopes them
to this delivery (under either Order or Quote ownership).
4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 |
# File 'app/models/delivery.rb', line 4308 def versions_for_audit_trail(_params = {}) query_sql = %q{ ( item_type = 'LineItem' AND reference_data @> '{"resource_type": "Order"}' AND reference_data @> :delivery_id_json ) OR ( item_type = 'LineItem' AND reference_data @> '{"resource_type": "Quote"}' AND reference_data @> :delivery_id_json ) OR (item_type = 'Delivery' AND item_id = :id) } RecordVersion.where(query_sql, id:, delivery_id_json: { delivery_id: id }.to_json) end |
#void_early_label_on_order ⇒ Boolean
Void early-purchased label on the order if one exists and hasn't been transferred to a shipment yet
Only called when there are no completed shipments (label not yet transferred)
3508 3509 3510 3511 3512 3513 3514 3515 3516 |
# File 'app/models/delivery.rb', line 3508 def void_early_label_on_order return false unless resource.is_a?(Order) return false unless resource.respond_to?(:has_early_purchased_label?) return false unless resource.has_early_purchased_label? Rails.logger.info("[Delivery] Voiding early label on order #{resource.reference_number} (not yet transferred to shipment)") resource.void_early_label!(reason: 'Shipments voided on delivery') true end |
#void_early_label_on_order_if_exists ⇒ Object
Void the early-purchased label on the order if one exists (regardless of
whether it was transferred to a shipment). Also resets purchase_label_early.
Called from void_shipments when completed shipments exist — the carrier void
already happened via WyShipping.void_delivery, this just cleans up the
early label metadata so the order behaves like a regular order.
3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 |
# File 'app/models/delivery.rb', line 3523 def void_early_label_on_order_if_exists return unless resource.is_a?(Order) if resource.has_early_purchased_label? Rails.logger.info("[Delivery] Voiding early label metadata on order #{resource.reference_number} (shipments voided)") resource.void_early_label!(reason: 'Shipments voided on delivery', reset_flag: true) elsif resource.purchase_label_early? reset_early_label_flag_on_order end end |
#void_marketplace_labels ⇒ Object
Void marketplace labels (Walmart SWW, Amazon, etc.) for all shipments with labels
1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 |
# File 'app/models/delivery.rb', line 1210 def void_marketplace_labels shipments.label_complete.each do |shipment| next unless shipment.tracking_number.present? # Check if this is a marketplace label purchaser_class = Edi::MarketplaceLabelPurchaser.for_delivery(self) next unless purchaser_class begin purchaser = purchaser_class.new(shipment) if purchaser.respond_to?(:void_label) result = purchaser.void_label if result[:success] logger.info("[Delivery] Voided marketplace label for shipment #{shipment.id}") else logger.warn("[Delivery] Failed to void marketplace label for shipment #{shipment.id}: #{result[:error]}") # Continue anyway - the label might already be voided or the API might be unavailable end end rescue StandardError => e logger.error("[Delivery] Error voiding marketplace label for shipment #{shipment.id}: #{e.}") # Continue anyway - don't block the cancel operation end end end |
#void_shipments ⇒ Hash{Symbol => Object}
Voids all completed Shipments on the delivery: walks the
carrier-void path through WyShipping, falls back to manual void
emails for Canadapost/Purolator, clears master tracking/BOL/cost,
transitions back to pending_ship_labels and cleans up any
early-purchased label on the parent Order.
3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 |
# File 'app/models/delivery.rb', line 3438 def void_shipments # puts "!!void_shipments" if shipments.completed.any? && !is_part_of_transmitted_manifest? # Voiding after the Spee-Dee close cancels the Dispatch Science order and # keeps the parcel on the dock, so it must come off the printed sheet too # — the manifest's whole job is to match the dashboard and the truck. SpeedeeManifest.drop_voided_shipments!(shipments) shipments.manually_complete.each(&:manually_voided!) shipping_result = {} shipping_result[:status_code] = :ok if shipments.label_complete.any? tracking_numbers = shipments.label_complete.pluck(:tracking_number) shipping_result = WyShipping.void_delivery(self) append_to_shipping_api_log!(kind: 'void', shipping_result: shipping_result) # going to go merrily along but send admin notification if this didn't properly void # puts "shipping_result: #{shipping_result}" if shipping_result[:status_code] != :ok msg = %( delivery#void_shipments for delivery #{id} returned error shipping_result[:status_code]: #{shipping_result[:status_code]} ) ErrorReporting.error(msg, delivery_id: id, shipping_result:, tracking_numbers: shipments.label_complete.map(&:tracking_number)) Rails.logger.error msg send_canada_post_manual_void_email(tracking_numbers) if carrier == 'Canadapost' send_purolator_manual_void_email(tracking_numbers) if carrier == 'Purolator' elsif carrier == 'Freightquote' && freight_order_number.present? # CHR's DELETE returns an async requestId rather than a # synchronous cancel confirmation. Schedule a follow-up that # alerts ops if no LOAD CANCELLED / ORDER CANCELED event has # arrived within 1 hour. Non-blocking — same notify-and-move-on # pattern as the Canadapost / Purolator manual void emails # above. FreightquoteVoidConfirmationWorker.perform_in( 1.hour, id, freight_order_number, Time.current.iso8601 ) end shipments.label_complete.each(&:label_voided!) # Clear carrier-assigned fields so the re-label form starts clean. # freight_order_number is included so a re-label after voiding a Spee-Dee # (Dispatch Science) order creates a FRESH order rather than resuming the # cancelled one (see Delivery::GenerateLabels Spee-Dee resume guard). The # Freightquote void-confirmation worker already received it as an arg # above, so nil-ing the column here is safe for that carrier too. update_columns(master_tracking_number: nil, carrier_bol: nil, actual_shipping_cost: nil, shipengine_label_id: nil, freight_order_number: nil, confirmed_pickup_date: nil, confirmed_pickup_window_start_at: nil, confirmed_pickup_window_end_at: nil) reload.cancel_shipments! unless pending_ship_labels? end # Void the early-purchased label metadata on the order (if any) so the # picker no longer shows the early label banner and the order behaves # like a regular order going forward. Also resets purchase_label_early. void_early_label_on_order_if_exists { status_code: shipping_result[:status_code], status_message: "Please ensure you manually void manual shipments. Shipments voided. #{shipping_result[:status_message]}" } elsif void_early_label_on_order # No completed shipments yet, but there's an early label that hasn't been transferred - void it { status_code: :ok, status_message: 'Early-purchased shipping label has been voided.' } else { status_code: :error, status_message: "Can't void shipments because the delivery has no completed shipments OR delivery has been added to a carrier-transmitted manifest." } end end |