Class: Delivery
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Delivery
- Includes:
- Memery, Models::Auditable, Models::LegacyRateRequest, Models::Md5Hashable, 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)
Defined Under Namespace
Classes: InvoicingHandler
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 Schedulable
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.
-
#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.
Attributes included from Models::Profitable
Belongs to collapse
- #destination_address ⇒ Address
- #order ⇒ Order
- #origin_address ⇒ Address
- #prepack_requester ⇒ Party
- #quote ⇒ Quote
- #selected_shipping_cost ⇒ ShippingCost
- #shipping_account_number ⇒ ShippingAccountNumber
- #shipping_option ⇒ ShippingOption
- #supplier ⇒ Supplier
Methods included from Models::Auditable
Has one collapse
Has many collapse
- #activities ⇒ ActiveRecord::Relation<Activity>
- #discounts ⇒ ActiveRecord::Relation<Discount>
- #drop_ship_purchase_orders ⇒ ActiveRecord::Relation<PurchaseOrder>
-
#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>
- #item_ledger_entries ⇒ ActiveRecord::Relation<ItemLedgerEntry>
- #ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
- #line_discounts ⇒ ActiveRecord::Relation<LineDiscount>
- #line_items ⇒ ActiveRecord::Relation<LineItem>
- #messaging_logs ⇒ ActiveRecord::Relation<MessagingLog>
- #preset_jobs ⇒ ActiveRecord::Relation<PresetJob>
- #reserved_serial_numbers ⇒ ActiveRecord::Relation<ReservedSerialNumber>
- #shipments ⇒ ActiveRecord::Relation<Shipment>
-
#shipping_costs ⇒ ActiveRecord::Relation<ShippingCost>
dependent destroy handled by trigger.
- #uploads ⇒ ActiveRecord::Relation<Upload>
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
-
.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
- .invoice_shipped_deliveries(_logger = Rails.logger) ⇒ Object
-
.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.
-
.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.
-
.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
-
.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
-
#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
-
#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
-
#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_dropship_items_fulfilled? ⇒ Boolean
-
#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
- #all_lines_allocated_to_shipments_and_shipments_have_weight? ⇒ Boolean
-
#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) ⇒ Object
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 Ensures consistency whether selection is auto-computed or explicitly chosen elsewhere.
-
#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
-
#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 — addresses, country, package dimensions/weights, residential flag, COD/insurance values, LTL/freight defaults, RMA carrier restrictions, etc. - #can_be_deleted? ⇒ Boolean
- #can_print_carton_labels? ⇒ Boolean
- #can_update_tracking_info? ⇒ Boolean
- #can_void_rma_delivery? ⇒ Boolean
-
#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). -
#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
-
#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?
-
#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
- #do_not_validate_line_items? ⇒ Boolean
- #does_not_require_shipping_labeling? ⇒ Boolean
-
#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
-
#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
-
#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 ⇒ void
Combines every per-shipment label PDF, the serial-numbers PDF, any master-carton labels, and any return-RMA labels into batched
all_labels_pdfUploads. - #generate_asynch_labels ⇒ Object
-
#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
this must be here, even if just as a wrapper because the method above calls it!.
-
#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 ⇒ Object
res = uploads << upload end res 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. -
#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
- #has_custom_shipping_labels? ⇒ Boolean
-
#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
- #has_future_release_date? ⇒ Boolean
- #has_kits? ⇒ Boolean
- #has_kits_or_serial_numbers? ⇒ Boolean
- #has_not_changed_but_has_shipping_lines? ⇒ Boolean
- #has_ready_to_print_amazon_fba_items? ⇒ Boolean
- #has_serial_numbers? ⇒ Boolean
-
#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
- #has_unfulfilled_dropship_items? ⇒ Boolean
- #has_unprocessed_dropship_items? ⇒ Boolean
- #has_valid_shipments? ⇒ Boolean
- #incurs_oversized_penalty? ⇒ Boolean
- #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
- #is_cross_border? ⇒ Boolean
- #is_default_ltl_freight? ⇒ Boolean
- #is_domestic? ⇒ Boolean
- #is_international? ⇒ Boolean
- #is_international_and_ups_or_fedex? ⇒ Boolean
- #is_part_of_manifest? ⇒ Boolean
-
#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
-
#is_sww_shipping_cost?(sc = nil) ⇒ Boolean
Check if the shipping cost is a Ship with Walmart rate.
- #is_www? ⇒ Boolean
- #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
- #ltl_freight_has_changed? ⇒ Boolean
- #manual_pickup_contact ⇒ Object
-
#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
-
#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
-
#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
-
#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. -
#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
- #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_manifest_completion? ⇒ Boolean
- #requires_manual_pickup? ⇒ Boolean
-
#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
rescue "n/a".
-
#retrieve_shipping_costs(rate_ship_date: nil) ⇒ Object
Retrieve shipping costs from carriers.
-
#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 (shipping_option,selected_shipping_cost,shipping_cost) 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
- #ship_labeled_via_heatwave_or_manual_and_ship_insuring? ⇒ Boolean
-
#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
-
#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
- #shipping? ⇒ Boolean
-
#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
-
#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
- #ships_economy_package? ⇒ Boolean
- #ships_ltl_freight? ⇒ Boolean
- #should_have_electronic_commercial_invoice? ⇒ Boolean
- #should_print_heating_element_labels? ⇒ Boolean
- #should_send_commercial_invoice_to_carrier? ⇒ Boolean
- #should_ship_ltl_freight? ⇒ Boolean
- #show_packaging_on_pick_slip? ⇒ Boolean
-
#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
-
#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
- #valid_for_generating_ship_labels? ⇒ Boolean
- #valid_for_voiding_ship_labels? ⇒ Boolean
- #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::Md5Hashable
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 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.
92 93 94 |
# File 'app/models/delivery.rb', line 92 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.
92 93 94 |
# File 'app/models/delivery.rb', line 92 def force_shipping_cost_update @force_shipping_cost_update end |
#ltl_exclusion_reasons ⇒ Object
Returns the value of attribute ltl_exclusion_reasons.
92 93 94 |
# File 'app/models/delivery.rb', line 92 def ltl_exclusion_reasons @ltl_exclusion_reasons end |
#override_carrier ⇒ Object
Returns the value of attribute override_carrier.
92 93 94 |
# File 'app/models/delivery.rb', line 92 def override_carrier @override_carrier end |
#override_future_release_date ⇒ Object
Returns the value of attribute override_future_release_date.
92 93 94 |
# File 'app/models/delivery.rb', line 92 def override_future_release_date @override_future_release_date end |
#payment_ids ⇒ Object
Returns the value of attribute payment_ids.
92 93 94 |
# File 'app/models/delivery.rb', line 92 def payment_ids @payment_ids end |
Class Method Details
.active ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are active. Active Record Scope
356 |
# File 'app/models/delivery.rb', line 356 scope :active, -> { where.not(state: 'cancelled') } |
.all_at_warehouse ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are all at warehouse. Active Record Scope
345 |
# File 'app/models/delivery.rb', line 345 scope :all_at_warehouse, -> { where(state: WAREHOUSE_STATES) } |
.auto_ship_confirm(logger: nil) ⇒ Object
4530 4531 4532 4533 4534 4535 4536 4537 4538 |
# File 'app/models/delivery.rb', line 4530 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
362 |
# File 'app/models/delivery.rb', line 362 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
342 |
# File 'app/models/delivery.rb', line 342 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
343 |
# File 'app/models/delivery.rb', line 343 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
341 |
# File 'app/models/delivery.rb', line 341 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
357 |
# File 'app/models/delivery.rb', line 357 scope :cancelable, -> { where(state: CANCELABLE_STATES) } |
.dropship ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are dropship. Active Record Scope
361 |
# File 'app/models/delivery.rb', line 361 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
376 |
# File 'app/models/delivery.rb', line 376 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
375 |
# File 'app/models/delivery.rb', line 375 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
367 |
# File 'app/models/delivery.rb', line 367 scope :for_future_release, -> { where(state: 'future_release') } |
.generate_super_pick_slip_pdf(deliveries, split_kits = false) ⇒ Object
1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 |
# File 'app/models/delivery.rb', line 1341 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
4520 4521 4522 4523 4524 4525 4526 4527 4528 |
# File 'app/models/delivery.rb', line 4520 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
354 |
# File 'app/models/delivery.rb', line 354 scope :invoiced, -> { where(state: 'invoiced') } |
.limit_to_fba ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are limit to fba. Active Record Scope
366 |
# File 'app/models/delivery.rb', line 366 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') } |
.non_pickups ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are non pickups. Active Record Scope
365 |
# File 'app/models/delivery.rb', line 365 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
352 |
# File 'app/models/delivery.rb', line 352 scope :non_quoting, -> { where.not(state: 'quoting') } |
.not_cancelable ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are not cancelable. Active Record Scope
358 |
# File 'app/models/delivery.rb', line 358 scope :not_cancelable, -> { where.not(state: CANCELABLE_STATES) } |
.pending_manifest_completion ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are pending manifest completion. Active Record Scope
377 |
# File 'app/models/delivery.rb', line 377 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
368 |
# File 'app/models/delivery.rb', line 368 scope :pending_ship_confirm, -> { where(state: 'pending_ship_confirm') } |
.pickups ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are pickups. Active Record Scope
364 |
# File 'app/models/delivery.rb', line 364 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
363 |
# File 'app/models/delivery.rb', line 363 scope :processing_po_fulfillment, -> { where(state: 'processing_po_fulfillment') } |
.quoting ⇒ ActiveRecord::Relation<Delivery>
A relation of Deliveries that are quoting. Active Record Scope
350 |
# File 'app/models/delivery.rb', line 350 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
351 |
# File 'app/models/delivery.rb', line 351 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.
895 896 897 898 899 900 901 902 903 904 |
# File 'app/models/delivery.rb', line 895 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
359 |
# File 'app/models/delivery.rb', line 359 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.
910 911 912 913 914 915 916 917 918 919 920 |
# File 'app/models/delivery.rb', line 910 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
378 |
# File 'app/models/delivery.rb', line 378 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
353 |
# File 'app/models/delivery.rb', line 353 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
360 |
# File 'app/models/delivery.rb', line 360 scope :shipping, -> { where(state: SHIPPING_STATES) } |
.states_for_select ⇒ Object
4516 4517 4518 |
# File 'app/models/delivery.rb', line 4516 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
346 347 348 349 |
# File 'app/models/delivery.rb', line 346 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
370 371 372 373 374 |
# File 'app/models/delivery.rb', line 370 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
344 |
# File 'app/models/delivery.rb', line 344 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
355 |
# File 'app/models/delivery.rb', line 355 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
369 |
# File 'app/models/delivery.rb', line 369 scope :with_shipment_carrier, ->(carrier) { joins(:shipments).where(shipments: { carrier: }) } |
Instance Method Details
#activities ⇒ ActiveRecord::Relation<Activity>
122 |
# File 'app/models/delivery.rb', line 122 has_many :activities, as: :resource, dependent: :nullify |
#actual_shipping_cost_exceeds_threshold? ⇒ Boolean
4633 4634 4635 4636 4637 4638 |
# File 'app/models/delivery.rb', line 4633 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.
4621 4622 4623 4624 4625 |
# File 'app/models/delivery.rb', line 4621 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
4627 4628 4629 4630 4631 |
# File 'app/models/delivery.rb', line 4627 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.
4645 4646 4647 4648 4649 4650 4651 4652 4653 |
# File 'app/models/delivery.rb', line 4645 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
4656 4657 4658 4659 4660 4661 4662 4663 |
# File 'app/models/delivery.rb', line 4656 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.
3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 |
# File 'app/models/delivery.rb', line 3232 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.
3213 3214 3215 3216 3217 3218 3219 3220 |
# File 'app/models/delivery.rb', line 3213 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).
4245 4246 4247 4248 4249 4250 4251 |
# File 'app/models/delivery.rb', line 4245 def adjusted_actual_shipping_cost if third_party_billed? BigDecimal(0) else actual_shipping_cost end end |
#all_activities ⇒ ActiveRecord::Relation<Activity>
800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 |
# File 'app/models/delivery.rb', line 800 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_dropship_items_fulfilled? ⇒ Boolean
3117 3118 3119 |
# File 'app/models/delivery.rb', line 3117 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).
4029 4030 4031 |
# File 'app/models/delivery.rb', line 4029 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.
3994 3995 3996 |
# File 'app/models/delivery.rb', line 3994 def all_labels_pdf uploads.order(:id).reverse_order.find_by(category: 'all_labels_pdf') end |
#all_lines_allocated_to_shipments? ⇒ Boolean
977 978 979 |
# File 'app/models/delivery.rb', line 977 def all_lines_allocated_to_shipments? line_allocation_status_hash.values.all?(&:zero?) end |
#all_lines_allocated_to_shipments_and_shipments_have_weight? ⇒ Boolean
981 982 983 984 985 986 987 988 989 990 991 992 993 |
# File 'app/models/delivery.rb', line 981 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_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).
4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 |
# File 'app/models/delivery.rb', line 4805 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) ⇒ Object
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.
3941 3942 3943 3944 3945 3946 3947 |
# File 'app/models/delivery.rb', line 3941 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.
3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 |
# File 'app/models/delivery.rb', line 3900 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.
2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 |
# File 'app/models/delivery.rb', line 2008 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 if order&.customer_qualifies_for_free_online_shipping? # if order had free shipping online coupon, remove it now free_online_shipping_discount = order.discounts.free_online_shipping.first Coupon::DeleteDiscount.new.perform(free_online_shipping_discount, { skip_lock_check: true }) end 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
Ensures consistency whether selection is auto-computed or explicitly chosen elsewhere
2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 |
# File 'app/models/delivery.rb', line 2986 def apply_selected_shipping_cost!(shipping_cost_entry, previous_selected_id: nil, persist: false) unless shipping_cost_entry.shipping_option.item raise "Cannot create shipping line item as item is missing from shipping_option ID: #{begin shipping_cost_entry.shipping_option_id rescue StandardError nil end}" end shipping_line = line_items.detect(&:is_shipping?) shipping_line ||= line_items.build(resource:, tax_class: 'shp') shipping_line.quantity = is_rma_return? ? -1 : 1 shipping_line.item = shipping_cost_entry.shipping_option.item shipping_line.price = shipping_cost_entry.calculated_cost # Clear old line_discounts when shipping changes - reset_discount will recalculate them if previous_selected_id && previous_selected_id != shipping_cost_entry.id if persist shipping_line.line_discounts.destroy_all else shipping_line.line_discounts.each(&:mark_for_destruction) end end shipping_line.discounted_price = shipping_cost_entry.calculated_cost shipping_line.shipping_cost = shipping_cost_entry self.selected_shipping_cost_id = shipping_cost_entry.id shipping_line.description = retrieve_shipping_description_for_shipping_cost(shipping_cost_entry) self.shipping_cost = shipping_cost_entry.calculated_cost carrier = shipping_cost_entry&.shipping_option&.carrier shipments.each do |s| Rails.logger.debug { "apply_selected_shipping_cost!, before: carrier: #{carrier}, s.carrier: #{s.carrier}" } s.update(carrier:) Rails.logger.debug { "apply_selected_shipping_cost!, after: carrier: #{carrier}, s.carrier: #{s.carrier}" } end self.do_not_recalculate = true if persist # Persist delivery columns without invoking callbacks update_columns( shipping_option_id: shipping_cost_entry.shipping_option_id, shipping_cost: shipping_cost_entry.calculated_cost, selected_shipping_cost_id: shipping_cost_entry.id, updated_at: Time.current ) # Persist shipping line shipping_line.save! # Remove any extra shipping lines now that the current one is saved extra_shipping_lines = line_items.select(&:is_shipping?) - [shipping_line] extra_shipping_lines.each(&:destroy) # Ensure itemizable knows about the shipping line resource.sync_shipping_line if resource.respond_to?(:sync_shipping_line) else extra_shipping_lines = line_items.select(&:is_shipping?) - [shipping_line] extra_shipping_lines.each(&:destroy) end resource.do_not_set_totals = nil if resource.present? if resource && !resource.editing_locked? resource.force_total_reset = true end Rails.logger.debug { "apply_selected_shipping_cost!, after: shipping_line: #{shipping_line.inspect}" } 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.
2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 |
# File 'app/models/delivery.rb', line 2029 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 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 = (order.shipping_cost_at_time_of_checkout || order.shipping_cost) - order.shipping_cost # order.shipping_cost_at_time_of_checkout should NEVER be nil, but hey it happens, very rarely, for no explicable reason, the fix is to set it to order.shipping_cost return unless amount.abs > 0.0 # only proceed if we have an order balance 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 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
2602 2603 2604 2605 |
# File 'app/models/delivery.rb', line 2602 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.
3440 3441 3442 3443 3444 3445 3446 3447 |
# File 'app/models/delivery.rb', line 3440 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).
965 966 967 |
# File 'app/models/delivery.rb', line 965 def Packing.where(delivery_id: id, origin: %i[from_delivery from_manual_entry]).exists? end |
#billing_entity ⇒ Object
Alias for Resource#billing_entity
175 |
# File 'app/models/delivery.rb', line 175 delegate :billing_entity, to: :resource |
#bol_pdf ⇒ Upload?
Latest bill-of-lading Upload attached to the delivery.
4001 4002 4003 |
# File 'app/models/delivery.rb', line 4001 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.
3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 |
# File 'app/models/delivery.rb', line 3258 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.
3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 |
# File 'app/models/delivery.rb', line 3178 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.
848 849 850 |
# File 'app/models/delivery.rb', line 848 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.
4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 |
# File 'app/models/delivery.rb', line 4337 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).
4328 4329 4330 |
# File 'app/models/delivery.rb', line 4328 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 — addresses, country, package dimensions/weights, residential
flag, COD/insurance values, LTL/freight defaults, RMA carrier
restrictions, etc. Pure construction; does not call carriers itself.
1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 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 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 |
# File 'app/models/delivery.rb', line 1911 def ( = {}) # TBD REFACTOR NO ORDER DEPENDENCY FOR RETURN DELIVERY [:resource_shipping_method] = (resource&.try(:edi_shipping_option_name) || resource_shipping_method) if resource&.try(:edi_shipping_option_name) [:force_shipping_carrier] = (ShippingOption.active.where(name: resource.try(:edi_shipping_option_name))&.last&.carrier || ShippingOption.where(name: resource.try(:edi_shipping_option_name))&.last&.carrier) end [:origin_address] = origin_address [:origin_country_iso3] = origin_address.country.iso3 if destination_address.present? [:city] = destination_address.city [:state] = destination_address.state_code [:country_iso] = destination_address.country.iso [:postal_code] = destination_address.zip_compact.to_s.upcase.strip.squeeze(' ') [:is_residential] = destination_address.is_residential [:destination_address] = destination_address elsif resource.installation_postal_code.present? [:state] = resource.installation_state_code [:country_iso] = resource.installation_country_iso [:postal_code] = resource.installation_postal_code end # As far as i can tell this is useless when we use a scope to load them # options[:line_items] = line_items.select { |li| !li.destroyed? && !li.marked_for_destruction? && !li.is_shipping? && !li.is_service? } # This skips deleted, shipping and service line items [:store] = customer.store [:saturday_delivery] = saturday_delivery [:signature_confirmation] = signature_confirmation.to_b [:myprojects_legacy] = false if [:country_iso] == 'CA' # kludge [:ltl_freight] = is_default_ltl_freight? || ltl_freight.to_b || ltl_freight_guaranteed.to_b # puts "!!!!!!!!options[:ltl_freight]: #{options[:ltl_freight]}, self.is_default_ltl_freight?: #{self.is_default_ltl_freight?}, self.ltl_freight: #{self.ltl_freight}, self.ltl_freight_guaranteed: #{self.ltl_freight_guaranteed}" else [:ltl_freight] = ltl_freight.to_b [:ltl_freight_guaranteed] = ltl_freight_guaranteed.to_b end [:insured_value] = calculate_declared_value use_shipments = Shipping::CreateSuggestedShipment.new.process(self) .merge!(shipments_to_packages_hash(use_shipments.select { |s| s.parent_shipment_id.nil? })) cod_amount = 0.0 cod_amount = resource&.total if cod_collection_type && resource [:cod_collection_type] = cod_collection_type [:cod_amount] = cod_amount # Limit carriers for RMA returns to carriers assigned for RMAs in the country if is_rma_return? rma_country = rma_for_return&.customer&.store&.country&.iso if rma_country.present? rma_sos = ShippingOption.active.for_rmas .where(country: rma_country) .where.not(carrier: nil) [:allowed_carriers] = rma_sos.distinct.pluck(:carrier) [:limit_service_codes] = rma_sos.where.not(service_code: nil).pluck(:service_code) end end # options[:media_mail] = true if self.line_items.goods.all?{|li| li.is_publication?} # apparently only for educational materials, see:https://about.usps.com/notices/not121/not121_tech.htm # puts "calculate_shipping_options: #{options.inspect}" if resource&.try(:is_www_ship_by_zip) || (resource&.try(:in_shipping_estimate?) && resource&.try(:shipping_address_id).nil?) [:is_www_ship_by_zip] = true elsif resource&.try(:is_www) [:www] = true end end |
#can_be_deleted? ⇒ Boolean
761 762 763 |
# File 'app/models/delivery.rb', line 761 def can_be_deleted? quoting? || pre_pack? || picking? || at_warehouse? || (order && order.order_type == Order::CREDIT_ORDER) end |
#can_print_carton_labels? ⇒ Boolean
769 770 771 |
# File 'app/models/delivery.rb', line 769 def can_print_carton_labels? shipments.packed_or_measured.present? end |
#can_update_tracking_info? ⇒ Boolean
765 766 767 |
# File 'app/models/delivery.rb', line 765 def can_update_tracking_info? shipments.completed.present? end |
#can_void_rma_delivery? ⇒ Boolean
877 878 879 |
# File 'app/models/delivery.rb', line 877 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
2511 2512 2513 2514 2515 2516 2517 2518 |
# File 'app/models/delivery.rb', line 2511 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.
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 |
# File 'app/models/delivery.rb', line 1051 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.
4993 4994 4995 |
# File 'app/models/delivery.rb', line 4993 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
3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 |
# File 'app/models/delivery.rb', line 3076 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).
3470 3471 3472 |
# File 'app/models/delivery.rb', line 3470 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).
3426 3427 3428 3429 3430 3431 3432 3433 |
# File 'app/models/delivery.rb', line 3426 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.
4156 4157 4158 |
# File 'app/models/delivery.rb', line 4156 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.
973 974 975 |
# File 'app/models/delivery.rb', line 973 def Shipment.(self) end |
#catalog ⇒ Object
Alias for Customer#catalog
176 |
# File 'app/models/delivery.rb', line 176 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.
2259 2260 2261 2262 2263 |
# File 'app/models/delivery.rb', line 2259 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).
2229 2230 2231 2232 2233 |
# File 'app/models/delivery.rb', line 2229 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 |
#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.
4279 4280 4281 |
# File 'app/models/delivery.rb', line 4279 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.
4287 4288 4289 |
# File 'app/models/delivery.rb', line 4287 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.
4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 |
# File 'app/models/delivery.rb', line 4951 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
1228 1229 1230 |
# File 'app/models/delivery.rb', line 1228 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.
4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 |
# File 'app/models/delivery.rb', line 4571 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.
4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 |
# File 'app/models/delivery.rb', line 4354 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.
4499 4500 4501 4502 4503 4504 4505 |
# File 'app/models/delivery.rb', line 4499 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.
4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 |
# File 'app/models/delivery.rb', line 4870 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).
4487 4488 4489 4490 |
# File 'app/models/delivery.rb', line 4487 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.
4373 4374 4375 |
# File 'app/models/delivery.rb', line 4373 def currency order&.currency || resource&.currency || rma_for_return&.customer&.catalog&.currency end |
#currency_symbol ⇒ String
Glyph for #currency (e.g. "$", "€") for display formatting.
4380 4381 4382 |
# File 'app/models/delivery.rb', line 4380 def currency_symbol Money::Currency.new(currency).symbol end |
#custom_pack_list ⇒ Upload?
1311 1312 1313 1314 1315 1316 |
# File 'app/models/delivery.rb', line 1311 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
174 |
# File 'app/models/delivery.rb', line 174 delegate :customer, :primary_party, to: :resource_or_rma_for_delivery |
#default_container_type ⇒ String
Default packaging container for new Shipments on this delivery —
pallet for LTL freight, otherwise carton.
4938 4939 4940 4941 4942 |
# File 'app/models/delivery.rb', line 4938 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.
4735 4736 4737 |
# File 'app/models/delivery.rb', line 4735 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.
1008 1009 1010 |
# File 'app/models/delivery.rb', line 1008 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
Validations (unless => #skip_destination_address_validation? ):
104 |
# File 'app/models/delivery.rb', line 104 belongs_to :destination_address, class_name: 'Address', validate: true, optional: true |
#discounts ⇒ ActiveRecord::Relation<Discount>
115 |
# File 'app/models/delivery.rb', line 115 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.
2399 2400 2401 |
# File 'app/models/delivery.rb', line 2399 def display_carrier_name chosen_shipping_method&.rate_data&.[]('carrier_name') || reported_carrier end |
#do_not_ship_insure_via_carrier? ⇒ Boolean
2243 2244 2245 2246 2247 2248 2249 2250 2251 |
# File 'app/models/delivery.rb', line 2243 def do_not_ship_insure_via_carrier? res = false 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
3449 3450 3451 |
# File 'app/models/delivery.rb', line 3449 def do_not_validate_line_items? do_not_validate_line_items end |
#does_not_require_shipping_labeling? ⇒ Boolean
5040 5041 5042 5043 |
# File 'app/models/delivery.rb', line 5040 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>
118 |
# File 'app/models/delivery.rb', line 118 has_many :drop_ship_purchase_orders, class_name: 'PurchaseOrder', foreign_key: 'drop_ship_delivery_id' |
#electronic_ship_ci_pdf ⇒ Upload?
Most recent carrier-electronic commercial-invoice Upload (for UPS
paperless invoicing and similar).
4017 4018 4019 |
# File 'app/models/delivery.rb', line 4017 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
754 755 756 757 758 759 |
# File 'app/models/delivery.rb', line 754 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.
4670 4671 4672 4673 4674 4675 4676 |
# File 'app/models/delivery.rb', line 4670 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
5045 5046 5047 |
# File 'app/models/delivery.rb', line 5045 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.
3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 |
# File 'app/models/delivery.rb', line 3541 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.
2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 |
# File 'app/models/delivery.rb', line 2211 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).
136 137 |
# File 'app/models/delivery.rb', line 136 has_many :freight_events, -> { order(emitted_at: :desc, event_time: :desc, id: :desc) }, inverse_of: :delivery, dependent: :nullify |
#freightquote_carrier? ⇒ Boolean
2392 2393 2394 |
# File 'app/models/delivery.rb', line 2392 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.
2322 2323 2324 2325 2326 2327 2328 |
# File 'app/models/delivery.rb', line 2322 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.
2374 2375 2376 |
# File 'app/models/delivery.rb', line 2374 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.
3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 |
# File 'app/models/delivery.rb', line 3623 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 steel_decl = Upload.where(category: 'declaration_of_steel_aluminimum_copper').order(id: :desc).first usmca_cert_uploads << steel_decl if steel_decl 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 # 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 ⇒ void
This method returns an undefined value.
Combines every per-shipment label PDF, the serial-numbers PDF, any
master-carton labels, and any return-RMA labels into batched
all_labels_pdf Uploads. Batched at 10 to stay within the temp-dir
FD ceiling.
3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 |
# File 'app/models/delivery.rb', line 3579 def generate_all_labels_pdf all_shipments = shipments.completed.order(:id).includes(:uploads) # 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_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
3984 3985 3986 3987 3988 3989 |
# File 'app/models/delivery.rb', line 3984 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.
1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 |
# File 'app/models/delivery.rb', line 1376 def require 'barby' require 'barby/barcode/code_128' require 'barby/outputter/png_outputter' = Barby::Code128B.new(order.reference_number.to_s) 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_png(height: 70) 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.
1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 |
# File 'app/models/delivery.rb', line 1398 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.
1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 |
# File 'app/models/delivery.rb', line 1441 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
this must be here, even if just as a wrapper because the method above calls it!
1297 1298 1299 1300 1301 1302 1303 1304 |
# File 'app/models/delivery.rb', line 1297 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_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.
3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 |
# File 'app/models/delivery.rb', line 3136 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 ⇒ Object
res = uploads << upload
end
res
end
3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 |
# File 'app/models/delivery.rb', line 3676 def generate_labels if pending_ship_labels? && (incurs_oversized_penalty? != true) require 'wy_shipping' shipping_result = WyShipping.ship_delivery(self) Rails.logger.info("#{Time.current}: Order ID #{order&.id}, reference number: #{order&.reference_number}, RMA: #{rma_for_return&.rma_number}, labels generated START LOG $$$$$$$$$$$$$$$$$$$$$$$$$$$$, status_code: #{shipping_result[:status_code]}") append_to_shipping_api_log!(kind: 'label', shipping_result: shipping_result) if shipping_result[:status_code] == :error Rails.logger.info("#{carrier}, request_xml:") if carrier == 'LegacyUPS' keys_of_interest = %i[confirm_request_xml confirm_response_xml accept_request_xml accept_response_xml] keys_of_interest.each do |key| Rails.logger.info("#{key}:") Rails.logger.info(shipping_result.dig(:shipment, key)) end else # self.carrier == "FedEx" || self.carrier == "UPSFreight" || self.carrier == "Purolator" keys_of_interest = %i[ship_request_xml ship_reply_xml] keys_of_interest.each do |key| Rails.logger.info("#{key}:") Rails.logger.info(shipping_result.dig(:shipment, key)) end end end send_address_type_issue_notification if shipping_result[:flag_address_type_issue] # see if labels generated if shipping_result[:status_code] == :error { status_code: :error, status_message: shipping_result[:status_message] } else # only save if labels successfully generated # first pass get tracking numbers and charges Rails.logger.debug("Shipping result received", delivery_id: id) self.master_tracking_number = shipping_result[:shipment][:shipment_identification_number] # ShipEngine LTL carriers split into auto-PRO (SAIA, FedEx Freight, ABF…) # and async-PRO (Roadrunner, R+L Carriers, XPO/Con-way) per ShipEngine's # supported-carriers table. Auto-PRO carriers return pro_number in # book_pickup → pre-populate ltl_pro_number so the `:shipped` transition # guard at delivery.rb:364 doesn't trip. Async-PRO carriers return nil # — warehouse staff enters the PRO from the driver's sticker at pickup, # same fallback as the FreightQuote e_bol_enabled=false branch in # GetFreightquoteLoadNumber. if ShippingOption::SHIPENGINE_LTL_CARRIERS.include?(carrier) && shipping_result[:shipment][:shipment_identification_number].present? self.ltl_pro_number = shipping_result[:shipment][:shipment_identification_number] end self.carrier_bol = shipping_result[:shipment][:carrier_bol] self.pickup_confirmation_number = shipping_result[:shipment][:pickup_confirmation_number] self.shipengine_label_id = shipping_result[:shipment][:shipengine_label_id] self.freight_order_number = shipping_result[:shipment][:freight_order_number] self.actual_shipping_cost = shipping_result[:shipment][:total_charges] self.confirmed_pickup_date = shipping_result[:shipment][:confirmed_pickup_date] self.confirmed_pickup_window_start_at = shipping_result[:shipment][:confirmed_pickup_window_start_at] self.confirmed_pickup_window_end_at = shipping_result[:shipment][:confirmed_pickup_window_end_at] Rails.logger.info("master_tracking_number: #{master_tracking_number}, actual_shipping_cost: #{actual_shipping_cost}") # puts "shipping_result[:shipment][:labels].length: #{shipping_result[:shipment][:labels].length}" # puts "shipping_result[:shipment][:labels]: #{shipping_result[:shipment][:labels].inspect}" save # pure hacking to get this to work reload # pure hacking to get this to work shipments.awaiting_label.order('shipments.id ASC').each_with_index do |shipment, j| # Single-document LTL carriers return one BOL covering every pallet, # so every shipment row maps to labels[0]. Everyone else gets a # per-package label at labels[j]. Without ShipEngine's LTL family # here, the loop would mark only the first pallet label_complete # and leave the others stuck in awaiting_label. i = (%w[UPSFreight Freightquote RlCarriers Saia YrcFreight] + ShippingOption::SHIPENGINE_LTL_CARRIERS).include?(carrier) ? 0 : j Rails.logger.debug { "shipment: #{shipment.inspect}" } next unless label = shipping_result.dig(:shipment, :labels, i) Rails.logger.debug { "shipping_result[:shipment][:labels][i]: #{label&.inspect}" } Rails.logger.debug { "shipping_result[:shipment][:labels][i][:tracking_number]: #{label[:tracking_number]}" } Rails.logger.debug { "shipping_result[:shipment][:labels][i][:shipengine_label_id]: #{label[:shipengine_label_id]}" } shipment.tracking_number = label[:tracking_number] shipment.shipengine_label_id = label[:shipengine_label_id] shipment.label_generated! end Rails.logger.info("#{Time.current}: Order ID #{order&.id}, reference number: #{order&.reference_number}, RMA: #{rma_for_return&.rma_number}, labels generated END LOG $$$$$$$$$$$$$$$$$$$$$$$$$$$$") # save this delivery save # second pass generate the labels from the temporary paths label_exceptions = [] bol_exceptions = [] status_code = :ok shipments.label_complete.order(:id).each_with_index do |shipment, j| # UPS Freight, Freightquote, ShipEngine LTL, etc. only have a single # label / BOL for the whole load — same single-document-LTL set as # the state-transition loop above. i = (%w[UPSFreight Freightquote RlCarriers Saia YrcFreight] + ShippingOption::SHIPENGINE_LTL_CARRIERS).include?(carrier) ? 0 : j old_label_path = (begin shipping_result[:shipment][:labels][i][:image].path rescue StandardError nil end) # skip when label path is nil if old_label_path label_path = old_label_path if shipment.generate_ship_label_pdf(label_path, carrier) # generate letter shipping label pdf for e-mailing rma return if this is an rma return if is_rma_return? # Per-package paperless QR/barcode. For USPS Label Broker # (display_scheme: 'paperless') the QR IS the deliverable — # `label_download` is the QR page itself, NOT a separate # printable label — so attach the QR and do NOT rotate it # into a bogus letter-format "label". When the carrier didn't # issue paperless (nil), fall back to the printable # letter-format label. paperless_png = shipping_result.dig(:shipment, :labels, i, :paperless_png) if paperless_png shipment.attach_paperless_qr( paperless_png.path, shipping_result.dig(:shipment, :labels, i, :paperless_instructions), shipping_result.dig(:shipment, :labels, i, :paperless_handoff_code) ) elsif %w[UPS FedEx USPS].include?(carrier) # Shipengine will have the PNG files for each label so use those for image processing rather than the PDF # rotate label 90 left and generate letter ship label label_png_path = shipping_result.dig(:shipment, :labels, i, :png).path new_label_png_path = "#{label_png_path}_rot.png" require 'vips' image = Vips::Image.new_from_file(label_png_path) image = image.rot(:d270) # Rotate 90 degrees counter-clockwise (same as -90) image.write_to_file(new_label_png_path) # Generate letter ship label PDF using the converted image shipment.generate_letter_ship_label_pdf(new_label_png_path, carrier) elsif %w[Canadapost Canpar].include?(carrier) # Canadapost and Canpar will have the PDF files for each label so use those for image processing rather than the PNG # Generate letter ship label PDF using the converted image shipment.generate_letter_ship_label_pdf(nil, carrier) end end else label_exceptions << (i + 1) end end if carrier == 'SpeedeeDelivery' if shipment.generate_speedee_label_pdf(shipping_result[:shipment][:rate_data]) shipment.generate_letter_ship_label_pdf(nil, carrier) if is_rma_return? else label_exceptions << (i + 1) end end end # Shipment-level paperless: the carrier issued a single QR/barcode # covering the whole MPS (typical for USPS Label Broker when one # ShipEngine call generated multiple per-package labels in one # response). Attach it to the first return shipment; the PDF # renderer emits one QR page up front when it finds this layout. if is_rma_return? && (sp = shipping_result.dig(:shipment, :shipment_paperless)) && sp[:png] first_return_shipment = shipments.label_complete.order(:id).first first_return_shipment&.attach_paperless_qr( sp[:png].path, sp[:instructions], sp[:handoff_code] ) end # generate delivery RMA return instructions and labels here if ships_ltl_freight? || carrier == 'Freightquote' ship_bol_pdf_path = (begin shipping_result[:shipment][:labels].first[:bol_image].path rescue StandardError nil end) logger.debug("ship_bol_pdf_path: #{ship_bol_pdf_path}") unless carrier == 'Freightquote' # this will be done asynchronously when we get the load number if generate_bol_pdf(ship_bol_pdf_path) else bol_exceptions << (i + 1) end end end ci_tmp_path = shipping_result.dig(:shipment)&.dig(:ci)&.dig(:image)&.path # Generated by carrier in the case of electronic customs documents if ci_tmp_path || is_international? # check if we need to generate commercial invoice generate_ci_pdf(ci_tmp_path) unless carrier == 'Freightquote' # for Freightquote, we generate this asynchronously after we get the load number end if label_exceptions.empty? && bol_exceptions.empty? && status_code == :ok begin generate_all_labels_pdf unless carrier == 'Freightquote' # this will be done asynchronously when we get the load number = "Labels generated for #{shipments.completed.length} packages. Estimated charge: #{currency_symbol}#{shipping_cost}. Actual charge: #{currency_symbol}#{actual_shipping_cost}. #{}" rescue StandardError => e ErrorReporting.error e = "Labels generated but combined PDF failed: #{e.}. Individual labels are still available." Rails.logger.error("generate_all_labels_pdf failed for delivery #{id}: #{e.class} - #{e.}") end # International forms backgrounded via worker (not needed for label printing) if is_international? && carrier != 'Freightquote' DeliveryPostLabelWorker.perform_async(id) end begin ship_labeled # implicit save! rescue RuntimeError => e return { status_code: :error, status_message: e. } end { status_code:, status_message: } else msg = String.new("Can't generate labels") msg += ", there was an issue with labels #{label_exceptions.map { |l| l + 1 }.join(', ')}" if label_exceptions.any? msg += ", there was an issue with bols #{bol_exceptions.map { |l| l + 1 }.join(', ')}" if bol_exceptions.any? msg += ", status code returned: #{status_code}" unless status_code == :ok msg += '.' { status_code: :error, status_message: msg } end end else error_msg = if incurs_oversized_penalty? "Can't generate labels because the shipping packages exceed oversized limits or include a pallet and would incur big penalties, please review packaging and consider shipping via LTL freight." else "Can't generate labels because the delivery is in the state: #{state.humanize}." end { status_code: :error, status_message: error_msg } end 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.
1287 1288 1289 1290 1291 1292 1293 1294 1295 |
# File 'app/models/delivery.rb', line 1287 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.
1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 |
# File 'app/models/delivery.rb', line 1328 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.
2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 |
# File 'app/models/delivery.rb', line 2075 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.
5095 5096 5097 5098 5099 5100 |
# File 'app/models/delivery.rb', line 5095 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.
3168 3169 3170 |
# File 'app/models/delivery.rb', line 3168 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.
5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 |
# File 'app/models/delivery.rb', line 5109 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.
1221 1222 1223 1224 1225 1226 |
# File 'app/models/delivery.rb', line 1221 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>}
3200 3201 3202 |
# File 'app/models/delivery.rb', line 3200 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.
3158 3159 3160 3161 |
# File 'app/models/delivery.rb', line 3158 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
1429 1430 1431 |
# File 'app/models/delivery.rb', line 1429 def has_custom_products? line_items.joins(:item).where(items: { product_category_id: ProductCategory.custom_product_ids }).exists? end |
#has_custom_shipping_labels? ⇒ Boolean
773 774 775 |
# File 'app/models/delivery.rb', line 773 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.
3070 3071 3072 |
# File 'app/models/delivery.rb', line 3070 def has_destination_postal_code destination_address || resource.installation_postal_code.present? end |
#has_dropship_items? ⇒ Boolean
3121 3122 3123 |
# File 'app/models/delivery.rb', line 3121 def has_dropship_items? line_items.joins(:item).where(items: { dropship: true }).present? end |
#has_future_release_date? ⇒ Boolean
852 853 854 |
# File 'app/models/delivery.rb', line 852 def has_future_release_date? future_release_date && (future_release_date > Date.current) end |
#has_kits? ⇒ Boolean
869 870 871 |
# File 'app/models/delivery.rb', line 869 def has_kits? line_items_with_counters.any? { |li| li.children_count.to_i.positive? } end |
#has_kits_or_serial_numbers? ⇒ Boolean
873 874 875 |
# File 'app/models/delivery.rb', line 873 def has_kits_or_serial_numbers? has_serial_numbers? || has_kits? end |
#has_not_changed_but_has_shipping_lines? ⇒ Boolean
2607 2608 2609 |
# File 'app/models/delivery.rb', line 2607 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
4765 4766 4767 |
# File 'app/models/delivery.rb', line 4765 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
865 866 867 |
# File 'app/models/delivery.rb', line 865 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)
3462 3463 3464 3465 3466 |
# File 'app/models/delivery.rb', line 3462 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
2611 2612 2613 |
# File 'app/models/delivery.rb', line 2611 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
3113 3114 3115 |
# File 'app/models/delivery.rb', line 3113 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
3125 3126 3127 |
# File 'app/models/delivery.rb', line 3125 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
4160 4161 4162 |
# File 'app/models/delivery.rb', line 4160 def has_valid_shipments? shipments.packed_or_measured.present? && shipments.packed_or_measured.all?(&:has_dimensions?) end |
#incurs_oversized_penalty? ⇒ Boolean
4836 4837 4838 |
# File 'app/models/delivery.rb', line 4836 def incurs_oversized_penalty? (ships_ltl_freight? != true) && (is_warehouse_pickup? != true) && shipments.packed_or_measured.any?(&:incurs_oversized_penalty?) end |
#index ⇒ Integer
1501 1502 1503 |
# File 'app/models/delivery.rb', line 1501 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.
4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 |
# File 'app/models/delivery.rb', line 4546 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.
3054 3055 3056 3057 |
# File 'app/models/delivery.rb', line 3054 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).
5125 5126 5127 5128 5129 5130 5131 |
# File 'app/models/delivery.rb', line 5125 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.
885 886 887 |
# File 'app/models/delivery.rb', line 885 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).
2239 2240 2241 |
# File 'app/models/delivery.rb', line 2239 def insured_value chosen_shipping_method&.insured_value end |
#invoices ⇒ ActiveRecord::Relation<Invoice>
119 |
# File 'app/models/delivery.rb', line 119 has_many :invoices |
#is_amazon_seller_central? ⇒ Object
Alias for Customer#is_amazon_seller_central?
176 |
# File 'app/models/delivery.rb', line 176 delegate :catalog, :is_amazon_seller_central?, to: :customer |
#is_amazon_seller_central_veeqo? ⇒ Boolean
2520 2521 2522 |
# File 'app/models/delivery.rb', line 2520 def is_amazon_seller_central_veeqo? is_amazon_seller_central? && override_shipping_method? end |
#is_cross_border? ⇒ Boolean
4451 4452 4453 |
# File 'app/models/delivery.rb', line 4451 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
2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 |
# File 'app/models/delivery.rb', line 2524 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
4443 4444 4445 |
# File 'app/models/delivery.rb', line 4443 def is_domestic? origin_address && destination_address && origin_address.country_iso3 == destination_address.country_iso3 end |
#is_international? ⇒ Boolean
4447 4448 4449 |
# File 'app/models/delivery.rb', line 4447 def is_international? !is_domestic? end |
#is_international_and_ups_or_fedex? ⇒ Boolean
4455 4456 4457 |
# File 'app/models/delivery.rb', line 4455 def is_international_and_ups_or_fedex? is_international? && %w[UPS FedEx].include?(carrier) end |
#is_part_of_manifest? ⇒ Boolean
4164 4165 4166 |
# File 'app/models/delivery.rb', line 4164 def is_part_of_manifest? shipments.any?(&:manifest) || shipments.any?(&:speedee_manifest_shipment) 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).
2490 2491 2492 |
# File 'app/models/delivery.rb', line 2490 def is_rma_return rma_for_return.present? end |
#is_smart_service? ⇒ Boolean
2506 2507 2508 |
# File 'app/models/delivery.rb', line 2506 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
2441 2442 2443 2444 2445 2446 |
# File 'app/models/delivery.rb', line 2441 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
5065 5066 5067 |
# File 'app/models/delivery.rb', line 5065 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>
124 |
# File 'app/models/delivery.rb', line 124 has_many :item_ledger_entries |
#ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
123 |
# File 'app/models/delivery.rb', line 123 has_many :ledger_transactions |
#line_allocation_status_hash ⇒ Hash{Integer => Integer}
1025 1026 1027 1028 1029 1030 1031 |
# File 'app/models/delivery.rb', line 1025 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>
113 |
# File 'app/models/delivery.rb', line 113 has_many :line_discounts, through: :line_items |
#line_items ⇒ ActiveRecord::Relation<LineItem>
112 |
# File 'app/models/delivery.rb', line 112 has_many :line_items, extend: LineItemExtension, autosave: true, dependent: :nullify |
#line_items_eligible_for_packing ⇒ ActiveRecord::Relation<LineItem>
949 950 951 |
# File 'app/models/delivery.rb', line 949 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>
4319 4320 4321 |
# File 'app/models/delivery.rb', line 4319 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.
861 862 863 |
# File 'app/models/delivery.rb', line 861 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
1016 1017 1018 |
# File 'app/models/delivery.rb', line 1016 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.
4719 4720 4721 |
# File 'app/models/delivery.rb', line 4719 def link_serial_numbers_to_line_items line_items.each(&:link_serial_numbers) end |
#linked_return_deliveries ⇒ Array<Delivery>
3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 |
# File 'app/models/delivery.rb', line 3557 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
2583 2584 2585 |
# File 'app/models/delivery.rb', line 2583 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
2624 2625 2626 2627 |
# File 'app/models/delivery.rb', line 2624 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
2571 2572 2573 |
# File 'app/models/delivery.rb', line 2571 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.
4753 4754 4755 |
# File 'app/models/delivery.rb', line 4753 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
3243 3244 3245 3246 3247 |
# File 'app/models/delivery.rb', line 3243 def matches?(existing_item, item, quantity) existing_item.line_item.nil? && existing_item.item == item && existing_item.unit_quantity == quantity end |
#messaging_logs ⇒ ActiveRecord::Relation<MessagingLog>
114 |
# File 'app/models/delivery.rb', line 114 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.
1512 1513 1514 1515 1516 1517 1518 1519 1520 |
# File 'app/models/delivery.rb', line 1512 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).
3529 3530 3531 3532 3533 |
# File 'app/models/delivery.rb', line 3529 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.
4784 4785 4786 4787 4788 |
# File 'app/models/delivery.rb', line 4784 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.
932 933 934 935 936 937 938 939 940 941 942 |
# File 'app/models/delivery.rb', line 932 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.
831 832 833 |
# File 'app/models/delivery.rb', line 831 def open_activities_counter all_activities.open_activities.count end |
#order ⇒ Order
98 |
# File 'app/models/delivery.rb', line 98 belongs_to :order, inverse_of: :deliveries, optional: true |
#origin_address ⇒ Address
Validations:
103 |
# File 'app/models/delivery.rb', line 103 belongs_to :origin_address, class_name: 'Address', validate: true, optional: true |
#override_shipping_method? ⇒ Boolean
2378 2379 2380 |
# File 'app/models/delivery.rb', line 2378 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.
4172 4173 4174 |
# File 'app/models/delivery.rb', line 4172 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).
4182 4183 4184 4185 4186 |
# File 'app/models/delivery.rb', line 4182 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).
4192 4193 4194 |
# File 'app/models/delivery.rb', line 4192 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.
4201 4202 4203 |
# File 'app/models/delivery.rb', line 4201 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?
2482 2483 2484 |
# File 'app/models/delivery.rb', line 2482 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.
4794 4795 4796 4797 4798 |
# File 'app/models/delivery.rb', line 4794 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.
2502 2503 2504 |
# File 'app/models/delivery.rb', line 2502 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/.
1109 1110 1111 1112 1113 1114 1115 1116 |
# File 'app/models/delivery.rb', line 1109 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
1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 |
# File 'app/models/delivery.rb', line 1119 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.
3970 3971 3972 |
# File 'app/models/delivery.rb', line 3970 def pickup_alert_visible? confirmed_pickup_date.present? && %w[shipped invoiced cancelled].exclude?(state) end |
#po_number ⇒ Object
Alias for Order#po_number
178 |
# File 'app/models/delivery.rb', line 178 delegate :po_number, to: :order, allow_nil: true |
#precreated_rma ⇒ Rma
108 |
# File 'app/models/delivery.rb', line 108 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
1524 1525 1526 1527 1528 1529 1530 1531 |
# File 'app/models/delivery.rb', line 1524 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
138 |
# File 'app/models/delivery.rb', line 138 belongs_to :prepack_requester, class_name: 'Party', optional: true |
#preset_jobs ⇒ ActiveRecord::Relation<PresetJob>
120 |
# File 'app/models/delivery.rb', line 120 has_many :preset_jobs, inverse_of: :order |
#primary_party ⇒ Object
Alias for Resource_or_rma_for_delivery#primary_party
174 |
# File 'app/models/delivery.rb', line 174 delegate :customer, :primary_party, to: :resource_or_rma_for_delivery |
#print_container_label? ⇒ Boolean
725 726 727 728 729 730 731 732 733 734 735 736 737 738 |
# File 'app/models/delivery.rb', line 725 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).
3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 |
# File 'app/models/delivery.rb', line 3351 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.
3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 |
# File 'app/models/delivery.rb', line 3332 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 |
#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.
999 1000 1001 |
# File 'app/models/delivery.rb', line 999 def quantities_remaining_to_allocate line_allocation_status_hash.values.sum end |
#quote ⇒ Quote
97 |
# File 'app/models/delivery.rb', line 97 belongs_to :quote, inverse_of: :deliveries, optional: true |
#ready_to_choose_ships_economy_carrier? ⇒ Boolean
5082 5083 5084 |
# File 'app/models/delivery.rb', line 5082 def ready_to_choose_ships_economy_carrier? ships_economy_package? && pending_ship_labels? end |
#ready_to_print_amazon_fba_line_items ⇒ Array<LineItem>
4761 4762 4763 |
# File 'app/models/delivery.rb', line 4761 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.
1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 |
# File 'app/models/delivery.rb', line 1478 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.
1038 1039 1040 |
# File 'app/models/delivery.rb', line 1038 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.
4406 4407 4408 4409 4410 4411 4412 |
# File 'app/models/delivery.rb', line 4406 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.
4711 4712 4713 |
# File 'app/models/delivery.rb', line 4711 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.
2620 2621 2622 |
# File 'app/models/delivery.rb', line 2620 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.
4512 4513 4514 |
# File 'app/models/delivery.rb', line 4512 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.
4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 |
# File 'app/models/delivery.rb', line 4683 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).
2388 2389 2390 |
# File 'app/models/delivery.rb', line 2388 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).
2408 2409 2410 |
# File 'app/models/delivery.rb', line 2408 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_manifest_completion? ⇒ Boolean
4930 4931 4932 |
# File 'app/models/delivery.rb', line 4930 def requires_manifest_completion? CARRIERS_REQUIRING_MANIFEST_COMPLETION.include?(carrier) end |
#requires_manual_pickup? ⇒ Boolean
2567 2568 2569 |
# File 'app/models/delivery.rb', line 2567 def requires_manual_pickup? effective_shipping_option&.requires_manual_pickup? || false end |
#reserved_serial_numbers ⇒ ActiveRecord::Relation<ReservedSerialNumber>
121 |
# File 'app/models/delivery.rb', line 121 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
4128 4129 4130 4131 4132 4133 4134 |
# File 'app/models/delivery.rb', line 4128 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.
2270 2271 2272 2273 |
# File 'app/models/delivery.rb', line 2270 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.
5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 |
# File 'app/models/delivery.rb', line 5011 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.
782 783 784 |
# File 'app/models/delivery.rb', line 782 def resource order || quote end |
#resource_or_rma_for_delivery ⇒ Order, ...
791 792 793 |
# File 'app/models/delivery.rb', line 791 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).
840 841 842 |
# File 'app/models/delivery.rb', line 840 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.
5138 5139 5140 5141 5142 5143 5144 5145 |
# File 'app/models/delivery.rb', line 5138 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
rescue "n/a"
2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 |
# File 'app/models/delivery.rb', line 2330 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) ⇒ Object
Retrieve shipping costs from carriers
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 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 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 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 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 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 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 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 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 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 |
# File 'app/models/delivery.rb', line 1544 def retrieve_shipping_costs(rate_ship_date: nil) # Use advisory lock to prevent concurrent processing and infinite loops lock_key = "delivery|#{id}|retrieve_shipping_costs" result = self.class.with_advisory_lock_result(lock_key, timeout_seconds: 0) do # Store the rate_ship_date for use later in this method @rate_ship_date_override = rate_ship_date # puts "Delivery#retrieve_shipping_costs: self.resource.shipping_address_id: #{self.resource.shipping_address_id}" # puts "Delivery#retrieve_shipping_costs: self.destination_address.id: #{self.destination_address.id}, self.destination_address_id: #{self.destination_address_id}" return_code = :no_options = +'' = +'' = [] is_store_transfer_delivery = order.try(:is_store_transfer?) shipping_option if is_service_only? || is_warehouse_pickup? so = ShippingOption.where(name: 'override', country: resource.store.country.iso).first self.shipping_option_id = so.id old_override_shipping_cost = shipping_costs.detect { |sc| sc.shipping_option == so } cost_to_use = 0.0 if old_override_shipping_cost cost_to_use = old_override_shipping_cost.cost || 0.0 end # need something here cost_to_use = 0.0 if is_warehouse_pickup? cost_to_use = WAREHOUSE_PICKUP_FEE if apply_warehouse_fee? # we apply warehouse pickup fee only to first delivery clear_shipping_costs_safely shpcst = ShippingCost.new(shipping_option: so, cost: cost_to_use) shpcst.description_override = retrieve_shipping_description_for_shipping_cost(shpcst) shipping_costs << shpcst raise "Cannot create shipping line item as item is missing from shipping_option ID: #{so.id}" if so.item.nil? return_code = :ok elsif is_zero_charge_dropship? Rails.logger.debug 'is_zero_charge_dropship? true' so = ShippingOption.where(name: 'override', country: resource.store.country.iso).first self.shipping_option_id = so.id shipping_costs.detect { |sc| sc.shipping_option == so } cost_to_use = 0.0 clear_shipping_costs_safely shpcst = ShippingCost.new(shipping_option: so, cost: cost_to_use) shpcst.description_override = retrieve_shipping_description_for_shipping_cost(shpcst) shipping_costs << shpcst raise "Cannot create shipping line item as item is missing from shipping_option ID: #{so.id}" if so.item.nil? return_code = :ok # elsif is_rma_return? # # TBD REFACTOR NO ORDER DEPENDENCY FOR RETURN DELIVERY # # here we want just UPS ground shipping option with cost of 0.00 # if self.shipping_option_id.present? # so = self.shipping_option # else # country_iso = resource&.store&.country&.iso || rma_for_return&.customer&.store&.country&.iso # ups_ground = 'ground' # ups_ground = 'standard' if country_iso == 'CA' # so = ShippingOption.active.where(name: ups_ground, country: country_iso).first || ShippingOption.where(name: ups_ground, country: country_iso).first # self.shipping_option_id = so.id # end # cost_to_use = 0.0 # shipping_costs.delete_all # shpcst = ShippingCost.new(shipping_option: so, cost: cost_to_use) # shpcst.description_override = retrieve_shipping_description_for_shipping_cost(shpcst) # shipping_costs << shpcst # raise "Cannot create shipping line item as item is missing from shipping_option ID: #{so.id}" if so.item.nil? # return_code = :ok elsif customer && (destination_address || resource.installation_postal_code) # test if we want to default to ltl freight, here the ltl_freight.nil? is important because it means the user did not explcitly set it to false, so it's true no need to set it true, or it's false which means it was manually and explicitly set to false so we 'stick' with the selected shipping option even if the weight increases past the default LTL threshold Rails.logger.debug { "retrieve_shipping_costs: ltl_freight: #{ltl_freight}, ltl_freight_guaranteed: #{ltl_freight_guaranteed}, order&.cart?: #{order&.cart?}, is_default_ltl_freight?: #{is_default_ltl_freight?}" } if (ltl_freight.nil? || order&.cart?) && !ltl_freight_guaranteed && is_default_ltl_freight? # only do this for when no shipping method has been chosen, i.e. no shipping costs present or none matching or is cart and freight option not already set self.ltl_freight = true end if (ltl_freight || ltl_freight_guaranteed) && order&.cart? && !is_default_ltl_freight? # also reset ltl freight for www carts and when freight is no longer relevant self.ltl_freight = false self.ltl_freight_guaranteed = false end # Heavyweight method call below. if ltl_freight_changed? # Modality switch (parcel ↔ LTL): drop suggested/packed shipments so # CreateSuggestedShipment rebuilds them against the new modality. # The freight-mode Packing record (kept per service_type) survives # and is rehydrated on the way back. Without this, pallet dims leak # into parcel mode (and vice versa) because CreateSuggestedShipment # only deletes suggested shipments with blank container_code and # returns any remaining non-voided shipments as-is. shipments.where(state: %w[suggested packed]).destroy_all # we reset selected shipping options because this is like a new shipping calculation and we want sensible defaults, not override update_column(:selected_shipping_cost_id, nil) update_column(:shipping_option_id, nil) end = (www: order.try(:is_www)) packages_arr = [] [:shipping_weights].each_with_index do |weight, i| packages_arr << { length: [:shipping_dimensions][i][0].to_f, width: [:shipping_dimensions][i][1].to_f, height: [:shipping_dimensions][i][2].to_f, weight: weight.to_f, flat_rate_package_type: [:flat_rate_package_types][i], container_type: [:container_types][i] } end carrier_responses_hash = { date: Time.current.to_s, packages: packages_arr, origin_address: get_address_hash_from_address([:origin_address]), destination_address: (if [:destination_address].present? get_address_hash_from_address([:destination_address]) else { postal_code: [:postal_code], state_code: [:state], country_iso: [:country_iso] } end ), rates: [] } [:delivery] = self # Calculate effective ship date for rate requests # This is when we plan to ship - user can override, otherwise defaults to: # - Today if a working day and before the noon same-day cutoff # - Next valid business day otherwise (skips weekends + company holidays) # Note: requested_ship_before is the retailer's deadline (informational only, not used for rate calculation) company = order&.company || resource&.company || rma_for_return&.company raise "Cannot calculate shipping - no company associated with order/resource/rma" unless company # Use user-provided rate_ship_date if valid, otherwise calculate default. # Orders use the noon-cutoff `next_warehouse_ship_date` so carrier-quoted # arrival dates match the customer-facing "Ships ___" banner; non-order # paths (RMAs, store transfers) fall back to the warehouse-hours-aware # `next_valid_ship_date`. ship_date = @rate_ship_date_override if @rate_ship_date_override && @rate_ship_date_override >= Date.current ship_date ||= order&.next_warehouse_ship_date ship_date ||= company.next_valid_ship_date # For LTL freight, ship date must be at least next business day (pickup scheduling required) if ltl_freight || ltl_freight_guaranteed next_business_day = company.next_business_day(Date.current + 1.day) if ship_date < next_business_day ship_date = next_business_day Rails.logger.debug { "retrieve_shipping_costs: LTL requires pickup scheduling, adjusted ship_date to #{ship_date}" } end end [:ship_date] = ship_date Rails.logger.debug { "retrieve_shipping_costs: using ship_date=#{ship_date} (override=#{@rate_ship_date_override.present?})" } # Add Walmart "Ship with Walmart" info for Walmart marketplace orders # This enables discounted shipping rate comparison from Walmart's carrier partnerships if order&.edi_orchestrator_partner&.start_with?('walmart_seller') [:walmart_order_info] = { partner: order.edi_orchestrator_partner.to_sym, po_number: order.edi_po_number, deliver_by_date: order.requested_deliver_by, ship_by_date: order.requested_ship_before || Date.current } end # Add Amazon Buy Shipping info for Amazon marketplace orders # This enables Amazon's negotiated rates with A-to-Z Buy Shipping protections if order&.edi_orchestrator_partner&.start_with?('amazon_seller') [:amazon_order_info] = { partner: order.edi_orchestrator_partner.to_sym, order_id: order.edi_po_number, deliver_by_date: order.requested_deliver_by, ship_by_date: ship_date } end # Pass catalog for carrier exclusion filtering in WyShipping [:catalog] = catalog if order&.customer&.catalog_id.present? = WyShipping.() Rails.logger.debug { "Delivery id: #{id}, retrieve_shipping_costs, shipping_options: #{.inspect}" } if .any? << .first[:status_description] self.ltl_exclusion_reasons = Array(.first[:ltl_exclusion_reasons]) if .first[:status_code] == '1' += .first[:packages].inspect.to_s # [0..149] # return_message += "..." if return_message.length > 149 so_override = is_rma_return? ? ShippingOption.find_by(name: 'override', country: rma_for_return&.customer&.store&.country&.iso) : ShippingOption.find_by(name: 'override', country: resource.store.country.iso) old_override_shipping_cost = shipping_costs.detect { |sc| sc.shipping_option_id == so_override&.id } cost_override = nil cost_override = old_override_shipping_cost.cost if old_override_shipping_cost cost_override = get_economy_shipping_cost_to_use if ships_economy_package? cost_override = get_fallback_cost_to_use if ships_economy_ltl? # Capture intent before deleting: if the ONLY existing cost is the auto-set override # (i.e. a previous failed request left no real options), we can release the override # lock when valid rates come back. If non-override costs already existed alongside the # override, the user deliberately chose override and we must respect that choice. override_was_only_option = shipping_option&.is_override? && shipping_costs.skip_override.empty? shipping_costs.skip_override.delete_all # Preload all ShippingOption records needed for this batch to avoid N+1 queries. # Many option names exist for multiple countries (US, CA, CN, etc.). # Scope to the store's origin country first so index_by always picks the # correct variant; fall back to any country if no origin-country record exists. all_option_names = .last.map { |opt, val| (val[:option_name] || opt).to_s }.uniq origin_country_iso = is_rma_return? ? rma_for_return&.customer&.store&.country&.iso : resource.store.country.iso so_by_name = ShippingOption.where(name: all_option_names, country: origin_country_iso).index_by(&:name) # Collect all new shipping cost records for batch insert (avoids 12+ individual transactions) now = Time.current batch_attrs = [] .last.each do |option, value| san_id = nil if bill_shipping_to_customer && origin_address.supports_third_party_shipping san = customer.shipping_account_number(value[:carrier], (begin resource.billing_address rescue StandardError nil end), destination_address) san_id = san.id if san end option_name = (value[:option_name] || option).to_s so = so_by_name[option_name] || ShippingOption.find_by(name: option_name, country: origin_country_iso) || ShippingOption.find_by(name: option_name) cod = true if cod_collection_type.present? # handle origin address supports_third_party_shipping varies from supplier to supplier if is_store_transfer_delivery || so.is_third_party_only? cost_to_use = 0.0 transportation_charges = 0.0 = 0.0 else cost_to_use = value[:cost] transportation_charges = value[:transportation_charges] = value[:service_options_charges] insured_value = value[:insured_value] end batch_attrs << { delivery_id: id, shipping_option_id: so.id, cost: cost_to_use, transportation_charges: transportation_charges || 0.0, service_options_charges: || 0.0, saturday_delivery: saturday_delivery || false, signature_confirmation: signature_confirmation || false, cod: cod || false, shipping_account_number_id: san_id, insured_value: insured_value, description_override: (so.is_freightquote? ? "Freightquote #{value[:service_code]}" : nil), rate_data: (value[:rate_data] || {}).merge({ actual_cost: value[:cost].to_f.round(2) }), created_at: now, updated_at: now } # Use .to_f before rounding — BigDecimal serializes as a String in JSONB, causing # sort/comparison failures when the column is read back (same pattern as line 1502). rate_entry = value.merge({ cost: cost_to_use.to_f.round(2), transportation_charges: value[:transportation_charges].to_f.round(2), service_options_charges: value[:service_options_charges].to_f.round(2) }) # Prefix marketplace rates so they're distinguishable from direct carrier rates if value[:carrier] == 'WalmartSeller' rate_entry[:service_name] = "Ship with Walmart: #{rate_entry[:service_name]}" elsif value[:carrier] == 'AmazonSeller' ptp_carrier = value.dig(:rate_data, :amz_carrier_id) || value.dig(:rate_data, 'amz_carrier_id') if ptp_carrier.present? && ptp_carrier != 'AMZN_US' rate_entry[:service_name] = "AMZ #{rate_entry[:service_name]}" else rate_entry[:service_name] = "Amazon Shipping: #{rate_entry[:service_name]}" end end carrier_responses_hash[:rates] << rate_entry end fk_error = false if batch_attrs.any? begin ShippingCost.insert_all(batch_attrs) rescue ActiveRecord::InvalidForeignKey => e Rails.logger.warn "Delivery#retrieve_shipping_costs: FK violation for delivery #{id} — #{e.}" fk_error = true end end unless fk_error shipping_costs.reload return_code = :ok if shipping_costs.any? # Release the override lock when valid rates come back AND the override was auto-set # (the only cost before this run was the placeholder override from a prior failed request). # If the user had already picked override while other options existed, non-override costs # would have been present before the delete above — override_was_only_option stays false # and we leave the intentional selection untouched. # EDI orders where the label is purchased externally always keep override regardless. if return_code == :ok && override_was_only_option && order&.edi_shipping_option_name != 'override' self.shipping_option_id = nil end unless shipping_costs.detect { |sc| sc.shipping_option_id == so_override&.id } if is_store_transfer_delivery cost_override = 0.0 else cost_override ||= (shipping_costs.detect { |sc| sc.shipping_option_id == shipping_option_id } || sorted_shipping_costs.first).try(:cost) || get_fallback_cost_to_use # need something here end shpcst = ShippingCost.new(shipping_option: so_override, cost: cost_override) shpcst.description_override = 'Shipping override, please confirm' shipping_costs << shpcst end sc_override = shipping_costs.detect { |sc| sc.shipping_option_id == so_override&.id } cost_override = get_economy_shipping_cost_to_use if ships_economy_package? cost_override = get_fallback_cost_to_use if ships_economy_ltl? sc_override.update(cost: cost_override) if sc_override end else # nothing comes back, invalid preference or option set like signature confirmation or saturday delivery, in this case just set override of $500 and let rep sort it out: we need some shipping option/cost. If europe, override is 0 euro so = is_rma_return? ? ShippingOption.find_by(name: 'override', country: rma_for_return&.customer&.store&.country&.iso) : ShippingOption.where(name: 'override', country: resource.store.country.iso).first self.shipping_option_id = so.id clear_shipping_costs_safely cost_to_use = if is_rma_return? rma_for_return&.customer&.store&.country&.eu_country? ? 0 : get_fallback_cost_to_use else resource.store.country.eu_country? ? 0 : get_fallback_cost_to_use end shpcst = ShippingCost.new(shipping_option: so, cost: cost_to_use) shpcst.description_override = 'Shipping override, please confirm' shipping_costs << shpcst return_code = :no_options << "No valid shipping options could be found for #{name}. Setting override of $#{get_fallback_cost_to_use}." end else # shipping service unavailable # shipping services unavailable, in this case just set override of $500 and let rep sort it out: we need some shipping option/cost. If europe, override is 0 euro so = is_rma_return? ? ShippingOption.find_by(name: 'override', country: rma_for_return&.customer&.store&.country&.iso) : ShippingOption.where(name: 'override', country: resource.store.country.iso).first self.shipping_option_id = so.id clear_shipping_costs_safely cost_to_use = resource.store.country.eu_country? ? 0 : get_fallback_cost_to_use shpcst = ShippingCost.new(shipping_option: so, cost: cost_to_use) shpcst.description_override = 'Shipping override, please confirm' shipping_costs << shpcst return_code = :no_response = "There was no response from carrier web services for #{name}. Setting override of $#{get_fallback_cost_to_use}, please try again or request assistance." end end result = { code: return_code, message: , packages: , created_at: Time.current.to_fs(:db) } # Persist the human/debug message via messaging_logs (see Models::LegacyRateRequest) begin messaging_logs.create!(category: 'shipping_rate_request', message: result) rescue StandardError # non-fatal; continue end # Assign carrier_responses in-memory self.carrier_responses = carrier_responses_hash || {} # Avoid triggering before_save callback work if nothing changed materially beyond carrier_responses if is_rma_return? && changes.except('carrier_responses').empty? update_columns(carrier_responses: self.carrier_responses) else save end resource.shipping_issue_alerted = false if resource.present? result end # If lock was not acquired, return already processing message unless result.lock_was_acquired? return { code: :already_processing, message: 'Shipping costs are already being retrieved', packages: '' } end # Return the result from the block result.result end |
#retrieve_shipping_description_for_line_item(shipping_line) ⇒ String?
Convenience for invoice rendering: pulls the shipping-cost description
from a shipping LineItem.
2453 2454 2455 |
# File 'app/models/delivery.rb', line 2453 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.
2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 |
# File 'app/models/delivery.rb', line 2463 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).
2061 2062 2063 2064 2065 2066 2067 2068 |
# File 'app/models/delivery.rb', line 2061 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
109 |
# File 'app/models/delivery.rb', line 109 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.
3977 3978 3979 3980 3981 3982 |
# File 'app/models/delivery.rb', line 3977 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).
3282 3283 3284 |
# File 'app/models/delivery.rb', line 3282 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.
5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 |
# File 'app/models/delivery.rb', line 5054 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.
5001 5002 5003 |
# File 'app/models/delivery.rb', line 5001 def schedule_request_estimated_packaging DeliveryRequestPrePackWorker.perform_in(5.seconds, id) end |
#selected_shipping_cost ⇒ ShippingCost
101 |
# File 'app/models/delivery.rb', line 101 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.
3291 3292 3293 |
# File 'app/models/delivery.rb', line 3291 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).
3388 3389 3390 |
# File 'app/models/delivery.rb', line 3388 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.
3406 3407 3408 |
# File 'app/models/delivery.rb', line 3406 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).
3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 |
# File 'app/models/delivery.rb', line 3370 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.
3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 |
# File 'app/models/delivery.rb', line 3311 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.
3299 3300 3301 |
# File 'app/models/delivery.rb', line 3299 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.
3397 3398 3399 |
# File 'app/models/delivery.rb', line 3397 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.
1198 1199 1200 |
# File 'app/models/delivery.rb', line 1198 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.
1207 1208 1209 1210 1211 1212 1213 1214 |
# File 'app/models/delivery.rb', line 1207 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.
3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 |
# File 'app/models/delivery.rb', line 3096 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.
4607 4608 4609 4610 4611 4612 4613 4614 |
# File 'app/models/delivery.rb', line 4607 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.
4922 4923 4924 4925 4926 4927 4928 |
# File 'app/models/delivery.rb', line 4922 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.
4211 4212 4213 |
# File 'app/models/delivery.rb', line 4211 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 (shipping_option,
selected_shipping_cost, shipping_cost) and the shipping
LineItem. Honours user-intentional overrides, EDI shipping options,
delivery deadlines, LTL switches, and falls back through preferred
service / carrier / cheapest. The bulk of carrier-selection policy
lives here.
2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 |
# File 'app/models/delivery.rb', line 2638 def set_proper_shipping_cost shipping_lines = line_items.select(&:is_shipping?) shipping_line = shipping_lines.pop Rails.logger.debug { "set_proper_shipping_cost, shipping_line: #{shipping_line ? shipping_line.id : 'none'}" } skip = false Rails.logger.debug { "set_proper_shipping_cost, self.changes: #{changes}" } Rails.logger.debug { "set_proper_shipping_cost, self.relevant_changes: #{relevant_changes}" } Rails.logger.debug { "set_proper_shipping_cost, self.has_not_changed_but_has_shipping_lines?: #{has_not_changed_but_has_shipping_lines?}" } Rails.logger.debug { "set_proper_shipping_cost, shipping_costs.select{|sc| sc.changes.any?}.empty?: #{shipping_costs.select { |sc| sc.changes.any? }.empty?}" } if has_not_changed_but_has_shipping_lines? && shipping_costs.select { |sc| sc.changes.any? }.empty? skip = true end # in the before_save context only set_proper_shipping_cost if we have not changed but have shipping lines Rails.logger.debug { "set_proper_shipping_cost, self.has_shipping_line_linked_to_shipping_cost_that_doesnt_exist_in_delivery?(shipping_line): #{has_shipping_line_linked_to_shipping_cost_that_doesnt_exist_in_delivery?(shipping_line)}" } if has_shipping_line_linked_to_shipping_cost_that_doesnt_exist_in_delivery?(shipping_line) skip = false end # this tests to see if we do have shipping cost ids but none of them match the shipping line's shipping cost id: this means we are out of synch and the shipping line must be updated Rails.logger.debug { "set_proper_shipping_cost, resource&.do_not_detect_shipping: #{resource&.do_not_detect_shipping}" } skip = true if resource&.do_not_detect_shipping Rails.logger.debug { "set_proper_shipping_cost, self.shipping_costs.present?: #{shipping_costs.present?}" } skip = true if shipping_costs.blank? Rails.logger.debug { "set_proper_shipping_cost, skip: #{skip}" } Rails.logger.debug { "set_proper_shipping_cost, force_shipping_cost_update: #{force_shipping_cost_update}" } # Extra guard: if no trigger fields changed, a valid selection exists, and no shipping_costs mutated, skip heavy work if is_rma_return? && !skip trigger_keys = %w[destination_address_id shipping_option_id ltl_freight ltl_freight_guaranteed signature_confirmation saturday_delivery selected_shipping_cost_id] if (relevant_changes.keys & trigger_keys).empty? && selected_shipping_cost_id.present? && shipping_costs.select { |sc| sc.changes.any? }.empty? && !has_shipping_line_linked_to_shipping_cost_that_doesnt_exist_in_delivery?(shipping_line) skip = true end end return true if skip && force_shipping_cost_update != true # try first with selected_shipping_cost_id Rails.logger.debug { "set_proper_shipping_cost, selected_shipping_cost_id: #{selected_shipping_cost_id}" } Rails.logger.debug { "set_proper_shipping_cost, self.shipping_option.id: #{shipping_option&.id}" } Rails.logger.debug { "set_proper_shipping_cost, self.shipping_option.is_override?: #{shipping_option&.is_override?}" } Rails.logger.debug { "set_proper_shipping_cost, self.selected_shipping_cost&.shipping_option&.is_override?: #{selected_shipping_cost&.shipping_option&.is_override?}" } if selected_shipping_cost.present? && selected_shipping_cost&.shipping_option&.is_override? reset_override = false reset_override = true if relevant_changes.keys.include?('destination_address_id') || ltl_freight_has_changed? Rails.logger.debug { "set_proper_shipping_cost, (relevant_changes.keys.include?('destination_address_id') || ltl_freight_has_changed?)): #{relevant_changes.keys.include?('destination_address_id') || ltl_freight_has_changed?}" } # If the override is intentionally selected, never reset it regardless of address/LTL changes. # Check both: 1) delivery.shipping_option is override, 2) order's EDI shipping option is 'override' override_is_intentional = self.shipping_option&.is_override? || order&.edi_shipping_option_name == 'override' reset_override = false if override_is_intentional if reset_override Rails.logger.debug 'set_proper_shipping_cost, reset_override!!!' # go for cheapest if address changed or it switched from package to ltl freight or vice versa self.selected_shipping_cost_id = nil end end # Skip beta and item-less options from auto-selection — beta options require an # explicit admin choice; item-less options crash apply_selected_shipping_cost! # (AppSignal #5031, #5157). Falls back to the next viable option. sorted_costs = sorted_shipping_costs(uniq_by_shipping_option_id: true, filter_by_ltl_freight: ships_ltl_freight?) .compact .reject { |sc| sc.shipping_option&.is_beta? } .select { |sc| sc.shipping_option&.item.present? } # If the current selection is a beta option, preserve it as an explicit admin choice — # beta options are intentionally excluded from sorted_costs (the auto-select pool) but # remain valid when a human picked them. Without this, the detect below returns nil and # the system treats the manual beta selection as "auto-matched", replacing it. selected_cost = shipping_costs.detect { |sc| sc.id == selected_shipping_cost_id } # Across a rate rebuild (retrieve_shipping_costs deletes + reinserts non-override # ShippingCost rows), selected_shipping_cost_id orphans to a deleted row. Non-beta, # non-freight selections recover via the `sc.shipping_option == self.shipping_option` # match below, but beta and freight options are filtered out of sorted_costs and can't # take that path — so fall back to matching by the delivery's still-set # shipping_option_id when the delivery is currently pointing at a beta or freight option. # Bugs surfaced on ST726065 (ShipEngine LTL beta dropped on every item edit) and # SO727121 (Sameday Worldwide Threshold dropped on release). if selected_cost.nil? && (shipping_option&.is_beta? || shipping_option&.is_freight) selected_cost = shipping_costs.detect { |sc| sc.shipping_option_id == shipping_option_id } end # Honor a deliberate selection that sorted_costs would otherwise drop, so an admin/EDI # pick survives a re-save (e.g. order release). sorted_costs excludes beta options # (rejected above) AND — because it is filtered by `filter_by_ltl_freight: # ships_ltl_freight?` — any freight option when the delivery's ltl_freight flag is unset. # In both cases the detect below misses the pick, shipping_cost_was_auto_matched flips # true, and the carrier / customer-default fallbacks replace it. That is the SO727121 # swap: the lone is_freight rate (Sameday Worldwide Threshold) on a delivery with # ltl_freight unset was overwritten by the customer default (Purolator Ground). explicit_freight_pick = selected_cost.present? && !selected_cost.is_override? && selected_cost.shipping_option&.is_freight && !ships_ltl_freight? if selected_cost&.shipping_option&.is_beta? || explicit_freight_pick shipping_cost_entry = selected_cost shipping_cost_was_auto_matched = false else shipping_cost_entry = sorted_costs.detect { |sc| sc.id == selected_shipping_cost_id } # need to do this because the selected_shipping_cost might not yet be save e.g. setting override and override cost in one step shipping_cost_was_auto_matched = shipping_cost_entry.nil? end # Retrieves based on the resource or customer default if not set already # IMPORTANT: Don't override manually selected shipping options (especially overrides) with EDI preferred option # Preserve override selections even when shipping_option is temporarily nil during transitions if shipping_option.blank? # If there's already a selected shipping cost (especially an override), use its shipping option if selected_shipping_cost.present? && selected_shipping_cost.shipping_option.present? self.shipping_option = selected_shipping_cost.shipping_option # Only fall back to preferred_shipping_option if no selection exists else self.shipping_option = preferred_shipping_option end # If shipping_option is already set (including overrides), preserve it - don't change to preferred_shipping_option # This ensures manually selected overrides are preserved during order release from CR hold end Rails.logger.debug { "set_proper_shipping_cost, sorted_costs: #{sorted_costs.map { |sc| [sc.shipping_option.name, sc.id] }.inspect}" } # If switching to LTL, prefer the absolute cheapest freight option BEFORE any heuristic matches # This avoids picking an expensive carrier/service-level just because it matches previous preferences if ships_ltl_freight? && shipping_cost_entry.nil? freight_candidates = shipping_costs.select { |sp| sp.shipping_option.is_freight && !sp.hide? && !sp.shipping_option.is_beta? && sp.shipping_option.item.present? } freight_candidates_sorted = freight_candidates.sort_by { |a| a.cost.to_f.round(2) } Rails.logger.debug do "set_proper_shipping_cost, freight candidates by cost: #{freight_candidates_sorted.map { |sc| [sc.id, sc.shipping_option&.description, sc.cost.to_f.round(2)] }.inspect}" end cheapest_freight = freight_candidates_sorted.first if cheapest_freight Rails.logger.debug do "set_proper_shipping_cost, preselect cheapest freight: id=#{cheapest_freight.id}, #{cheapest_freight.shipping_option&.description}, cost=#{cheapest_freight.cost.to_f.round(2)}" end shipping_cost_entry = cheapest_freight end end # We have a selected shipping option, we try to match it or come close to it if shipping_cost_entry.nil? && self.shipping_option && !self.shipping_option.is_override? Rails.logger.debug { "set_proper_shipping_cost, self.shipping_option: #{self.shipping_option.inspect}" } # First try to match the selected shipping option if we have one shipping_cost_entry = sorted_costs.detect { |sc| sc.shipping_option == self.shipping_option } Rails.logger.debug do "set_proper_shipping_cost, after match by shipping_option: shipping_cost_entry.shipping_option: #{begin shipping_cost_entry.shipping_option.inspect rescue StandardError nil end}" end # If Nothing matched based on the shipping option, try to be smart and find a similar service level and carrier option shipping_cost_entry ||= sorted_costs.detect { |sc| (sc.shipping_option.carrier == self.shipping_option.carrier) && (sc.shipping_option.service_level == self.shipping_option.service_level) } if self.shipping_option.service_level.present? Rails.logger.debug do "set_proper_shipping_cost, after match by shipping_option service_level and/or carrier: shipping_cost_entry.shipping_option: #{begin shipping_cost_entry.shipping_option.inspect rescue StandardError nil end}" end # Still nothing? so try to find the closest shipping_option based on the same service level, different carrier shipping_cost_entry ||= sorted_costs.detect { |sc| sc.shipping_option.service_level == self.shipping_option.service_level } if self.shipping_option.service_level.present? Rails.logger.debug do "set_proper_shipping_cost, after match by shipping_option.service_level: shipping_cost_entry.shipping_option: #{begin shipping_cost_entry.shipping_option.inspect rescue StandardError nil end}" end # Nothing again? ok try somethign with the same carrier shipping_cost_entry ||= sorted_costs.detect { |sc| sc.shipping_option.carrier == self.shipping_option.carrier } if self.shipping_option.carrier.present? Rails.logger.debug do "set_proper_shipping_cost, after match by shipping_option.carrier: shipping_cost_entry.shipping_option: #{begin shipping_cost_entry.shipping_option.inspect rescue StandardError nil end}" end end if self.shipping_option.present? && self.shipping_option.is_override? Rails.logger.debug { "set_proper_shipping_cost, self.shipping_option: #{self.shipping_option.inspect}" } shipping_cost_entry ||= sorted_costs.detect { |sc| sc.shipping_option == self.shipping_option } end if ltl_freight_has_changed? override_shipping_cost_entry ||= sorted_costs.detect(&:is_override?) override_shipping_cost_entry.cost = get_fallback_cost_to_use if relevant_changes['ltl_freight'].last == true override_shipping_cost_entry.cost = get_economy_shipping_cost_to_use if relevant_changes['ltl_freight'].last == false && ships_economy Rails.logger.debug { "set_proper_shipping_cost, LTL changes: #{relevant_changes['ltl_freight']}, shipping_cost_entry.cost: #{shipping_cost_entry&.cost}" } end # If LTL freight is active and nothing matched yet, as an additional guard prefer the cheapest freight option if ships_ltl_freight? shipping_cost_entry ||= sorted_shipping_costs_www_hash[:freight]&.reject { |sc| sc.shipping_option&.is_beta? }&.first end # If there's a delivery deadline, check if current selection meets it - if not, find best option if order&.requested_deliver_by.present? deliver_by = order.requested_deliver_by current_est_date = shipping_cost_entry&.carrier_estimated_delivery_date current_is_late = current_est_date.present? && current_est_date > deliver_by current_is_override = shipping_cost_entry&.is_override? current_has_no_date = shipping_cost_entry.present? && current_est_date.blank? # IMPORTANT: Do NOT auto-replace selections when: # 1. The delivery's shipping_option is explicitly set to override (user chose it) # 2. The order's EDI shipping option is 'override' (EDI orders like Amazon non-Buy-Shipping) # 3. The delivery's shipping_option is a Ship with Walmart (SWW) option (Walmart orders) # 4. The delivery already has an AmazonSeller option selected (user chose a specific AMZBS rate) user_selected_override = self.shipping_option&.is_override? edi_requires_override = order&.edi_shipping_option_name == 'override' user_selected_sww = self.shipping_option&.carrier == 'WalmartSeller' edi_requires_sww = order&.edi_shipping_option_name == 'sww' || order&.edi_shipping_option_name&.start_with?('sww_') user_selected_amz_bs = self.shipping_option&.carrier == 'AmazonSeller' edi_requires_amz_bs = order&.edi_shipping_option_name == 'amzbs' || order&.edi_shipping_option_name&.start_with?('amzbs_') selection_is_intentional = user_selected_override || edi_requires_override || user_selected_sww || edi_requires_sww || user_selected_amz_bs # For AMZBS orders where no prior selection existed (auto-matched by # carrier heuristic), optimize to the cheapest on-time AMZBS rate. # This handles initial import where the carrier-match picks a suboptimal # rate (e.g., Amazon Shipping Ground at $22.93 when FedEx HD at $18.61 # is available and on-time). Skipped when the user already selected a # specific AMZBS rate (selection_is_intentional) — a rate refresh # orphans the old selected_shipping_cost_id making # shipping_cost_was_auto_matched true even though the user chose it. if edi_requires_amz_bs && shipping_cost_entry.present? && shipping_cost_was_auto_matched && !selection_is_intentional amzbs_costs = sorted_costs.select(&:is_amzbs?) amzbs_on_time = amzbs_costs.select { |sc| est = sc.carrier_estimated_delivery_date est.present? && est <= deliver_by }.sort_by { |sc| sc.cost.to_f } cheapest_amzbs = amzbs_on_time.first if cheapest_amzbs && cheapest_amzbs.cost.to_f < shipping_cost_entry.cost.to_f shipping_cost_entry = cheapest_amzbs Rails.logger.debug { "set_proper_shipping_cost, AMZBS auto-optimized to cheapest on-time: #{cheapest_amzbs.shipping_option&.name} at $#{cheapest_amzbs.cost}" } end end # Only find a better option if: # - No selection exists AND selection is NOT intentional, OR # - Current selection is late (but NOT if selection is intentional), OR # - Current selection is an unintentional override, OR # - Current selection has no delivery date (but NOT if selection is intentional) # IMPORTANT: If selection is intentional (override, SWW, or AMZBS), we should # NEVER auto-select a different option even if shipping_cost_entry is nil # (happens during refresh_deliveries_quoting before the ShippingCost is recreated) should_find_better_option = (shipping_cost_entry.nil? && !selection_is_intentional) || (current_is_late && !selection_is_intentional) || (current_is_override && !selection_is_intentional) || (current_has_no_date && !selection_is_intentional) if should_find_better_option # Filter to non-override options for this selection = sorted_costs.reject(&:is_override?) # AMZBS orders must use Amazon Buy Shipping rates — Heatwave rates are # irrelevant because the label is purchased from Amazon's API. = .select(&:is_amzbs?) if edi_requires_amz_bs on_time_candidates = .select do |sc| est_date = sc.carrier_estimated_delivery_date est_date.present? && est_date <= deliver_by end.sort_by { |sc| sc.cost.to_f } best_option = nil if on_time_candidates.any? # If customer has a third-party billing account, prefer options matching that carrier if customer&.shipping_account_numbers&.any? preferred_carriers = customer.shipping_account_numbers.map(&:carrier).uniq best_option = on_time_candidates.detect { |sc| preferred_carriers.include?(sc.shipping_option.carrier) } end # Otherwise just pick the cheapest on-time option best_option ||= on_time_candidates.first else # No on-time options - pick the cheapest non-override option best_option = .sort_by { |sc| sc.cost.to_f }.first end if best_option shipping_cost_entry = best_option Rails.logger.debug do "set_proper_shipping_cost, delivery deadline selection: deliver_by=#{deliver_by}, on_time=#{on_time_candidates.any?}, selected=#{shipping_cost_entry&.shipping_option&.description}" end end end end if resource.try(:cart?) # For carts, fall back default is the cheapest shipping_cost_entry ||= sorted_shipping_costs_www_hash[:economy]&.first Rails.logger.debug do "set_proper_shipping_cost, after fall back default to cheapest for cart: shipping_cost_entry.shipping_option: #{begin shipping_cost_entry.shipping_option.inspect rescue StandardError nil end}" end elsif has_dropship_items? && !order&.single_origin # here we want to use the cheapest option for one of the carriers on the supplier shipping_cost_entry ||= sorted_costs.select { |sc| origin_address.supported_carriers.present? ? origin_address.supported_carriers.include?(sc.shipping_option.carrier) : true }.first Rails.logger.debug do "set_proper_shipping_cost, dropship delivery, after fall back default to cheapest supplier carrier option: #{customer.default_shipping_option_name}, shipping_cost_entry.shipping_option: #{begin shipping_cost_entry.shipping_option.inspect rescue StandardError nil end}" end else # Fall back default is the customer's default/preferred shipping_cost_entry ||= sorted_costs.detect { |sc| sc.shipping_option.name == customer.default_shipping_option_name } Rails.logger.debug do "set_proper_shipping_cost, after fall back default to customer.default_shipping_option_name: #{customer.default_shipping_option_name}, shipping_cost_entry.shipping_option: #{begin shipping_cost_entry.shipping_option.inspect rescue StandardError nil end}" end end if ships_economy_package? Rails.logger.debug { "set_proper_shipping_cost, ships_economy_package?: #{ships_economy_package?}" } # use the override when shipping economy and reaching this point shipping_cost_entry ||= sorted_costs.detect(&:is_override?) elsif ships_ltl_freight? # For LTL freight, always prefer cheapest non-override freight option non_override_freight = sorted_costs.reject(&:is_override?).sort_by { |sc| sc.cost.to_f } if shipping_cost_entry.nil? || shipping_cost_entry.is_override? shipping_cost_entry = non_override_freight.first if non_override_freight.any? end Rails.logger.debug { "set_proper_shipping_cost, ships_ltl_freight, selected: #{shipping_cost_entry&.shipping_option&.description}" } else Rails.logger.debug { "set_proper_shipping_cost, !ships_economy_package?: #{!ships_economy_package?}" } # Fall back default is the first non postal option shipping_cost_entry ||= sorted_costs.detect { |sc| !sc.shipping_option.is_postal? } end Rails.logger.debug do "set_proper_shipping_cost, after fall back default: shipping_cost_entry.shipping_option: #{begin shipping_cost_entry.shipping_option.inspect rescue StandardError nil end}" end # Store back the shipping option since it could be different or changed # IMPORTANT: Do NOT overwrite shipping_option if it's intentionally set to override # This preserves user's explicit override selection even when shipping costs are being refreshed current_override_intentional = self.shipping_option&.is_override? || order&.edi_shipping_option_name == 'override' if shipping_cost_entry && !current_override_intentional self.shipping_option = shipping_cost_entry.shipping_option elsif shipping_cost_entry && current_override_intentional && shipping_cost_entry.is_override? # Only update if the new entry is also override (to sync override cost with override option) self.shipping_option = shipping_cost_entry.shipping_option end Rails.logger.debug do "set_proper_shipping_cost, after all matches: shipping_option: #{begin shipping_option.inspect rescue StandardError nil end}" end return unless shipping_cost_entry apply_selected_shipping_cost!(shipping_cost_entry) true 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.
4270 4271 4272 |
# File 'app/models/delivery.rb', line 4270 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).
4009 4010 4011 |
# File 'app/models/delivery.rb', line 4009 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.
4397 4398 4399 |
# File 'app/models/delivery.rb', line 4397 def ship_from_attributes order&.ship_from_attributes(self) || rma_for_return&.ship_from_attributes end |
#ship_labeled_via_heatwave? ⇒ Boolean
740 741 742 |
# File 'app/models/delivery.rb', line 740 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
744 745 746 |
# File 'app/models/delivery.rb', line 744 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.
4258 4259 4260 4261 4262 4263 4264 |
# File 'app/models/delivery.rb', line 4258 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?
4388 4389 4390 |
# File 'app/models/delivery.rb', line 4388 def ship_to_attributes order&.ship_to_attributes || rma_for_return&.ship_to_attributes end |
#shipment_contents_editable?(current_user = nil) ⇒ Boolean
3510 3511 3512 3513 3514 |
# File 'app/models/delivery.rb', line 3510 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.
2559 2560 2561 2562 2563 2564 2565 |
# File 'app/models/delivery.rb', line 2559 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>
116 |
# File 'app/models/delivery.rb', line 116 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.
957 958 959 |
# File 'app/models/delivery.rb', line 957 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
1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 |
# File 'app/models/delivery.rb', line 1975 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
3516 3517 3518 |
# File 'app/models/delivery.rb', line 3516 def shipments_voidable? SHIPPING_STATES.include?(state.to_sym) end |
#shipping? ⇒ Boolean
3520 3521 3522 |
# File 'app/models/delivery.rb', line 3520 def shipping? %i[pending_ship_confirm shipped].include?(state.to_sym) end |
#shipping_account_number ⇒ ShippingAccountNumber
99 |
# File 'app/models/delivery.rb', line 99 belongs_to :shipping_account_number, optional: true |
#shipping_costs ⇒ ActiveRecord::Relation<ShippingCost>
dependent destroy handled by trigger
111 |
# File 'app/models/delivery.rb', line 111 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!.
1538 1539 1540 |
# File 'app/models/delivery.rb', line 1538 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.
1237 1238 1239 1240 1241 1242 1243 1244 |
# File 'app/models/delivery.rb', line 1237 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.
2593 2594 2595 2596 2597 2598 2599 2600 |
# File 'app/models/delivery.rb', line 2593 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
100 |
# File 'app/models/delivery.rb', line 100 belongs_to :shipping_option, optional: true |
#shipping_option_matches?(so_name) ⇒ Boolean
1246 1247 1248 1249 1250 1251 1252 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 |
# File 'app/models/delivery.rb', line 1246 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
5069 5070 5071 |
# File 'app/models/delivery.rb', line 5069 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
5078 5079 5080 |
# File 'app/models/delivery.rb', line 5078 def ships_economy_ltl? ships_economy && ships_ltl_freight? end |
#ships_economy_package? ⇒ Boolean
5074 5075 5076 |
# File 'app/models/delivery.rb', line 5074 def ships_economy_package? ships_economy && !ships_ltl_freight? end |
#ships_ltl_freight? ⇒ Boolean
2547 2548 2549 |
# File 'app/models/delivery.rb', line 2547 def ships_ltl_freight? !is_warehouse_pickup? && (ltl_freight.present? || ltl_freight_guaranteed.present?) end |
#should_have_electronic_commercial_invoice? ⇒ Boolean
4021 4022 4023 |
# File 'app/models/delivery.rb', line 4021 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
748 749 750 751 |
# File 'app/models/delivery.rb', line 748 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
3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 |
# File 'app/models/delivery.rb', line 3410 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
2579 2580 2581 |
# File 'app/models/delivery.rb', line 2579 def should_ship_ltl_freight? is_default_ltl_freight? || ltl_freight.present? || ltl_freight_guaranteed.present? end |
#show_packaging_on_pick_slip? ⇒ Boolean
1319 1320 1321 |
# File 'app/models/delivery.rb', line 1319 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.
2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 |
# File 'app/models/delivery.rb', line 2419 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
3061 3062 3063 |
# File 'app/models/delivery.rb', line 3061 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.
2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 |
# File 'app/models/delivery.rb', line 2103 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).
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 |
# File 'app/models/delivery.rb', line 2136 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. PO-box destinations are restricted to postal carriers.
2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 |
# File 'app/models/delivery.rb', line 2170 def sorted_shipping_costs_www_hash(sort_by_price: true) res = shipping_costs.skip_3rd_party.includes(:shipping_option) carriers = WWW_SHIPPING_CARRIERS[origin_address.country_iso3.to_sym] WWW_FREIGHT_CARRIERS[origin_address.country_iso3.to_sym] if destination_address.present? && destination_address.po_box? carriers = PO_BOX_CARRIERS end # If it's a PO box, then use postal carriers # ECONOMY SERVICE result_economy = shipping_costs.override_only # GROUND SERVICE # 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 gs = res.select { |sc| sc.shipping_option.days_commitment >= 3.5 || sc.shipping_option.carrier == 'SpeedeeDelivery' } result_ground = gs.select { |sp| carriers.include?(sp.shipping_option.carrier) && !sp.hide? }.sort_by { |a| [carriers.index(a.shipping_option.carrier) || 99, a.cost.to_f.round(2)] } result_ground = result_ground.sort_by { |a| a.cost.to_f.round(2) } if sort_by_price # EXPEDITED SERVICE es = res.select { |sc| sc.shipping_option.days_commitment >= 2 && sc.shipping_option.days_commitment < 3.5 && sc.shipping_option.carrier != 'SpeedeeDelivery' } result_expedited = es.select { |sp| carriers.include?(sp.shipping_option.carrier) && !sp.hide? }.sort_by { |a| [carriers.index(a.shipping_option.carrier) || 99, a.cost.to_f.round(2)] } result_expedited = result_expedited.sort_by { |a| a.cost.to_f.round(2) } if sort_by_price # RUSH SERVICE rs = res.select { |sc| sc.shipping_option.days_commitment < 2 && sc.shipping_option.carrier != 'SpeedeeDelivery' } result_rush = rs.select { |sp| carriers.include?(sp.shipping_option.carrier) && !sp.hide? }.sort_by { |a| [carriers.index(a.shipping_option.carrier) || 99, a.cost.to_f.round(2)] } result_rush = result_rush.sort_by { |a| a.cost.to_f.round(2) } if sort_by_price # FREIGHT SERVICE # filter these using our new is_freight column and choose the cheapest result_freight = [res.select { |sp| sp.shipping_option.is_freight && !sp.hide? }.min_by { |a| a.cost.to_f.round(2) }] { economy: result_economy.uniq, ground: result_ground.compact.uniq, faster: (result_expedited + result_rush).compact.uniq, freight: result_freight.compact.uniq } 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.
4702 4703 4704 |
# File 'app/models/delivery.rb', line 4702 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.
4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 |
# File 'app/models/delivery.rb', line 4465 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
177 |
# File 'app/models/delivery.rb', line 177 delegate :store, to: :resource |
#supplier ⇒ Supplier
102 |
# File 'app/models/delivery.rb', line 102 belongs_to :supplier, optional: true |
#supported_shipping_carrier? ⇒ Boolean
4414 4415 4416 4417 4418 4419 4420 4421 4422 |
# File 'app/models/delivery.rb', line 4414 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).
4229 4230 4231 |
# File 'app/models/delivery.rb', line 4229 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.
4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 |
# File 'app/models/delivery.rb', line 4140 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 Shipment.tracking_link(resolved_carrier, master_tracking_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.
4295 4296 4297 |
# File 'app/models/delivery.rb', line 4295 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.
4303 4304 4305 |
# File 'app/models/delivery.rb', line 4303 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.
4727 4728 4729 |
# File 'app/models/delivery.rb', line 4727 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).
4310 4311 4312 |
# File 'app/models/delivery.rb', line 4310 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.
4743 4744 4745 |
# File 'app/models/delivery.rb', line 4743 def update_serial_numbers_shipped_count line_items.each(&:update_serial_numbers_shipped_count) end |
#uploads ⇒ ActiveRecord::Relation<Upload>
117 |
# File 'app/models/delivery.rb', line 117 has_many :uploads, as: :resource, dependent: :destroy |
#valid_for_generating_return_labels? ⇒ Boolean
4436 4437 4438 4439 4440 4441 |
# File 'app/models/delivery.rb', line 4436 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
4424 4425 4426 4427 4428 4429 4430 |
# File 'app/models/delivery.rb', line 4424 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
4432 4433 4434 |
# File 'app/models/delivery.rb', line 4432 def valid_for_voiding_ship_labels? pending_ship_confirm? && shipments.label_complete.any? && !is_part_of_manifest? end |
#validate_all_contents_allocated ⇒ void
4773 4774 4775 4776 4777 |
# File 'app/models/delivery.rb', line 4773 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).
3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 |
# File 'app/models/delivery.rb', line 3496 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? raise "Cannot ship delivery #{id}: order #{order.id} does not have sufficient " \ "authorized or captured funds (balance: $#{'%.2f' % order.balance}). " \ "Resolve payment before shipping." 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).
4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 |
# File 'app/models/delivery.rb', line 4845 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)
4101 4102 4103 4104 4105 4106 4107 4108 4109 |
# File 'app/models/delivery.rb', line 4101 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.
4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 |
# File 'app/models/delivery.rb', line 4116 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
1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 |
# File 'app/models/delivery.rb', line 1078 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.
4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 |
# File 'app/models/delivery.rb', line 4040 def void_shipments # puts "!!void_shipments" if shipments.completed.any? && !is_part_of_manifest? 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 update_columns(master_tracking_number: nil, carrier_bol: nil, actual_shipping_cost: nil, shipengine_label_id: 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 manifest." } end end |