Class: Delivery

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 =
%i[at_warehouse picking pending_pickup_confirm pending_ship_labels pending_carrier_confirm pending_ship_confirm shipped].freeze
ANY_EMPLOYEE_CANCELABLE_STATES =
%i[quoting awaiting_po_fulfillment at_warehouse future_release service_ready_to_fulfill return_labels_complete].freeze
WAREHOUSE_CANCELABLE_STATES =
%i[pre_pack picking pending_pickup_confirm pending_ship_labels].freeze
WAREHOUSE_STATES =
%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 =
(ANY_EMPLOYEE_CANCELABLE_STATES + WAREHOUSE_CANCELABLE_STATES).uniq
CHECK_COLD_LEAD_NOTE =
'Check notes, contains COLD LEAD.'
SHIP_LABEL_HOLD_PERCENT_THRESHOLD =
25.0
SHIP_LABEL_HOLD_DOLLAR_THRESHOLD =
25.0
SHIP_LABEL_HOLD_WEIGHT_PERCENT_THRESHOLD =
15.0
SHIP_LABEL_HOLD_WEIGHT_THRESHOLD =
7.5
CARRIERS_REQUIRING_MANIFEST_COMPLETION =
['SpeedeeDelivery'].freeze
CROSS_BORDER_BROKER_INSTRUCTIONS =
'Broker: Willson International, Email: service@willsonintl.com'
CROSS_BORDER_COUNTRY_SPECIFIC_BROKER_TEXT =
{
  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 =
[
  {
    name: 'RlCarriers',
    customs_email: 'transbordersolutiongroup@rlcarriers.com'
  },
  {
    name: 'Freightquote'
  }
]
CARRIERS_NAMES_TO_SEND_COMMERCIAL_INVOICES =
CARRIERS_TO_SEND_COMMERCIAL_INVOICES.map{|carr| carr[:name]}
FREIGHTQUOTE_CARRIERS_TO_SEND_COMMERCIAL_INVOICES =
[
  {
    key: "polaris",
    name: "Polaris Transport Carriers Inc.",
    carrierCode: "T408447",
    scac:"POLT",
    customs_email: 'customs@polaristransport.com'
  }
]
FREIGHTQUOTE_CARRIER_SCACS_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 =
20.0
FALLBACK_PER_LB_OVERRIDE_COST_WWW =
5.0
FALLBACK_MAX_OVERRIDE_COST_FRACTION_WWW =
20.0
FALLBACK_OVERRIDE_COST_CRM =
500.0

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Attributes included from Models::Profitable

#min_profit_markup

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has one collapse

Has many collapse

Methods included from Models::Payable

#payments

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

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

#quick_note

Methods included from Models::Md5Hashable

#md5_hash_items_from_packable

Methods included from Models::Packable

#calculate_actual_insured_value, #carrier, #carrier_fedex?, #delivery_description, #domestic?, #has_supported_carrier?, #is_goods_shipping?, #is_onsite_service_only?, #is_remote_service_only?, #is_service_only?, #is_warehouse_ca_pickup?, #is_warehouse_pickup?, #is_warehouse_us_pickup?, #is_zero_charge_dropship?, #must_be_insured?, #must_be_signature_confirmation?, #search_deliveries_for_equivalent_packaging, #ship_weight, #ships_from_text, #subtotal, #subtotal_cogs, #subtotal_for_commercial_invoice, #subtotal_for_insured_value, #subtotal_for_ltl_threshold, #subtotal_msrp

Methods included from Models::Payable

#balance, #funded_by_cod?, #total_payments_authorized

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#do_not_validate_line_itemsObject

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_updateObject

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

#override_carrierObject

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_dateObject

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_idsObject

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

.activeActiveRecord::Relation<Delivery>

A relation of Deliveries that are active. Active Record Scope

Returns:

See Also:



284
# File 'app/models/delivery.rb', line 284

scope :active, -> { where.not(state: 'cancelled') }

.all_at_warehouseActiveRecord::Relation<Delivery>

A relation of Deliveries that are all at warehouse. Active Record Scope

Returns:

See Also:



273
# File 'app/models/delivery.rb', line 273

scope :all_at_warehouse, -> { where(state: WAREHOUSE_STATES) }

.auto_ship_confirm(logger: nil) ⇒ Object



3423
3424
3425
3426
3427
3428
3429
3430
3431
# File 'app/models/delivery.rb', line 3423

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_fulfillmentActiveRecord::Relation<Delivery>

A relation of Deliveries that are awaiting po fulfillment. Active Record Scope

Returns:

See Also:



290
# File 'app/models/delivery.rb', line 290

scope :awaiting_po_fulfillment, -> { where(state: 'awaiting_po_fulfillment') }

.by_order_store_idActiveRecord::Relation<Delivery>

A relation of Deliveries that are by order store id. Active Record Scope

Returns:

See Also:



270
# File 'app/models/delivery.rb', line 270

scope :by_order_store_id, ->(store_id) { where(SUBQUERY_ORDER_STORE_ID, store_id:) }

.by_quote_store_idActiveRecord::Relation<Delivery>

A relation of Deliveries that are by quote store id. Active Record Scope

Returns:

See Also:



271
# File 'app/models/delivery.rb', line 271

scope :by_quote_store_id, ->(store_id) { where(SUBQUERY_QUOTE_STORE_ID, store_id:) }

.by_store_idActiveRecord::Relation<Delivery>

A relation of Deliveries that are by store id. Active Record Scope

Returns:

See Also:



269
# File 'app/models/delivery.rb', line 269

scope :by_store_id, ->(store_id) { by_order_store_id(store_id).or(by_quote_store_id(store_id)) }

.cancelableActiveRecord::Relation<Delivery>

A relation of Deliveries that are cancelable. Active Record Scope

Returns:

See Also:



285
# File 'app/models/delivery.rb', line 285

scope :cancelable, -> { where(state: CANCELABLE_STATES) }

.dropshipActiveRecord::Relation<Delivery>

A relation of Deliveries that are dropship. Active Record Scope

Returns:

See Also:



289
# File 'app/models/delivery.rb', line 289

scope :dropship, -> { where(state: %w[awaiting_po_fulfillment processing_po_fulfillment]) }

.fedex_expressActiveRecord::Relation<Delivery>

A relation of Deliveries that are fedex express. Active Record Scope

Returns:

See Also:



299
# File 'app/models/delivery.rb', line 299

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_groundActiveRecord::Relation<Delivery>

A relation of Deliveries that are fedex ground. Active Record Scope

Returns:

See Also:



298
# File 'app/models/delivery.rb', line 298

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_releaseActiveRecord::Relation<Delivery>

A relation of Deliveries that are for future release. Active Record Scope

Returns:

See Also:



295
# File 'app/models/delivery.rb', line 295

scope :for_future_release, -> { where(state: 'future_release') }

.generate_super_pick_slip_pdf(deliveries, split_kits = false) ⇒ Object



1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
# File 'app/models/delivery.rb', line 1098

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



3413
3414
3415
3416
3417
3418
3419
3420
3421
# File 'app/models/delivery.rb', line 3413

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

.invoicedActiveRecord::Relation<Delivery>

A relation of Deliveries that are invoiced. Active Record Scope

Returns:

See Also:



282
# File 'app/models/delivery.rb', line 282

scope :invoiced, -> { where(state: 'invoiced') }

.limit_to_fbaActiveRecord::Relation<Delivery>

A relation of Deliveries that are limit to fba. Active Record Scope

Returns:

See Also:



294
# File 'app/models/delivery.rb', line 294

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_pickupsActiveRecord::Relation<Delivery>

A relation of Deliveries that are non pickups. Active Record Scope

Returns:

See Also:



293
# File 'app/models/delivery.rb', line 293

scope :non_pickups, -> { where.not(destination_address_id: WAREHOUSE_ADDRESS_IDS) }

.non_quotingActiveRecord::Relation<Delivery>

A relation of Deliveries that are non quoting. Active Record Scope

Returns:

See Also:



280
# File 'app/models/delivery.rb', line 280

scope :non_quoting, -> { where.not(state: 'quoting') }

.not_cancelableActiveRecord::Relation<Delivery>

A relation of Deliveries that are not cancelable. Active Record Scope

Returns:

See Also:



286
# File 'app/models/delivery.rb', line 286

scope :not_cancelable, -> { where.not(state: CANCELABLE_STATES) }

.pending_manifest_completionActiveRecord::Relation<Delivery>

A relation of Deliveries that are pending manifest completion. Active Record Scope

Returns:

See Also:



300
# File 'app/models/delivery.rb', line 300

scope :pending_manifest_completion, -> { where(state: 'pending_manifest_completion') }

.pending_ship_confirmActiveRecord::Relation<Delivery>

A relation of Deliveries that are pending ship confirm. Active Record Scope

Returns:

See Also:



296
# File 'app/models/delivery.rb', line 296

scope :pending_ship_confirm, -> { where(state: 'pending_ship_confirm') }

.pickupsActiveRecord::Relation<Delivery>

A relation of Deliveries that are pickups. Active Record Scope

Returns:

See Also:



292
# File 'app/models/delivery.rb', line 292

scope :pickups, -> { where.not(order_id: nil).where(destination_address_id: WAREHOUSE_ADDRESS_IDS) }

.processing_po_fulfillmentActiveRecord::Relation<Delivery>

A relation of Deliveries that are processing po fulfillment. Active Record Scope

Returns:

See Also:



291
# File 'app/models/delivery.rb', line 291

scope :processing_po_fulfillment, -> { where(state: 'processing_po_fulfillment') }

.quotingActiveRecord::Relation<Delivery>

A relation of Deliveries that are quoting. Active Record Scope

Returns:

See Also:



278
# File 'app/models/delivery.rb', line 278

scope :quoting, -> { where(state: 'quoting') }

.quoting_or_pre_packActiveRecord::Relation<Delivery>

A relation of Deliveries that are quoting or pre pack. Active Record Scope

Returns:

See Also:



279
# File 'app/models/delivery.rb', line 279

scope :quoting_or_pre_pack, -> { where(state: %w[quoting pre_pack]) }

.release_deliveries_past_release_dateObject



757
758
759
760
761
762
763
764
765
766
# File 'app/models/delivery.rb', line 757

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
    if d.at_warehouse?
      InternalMailer.delivery_automatically_released_notification(d).deliver_later
    else
      InternalMailer.delivery_automatic_release_failed_notification(d).deliver_later
    end
  end
end

.sales_ordersActiveRecord::Relation<Delivery>

A relation of Deliveries that are sales orders. Active Record Scope

Returns:

See Also:



287
# File 'app/models/delivery.rb', line 287

scope :sales_orders, -> { active.joins(:order).merge(Order.sales_orders) }

.send_manual_release_due_notificationObject



768
769
770
771
772
# File 'app/models/delivery.rb', line 768

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|
    InternalMailer.manual_release_delivery_due_notification(d).deliver_later
  end
end

.ship_labeled_beforeActiveRecord::Relation<Delivery>

A relation of Deliveries that are ship labeled before. Active Record Scope

Returns:

See Also:



301
# File 'app/models/delivery.rb', line 301

scope :ship_labeled_before, ->(time) { where(Delivery[:ship_labeled_at].lteq(time)) }

.shippedActiveRecord::Relation<Delivery>

A relation of Deliveries that are shipped. Active Record Scope

Returns:

See Also:



281
# File 'app/models/delivery.rb', line 281

scope :shipped, -> { where(state: %w[shipped pending_ship_confirm pending_pickup_confirm]) }

.shippingActiveRecord::Relation<Delivery>

A relation of Deliveries that are shipping. Active Record Scope

Returns:

See Also:



288
# File 'app/models/delivery.rb', line 288

scope :shipping, -> { where(state: SHIPPING_STATES) }

.states_for_selectObject



3409
3410
3411
# File 'app/models/delivery.rb', line 3409

def self.states_for_select
  state_machine.states.sort_by(&:human_name).map { |s| [s.human_name, s.value] }
end

.with_active_parentActiveRecord::Relation<Delivery>

A relation of Deliveries that are with active parent. Active Record Scope

Returns:

See Also:



274
275
276
277
# File 'app/models/delivery.rb', line 274

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_associationsActiveRecord::Relation<Delivery>

A relation of Deliveries that are with associations. Active Record Scope

Returns:

See Also:



272
# File 'app/models/delivery.rb', line 272

scope :with_associations, -> { includes(:shipments, :origin_address, :destination_address, { quote: [{ opportunity: [{ customer: [:buying_group, { catalog: :store }] }] }] }, order: [{ customer: [:buying_group, { catalog: :store }] }]) }

.with_line_itemsActiveRecord::Relation<Delivery>

A relation of Deliveries that are with line items. Active Record Scope

Returns:

See Also:



283
# File 'app/models/delivery.rb', line 283

scope :with_line_items, -> { includes(line_items: { catalog_item: { store_item: :item } }) }

.with_shipment_carrierActiveRecord::Relation<Delivery>

A relation of Deliveries that are with shipment carrier. Active Record Scope

Returns:

See Also:



297
# File 'app/models/delivery.rb', line 297

scope :with_shipment_carrier, ->(carrier) { joins(:shipments).where(shipments: { carrier: }) }

Instance Method Details

#activitiesActiveRecord::Relation<Activity>

Returns:

See Also:



121
# File 'app/models/delivery.rb', line 121

has_many :activities, as: :resource, dependent: :nullify

#actual_shipping_cost_exceeds_threshold?Boolean

Returns:

  • (Boolean)


3515
3516
3517
3518
3519
3520
# File 'app/models/delivery.rb', line 3515

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_showObject



3503
3504
3505
3506
3507
# File 'app/models/delivery.rb', line 3503

def actual_shipping_cost_to_show
  actual_shipping_cost_to_use = 0.0
  actual_shipping_cost_to_use = self.actual_shipping_cost.to_f if chosen_shipping_method&..blank?
  actual_shipping_cost_to_use.round(2)
end

#actual_shipping_cost_within_threshold?Boolean

Returns:

  • (Boolean)


3509
3510
3511
3512
3513
# File 'app/models/delivery.rb', line 3509

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_weightObject Also known as: actual_shipment_weight



3522
3523
3524
3525
3526
3527
3528
3529
3530
# File 'app/models/delivery.rb', line 3522

def actual_weight
  # only if you have a weight do we care
  shipments_for_weight = shipments.where.not(weight: nil)
  # We only use top level shipments, ie shipments that are not contained in other shipments/pallets
  shipments_for_weight = shipments_for_weight.top_level
  # Measured shipments at an advanced stage take precedence over the packed one
  shipments_for_weight = shipments_for_weight.measured.presence || shipments_for_weight.packed
  shipments_for_weight.sum(:weight).round(1)
end

#actual_weight_discrepancy_exceeds_threshold?Boolean

Returns:

  • (Boolean)


3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
# File 'app/models/delivery.rb', line 3533

def actual_weight_discrepancy_exceeds_threshold?
  wt_diff_exceeds_percent_thresh = false
  wt_diff_exceeds_percent_thresh = true if (actual_weight - ship_weight).abs > (Delivery::SHIP_LABEL_HOLD_WEIGHT_PERCENT_THRESHOLD / 100).round(2) * ship_weight
  wt_diff_exceeds_lbs_threshold = false
  wt_diff_exceeds_lbs_threshold = true if (actual_weight - ship_weight).abs > Delivery::SHIP_LABEL_HOLD_WEIGHT_THRESHOLD
  exceeds = false
  exceeds = true if 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}, estimated ship_weight: #{ship_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) ⇒ Object



2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
# File 'app/models/delivery.rb', line 2635

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) ⇒ Object



2626
2627
2628
2629
2630
2631
2632
2633
# File 'app/models/delivery.rb', line 2626

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_costObject



3220
3221
3222
3223
3224
3225
3226
# File 'app/models/delivery.rb', line 3220

def adjusted_actual_shipping_cost
  if chosen_shipping_method&..present?
    BigDecimal(0)
  else
    actual_shipping_cost
  end
end

#all_activitiesObject



690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
# File 'app/models/delivery.rb', line 690

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

Returns:

  • (Boolean)


2560
2561
2562
# File 'app/models/delivery.rb', line 2560

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_pdfObject



3086
3087
3088
# File 'app/models/delivery.rb', line 3086

def all_intl_forms_pdf
  uploads.order(:id).reverse_order.find_by(category: 'all_intl_forms_pdf')
end

#all_labels_pdfObject



3066
3067
3068
# File 'app/models/delivery.rb', line 3066

def all_labels_pdf
  uploads.order(:id).reverse_order.find_by(category: 'all_labels_pdf')
end

#all_lines_allocated_to_shipments?Boolean

Returns:

  • (Boolean)


798
799
800
# File 'app/models/delivery.rb', line 798

def all_lines_allocated_to_shipments?
  line_allocation_status_hash.values.all?(&:zero?)
end

#all_lines_allocated_to_shipments_and_shipments_have_weight?Boolean

Returns:

  • (Boolean)


802
803
804
805
806
807
808
809
810
811
812
813
814
# File 'app/models/delivery.rb', line 802

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_expectedObject



3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
# File 'app/models/delivery.rb', line 3612

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

#apply_cheapest_economy_shipping_methodObject



1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
# File 'app/models/delivery.rb', line 1696

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



2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
# File 'app/models/delivery.rb', line 2448

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_shippingObject



1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
# File 'app/models/delivery.rb', line 1711

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

Returns:

  • (Boolean)


2115
2116
2117
2118
# File 'app/models/delivery.rb', line 2115

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_shipmentObject



2725
2726
2727
2728
2729
2730
2731
2732
# File 'app/models/delivery.rb', line 2725

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).

Returns:

  • (Boolean)


790
791
792
# File 'app/models/delivery.rb', line 790

def authoritative_packing_for_shipment_contents?
  Packing.where(delivery_id: id, origin: %i[from_delivery from_manual_entry]).exists?
end

#billing_entityObject

Alias for Resource#billing_entity

Returns:

  • (Object)

    Resource#billing_entity

See Also:



155
# File 'app/models/delivery.rb', line 155

delegate :billing_entity, to: :resource

#bol_pdfObject



3070
3071
3072
# File 'app/models/delivery.rb', line 3070

def bol_pdf
  uploads.ship_bol_pdfs.first
end

#build_new_purchase_order_item(item, li, quantity, unit_cost) ⇒ Object



2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
# File 'app/models/delivery.rb', line 2652

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) ⇒ Object



2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
# File 'app/models/delivery.rb', line 2605

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_cogsObject



725
726
727
# File 'app/models/delivery.rb', line 725

def calculate_all_cogs
  BigDecimal(line_items.non_shipping.sum('unit_cogs * quantity'))
end

#calculate_declared_valueObject



3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
# File 'app/models/delivery.rb', line 3279

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_totalObject



3275
3276
3277
# File 'app/models/delivery.rb', line 3275

def calculate_grand_total
  (actual_shipping_cost || 0.0) + subtotal
end

#calculate_shipping_options(options = {}) ⇒ Object



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
# File 'app/models/delivery.rb', line 1606

def calculate_shipping_options(options = {})
  # TBD REFACTOR NO ORDER DEPENDENCY FOR RETURN DELIVERY
  options[:resource_shipping_method] = (resource&.try(:edi_shipping_option_name) || resource_shipping_method)
  if resource&.try(:edi_shipping_option_name)
    options[: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
  options[:origin_address] = origin_address
  options[:origin_country_iso3] = origin_address.country.iso3
  if destination_address.present?
    options[:city] = destination_address.city
    options[:state] = destination_address.state_code
    options[:country_iso] = destination_address.country.iso
    options[:postal_code] = destination_address.zip_compact.to_s.upcase.strip.squeeze(' ')
    options[:is_residential] = destination_address.is_residential
    options[:destination_address] = destination_address
  elsif resource.installation_postal_code.present?
    options[:state] = resource.installation_state_code
    options[:country_iso] = resource.installation_country_iso
    options[: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
  options[:store] = customer.store
  options[:saturday_delivery] = saturday_delivery
  options[:signature_confirmation] = signature_confirmation.to_b

  options[:myprojects_legacy] = false
  if options[:country_iso] == 'CA' # kludge
    options[: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
    options[:ltl_freight] = ltl_freight.to_b
    options[:ltl_freight_guaranteed] = ltl_freight_guaranteed.to_b
  end
  options[:insured_value] = calculate_declared_value
  use_shipments = Shipping::CreateSuggestedShipment.new.process(self)
  options.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
  options[:cod_collection_type] = cod_collection_type
  options[: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)
      options[:allowed_carriers] = rma_sos.distinct.pluck(:carrier)
      options[: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?)
    options[:is_www_ship_by_zip] = true
  elsif resource&.try(:is_www)
    options[:www] = true
  end
  options
end

#can_be_deleted?Boolean

Returns:

  • (Boolean)


666
667
668
# File 'app/models/delivery.rb', line 666

def can_be_deleted?
  quoting? || pre_pack? || picking? || at_warehouse? || (order && order.order_type == Order::CREDIT_ORDER)
end

#can_print_carton_labels?Boolean

Returns:

  • (Boolean)


674
675
676
# File 'app/models/delivery.rb', line 674

def can_print_carton_labels?
  shipments.packed_or_measured.present?
end

#can_update_tracking_info?Boolean

Returns:

  • (Boolean)


670
671
672
# File 'app/models/delivery.rb', line 670

def can_update_tracking_info?
  shipments.completed.present?
end

#can_void_rma_delivery?Boolean

Returns:

  • (Boolean)


749
750
751
# File 'app/models/delivery.rb', line 749

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

Returns:

  • (Boolean)


2058
2059
2060
2061
2062
2063
2064
2065
# File 'app/models/delivery.rb', line 2058

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

#cancelObject



847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
# File 'app/models/delivery.rb', line 847

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_packagingObject



3757
3758
3759
# File 'app/models/delivery.rb', line 3757

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

Returns:

  • (Boolean)


2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
# File 'app/models/delivery.rb', line 2525

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).

Returns:

  • (Boolean)


2755
2756
2757
# File 'app/models/delivery.rb', line 2755

def cannot_ship_empty_delivery?
  !has_shippable_content?
end

#carrier_customs_emailObject



2716
2717
2718
2719
2720
2721
2722
2723
# File 'app/models/delivery.rb', line 2716

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_iconObject



3186
3187
3188
# File 'app/models/delivery.rb', line 3186

def carrier_icon
  Shipment.carrier_icon(carrier)
end

#carrier_options_for_selectObject



794
795
796
# File 'app/models/delivery.rb', line 794

def carrier_options_for_select
  Shipment.carrier_options_for_select(self)
end

#catalogObject

Alias for Customer#catalog

Returns:

  • (Object)

    Customer#catalog

See Also:



156
# File 'app/models/delivery.rb', line 156

delegate :catalog, :is_amazon_seller_central?, to: :customer

#chosen_shipping_methodObject



1884
1885
1886
1887
1888
# File 'app/models/delivery.rb', line 1884

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_costObject



1865
1866
1867
1868
1869
# File 'app/models/delivery.rb', line 1865

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_itemsObject



3248
3249
3250
# File 'app/models/delivery.rb', line 3248

def commit_catalog_items
  Item::InventoryCommitter.crm_commit(line_items)
end

#commit_reserved_serial_numbersObject



3252
3253
3254
# File 'app/models/delivery.rb', line 3252

def commit_reserved_serial_numbers
  line_items.each(&:commit_reserved_serial_numbers)
end

#complete_pickedObject



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
# File 'app/models/delivery.rb', line 3729

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
      if order&.pre_pack?
        # order pre_pack flow has its own notifications
        if order&.is_wasn4_or_wat0f?
          order.request_carrier_assignment
        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
      set_packaged_items_md5_hash(origin: :from_manual_entry) # for all pre-packs, add to md5 Packing db
    end
  elsif picking? || pending_ship_labels? # || processing_po_fulfillment?) # rb_any_ship_from here we are plan to ship-label the drop-ship delivery
    picked
    if all_lines_allocated_to_shipments_and_shipments_have_weight? && pending_ship_labels? # we need this all_lines_allocated_to_shipments_and_shipments_have_weight? call to populate the errors
      shipments.suggested.each(&:pack!) # mark shipments as actually packed
    end
  end
end

#completed_regular_delivery?Boolean

Returns:

  • (Boolean)


1005
1006
1007
# File 'app/models/delivery.rb', line 1005

def completed_regular_delivery?
  (pending_pickup_confirm? || shipped? || invoiced?) && !is_service_only?
end

#copy_shipments_if_drop_ship_poObject

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.



3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
# File 'app/models/delivery.rb', line 3464

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

#countryObject



3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
# File 'app/models/delivery.rb', line 3291

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_invoiceObject



3397
3398
3399
3400
3401
3402
3403
# File 'app/models/delivery.rb', line 3397

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') ⇒ Object



3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
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
# File 'app/models/delivery.rb', line 3665

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_entriesObject



3392
3393
3394
3395
# File 'app/models/delivery.rb', line 3392

def create_st_ledger_entries
  LedgerTransaction.process_intracompany_st_delivery(self)
  ItemLedgerEntry.process_intracompany_st_delivery(self)
end

#currencyObject



3306
3307
3308
# File 'app/models/delivery.rb', line 3306

def currency
  order&.currency || resource&.currency || rma_for_return&.customer&.catalog&.currency
end

#currency_symbolObject



3310
3311
3312
# File 'app/models/delivery.rb', line 3310

def currency_symbol
  Money::Currency.new(currency).symbol
end

#custom_pack_listObject



1073
1074
1075
1076
1077
1078
# File 'app/models/delivery.rb', line 1073

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

#customerObject

Alias for Resource_or_rma_for_delivery#customer

Returns:

  • (Object)

    Resource_or_rma_for_delivery#customer

See Also:



154
# File 'app/models/delivery.rb', line 154

delegate :customer, :primary_party, to: :resource_or_rma_for_delivery

#default_container_typeObject



3723
3724
3725
3726
3727
# File 'app/models/delivery.rb', line 3723

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_reservationsObject



3574
3575
3576
# File 'app/models/delivery.rb', line 3574

def delete_serial_number_reservations
  line_items.each { |li| li.reserved_serial_numbers.destroy_all }
end

#delta_weight_factor_remaining_to_allocateObject



820
821
822
# File 'app/models/delivery.rb', line 820

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_addressAddress

Returns:

See Also:

Validations (unless => #skip_destination_address_validation? ):



103
# File 'app/models/delivery.rb', line 103

belongs_to :destination_address, class_name: 'Address', validate: true, optional: true

#discountsActiveRecord::Relation<Discount>

Returns:

See Also:



114
# File 'app/models/delivery.rb', line 114

has_many :discounts, through: :line_discounts

#display_carrier_nameObject

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.



1984
1985
1986
# File 'app/models/delivery.rb', line 1984

def display_carrier_name
  chosen_shipping_method&.rate_data&.[]('carrier_name') || reported_carrier
end

#do_not_ship_insure_via_carrier?Boolean

Returns:

  • (Boolean)


1875
1876
1877
1878
1879
1880
1881
1882
# File 'app/models/delivery.rb', line 1875

def do_not_ship_insure_via_carrier?
  res = false
  res = true if Shipping::ShippingInsurance.new.qualifies_for_rating?(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
end

#do_not_validate_line_items?Boolean

Returns:

  • (Boolean)


2734
2735
2736
# File 'app/models/delivery.rb', line 2734

def do_not_validate_line_items?
  do_not_validate_line_items
end

#does_not_require_shipping_labeling?Boolean

Returns:

  • (Boolean)


3796
3797
3798
3799
# File 'app/models/delivery.rb', line 3796

def does_not_require_shipping_labeling?
  order&.is_store_transfer? &&
    customer&.id&.in?(CustomerConstants::TRANSFER_TO_WY_CUSTOMER_IDS)
end

#drop_ship_purchase_ordersActiveRecord::Relation<PurchaseOrder>

Returns:

See Also:



117
# File 'app/models/delivery.rb', line 117

has_many :drop_ship_purchase_orders, class_name: 'PurchaseOrder', foreign_key: 'drop_ship_delivery_id'

#electronic_ship_ci_pdfObject



3078
3079
3080
# File 'app/models/delivery.rb', line 3078

def electronic_ship_ci_pdf
  uploads.order(:id).reverse_order.find_by(category: 'electronic_ship_ci_pdf')
end

#estimated_delivery_dateObject

Trying to 'guess' the delivery date



659
660
661
662
663
664
# File 'app/models/delivery.rb', line 659

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

#european_shipment?Boolean

Returns:

  • (Boolean)


3801
3802
3803
# File 'app/models/delivery.rb', line 3801

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) ⇒ Object



2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
# File 'app/models/delivery.rb', line 2779

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) ⇒ Object



1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
# File 'app/models/delivery.rb', line 1852

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

#freightquote_carrier?Boolean

Returns:

  • (Boolean)


1977
1978
1979
# File 'app/models/delivery.rb', line 1977

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) ⇒ Object



1918
1919
1920
1921
1922
1923
1924
# File 'app/models/delivery.rb', line 1918

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_ediObject



1965
1966
1967
# File 'app/models/delivery.rb', line 1965

def friendly_shipping_method_for_edi
  retrieve_friendly_shipping_method(false, false, nil, false, true)
end

#generate_all_international_forms_pdfObject



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
# File 'app/models/delivery.rb', line 2843

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_pdfObject



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
# File 'app/models/delivery.rb', line 2805

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_labelsObject

ScoutAPM Disabled instrument_method :generate_labels



3059
3060
3061
3062
3063
3064
# File 'app/models/delivery.rb', line 3059

def generate_asynch_labels
  generate_bol_pdf
  generate_ci_pdf
  generate_all_labels_pdf
  generate_all_international_forms_pdf
end

#generate_barcodeObject



1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
# File 'app/models/delivery.rb', line 1129

def generate_barcode
  require 'barby'
  require 'barby/barcode/code_128'
  require 'barby/outputter/png_outputter'
  barcode = 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 barcode.to_png(height: 70)
    file.flush
    file.fsync
  end
  path
end

#generate_bol_pdf(ship_bol_tmp_path = nil) ⇒ Object



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
# File 'app/models/delivery.rb', line 1143

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) ⇒ Object



1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
# File 'app/models/delivery.rb', line 1178

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!



1064
1065
1066
1067
1068
1069
1070
1071
# File 'app/models/delivery.rb', line 1064

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_poObject

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.



2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
# File 'app/models/delivery.rb', line 2579

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_labelsObject

res = uploads << upload
end
res
end



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
2983
2984
2985
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
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
# File 'app/models/delivery.rb', line 2896

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]}")
    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]
      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]
      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|
        i = %w[UPSFreight Freightquote RlCarriers Saia YrcFreight].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 and Freightquote only have a single label, bill of lading etc
        i = %w[UPSFreight Freightquote RlCarriers Saia YrcFreight].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?
              if %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
              # FileUtils.rm new_label_png_path
            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

      # generate delivery RMA return instructions and labels here

      if carrier == 'UPSFreight' || carrier == 'Freightquote' || carrier == 'RlCarriers' || carrier == 'Saia' || carrier == 'YrcFreight' || carrier == 'FedExFreight'
        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
          status_message = "Labels generated for #{shipments.completed.length} packages. Estimated charge: #{currency_symbol}#{shipping_cost}. Actual charge: #{currency_symbol}#{actual_shipping_cost}. #{status_message}"
        rescue StandardError => e
          ErrorReporting.error e
          status_message = "Labels generated but combined PDF failed: #{e.message}. Individual labels are still available."
          Rails.logger.error("generate_all_labels_pdf failed for delivery #{id}: #{e.class} - #{e.message}")
        end
        # International forms backgrounded via worker (not needed for label printing)
        if is_international? && carrier != 'Freightquote'
          DeliveryPostLabelWorker.perform_async(id)
        end
        ship_labeled # implicit save!
        { 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) ⇒ Object



1054
1055
1056
1057
1058
1059
1060
1061
1062
# File 'app/models/delivery.rb', line 1054

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_pdfObject



1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
# File 'app/models/delivery.rb', line 1085

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) ⇒ Object



1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
# File 'app/models/delivery.rb', line 1745

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_useObject



3837
3838
3839
# File 'app/models/delivery.rb', line 3837

def get_economy_shipping_cost_to_use
  sorted_shipping_costs_www_hash[:ground]&.first&.cost || get_fallback_cost_to_use
end

#get_existing_po_itemsObject



2601
2602
2603
# File 'app/models/delivery.rb', line 2601

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_useObject



3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
# File 'app/models/delivery.rb', line 3841

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) ⇒ Object



998
999
1000
1001
1002
1003
# File 'app/models/delivery.rb', line 998

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.attachment.blank?)
  pdf
end

#group_line_items_by_item(line_items) ⇒ Object



2622
2623
2624
# File 'app/models/delivery.rb', line 2622

def group_line_items_by_item(line_items)
  line_items.group_by(&:item)
end

#group_unprocessed_dropship_line_items_by_supplierObject



2596
2597
2598
2599
# File 'app/models/delivery.rb', line 2596

def group_unprocessed_dropship_line_items_by_supplier
  # here we want the dropship line items that have yet to be processed, i.e. no linked dropship PO or the dropship PO item is cancelled, all group by supplier
  line_items.dropship.includes(:item).select { |li| li.purchase_order_item.nil? || li.purchase_order_item&.cancelled? }.group_by { |li| li.item.supplier_item.supplier }
end

#has_custom_products?Boolean

Returns:

  • (Boolean)


1174
1175
1176
# File 'app/models/delivery.rb', line 1174

def has_custom_products?
  line_items.joins(:item).where(items: { product_category_id: ProductCategory.custom_product_ids }).exists?
end

#has_custom_shipping_labels?Boolean

Returns:

  • (Boolean)


678
679
680
# File 'app/models/delivery.rb', line 678

def has_custom_shipping_labels?
  order.custom_shipping_labels.present?
end

#has_destination_postal_codeObject



2519
2520
2521
# File 'app/models/delivery.rb', line 2519

def has_destination_postal_code
  destination_address || resource.installation_postal_code.present?
end

#has_dropship_items?Boolean

Returns:

  • (Boolean)


2564
2565
2566
# File 'app/models/delivery.rb', line 2564

def has_dropship_items?
  line_items.joins(:item).where(items: { dropship: true }).present?
end

#has_future_release_date?Boolean

Returns:

  • (Boolean)


729
730
731
# File 'app/models/delivery.rb', line 729

def has_future_release_date?
  future_release_date && (future_release_date > Date.current)
end

#has_kits?Boolean

Returns:

  • (Boolean)


741
742
743
# File 'app/models/delivery.rb', line 741

def has_kits?
  line_items_with_counters.any? { |li| li.children_count.to_i.positive? }
end

#has_kits_or_serial_numbers?Boolean

Returns:

  • (Boolean)


745
746
747
# File 'app/models/delivery.rb', line 745

def has_kits_or_serial_numbers?
  has_serial_numbers? || has_kits?
end

#has_not_changed_but_has_shipping_lines?Boolean

Returns:

  • (Boolean)


2120
2121
2122
# File 'app/models/delivery.rb', line 2120

def has_not_changed_but_has_shipping_lines?
  relevant_changes.empty? && line_items.select(&:is_shipping?).any?
end

#has_ready_to_print_amazon_fba_items?Boolean

Returns:

  • (Boolean)


3590
3591
3592
# File 'app/models/delivery.rb', line 3590

def has_ready_to_print_amazon_fba_items?
  line_items.goods.any? { |li| li.item.ready_to_print_amazon_fba_labels? }
end

#has_serial_numbers?Boolean

Returns:

  • (Boolean)


737
738
739
# File 'app/models/delivery.rb', line 737

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)

Returns:

  • (Boolean)


2747
2748
2749
2750
2751
# File 'app/models/delivery.rb', line 2747

def has_shippable_content?
  return true if is_rma_return?

  line_items.non_shipping.exists?
end

#has_shipping_line_linked_to_shipping_cost_that_doesnt_exist_in_delivery?(shipping_line) ⇒ Boolean

Returns:

  • (Boolean)


2124
2125
2126
# File 'app/models/delivery.rb', line 2124

def has_shipping_line_linked_to_shipping_cost_that_doesnt_exist_in_delivery?(shipping_line)
  shipping_line && shipping_cost_ids.any? && shipping_cost_ids.index(shipping_line.shipping_cost_id).nil?
end

#has_unfulfilled_dropship_items?Boolean

Returns:

  • (Boolean)


2556
2557
2558
# File 'app/models/delivery.rb', line 2556

def has_unfulfilled_dropship_items?
  line_items.any? { |li| li.dropship? && (li.purchase_order_item.nil? || !li.purchase_order_item.fully_receipted?) }
end

#has_unprocessed_dropship_items?Boolean

Returns:

  • (Boolean)


2568
2569
2570
# File 'app/models/delivery.rb', line 2568

def has_unprocessed_dropship_items?
  line_items.any? { |li| li.dropship? && (li.purchase_order_item.nil? || li.purchase_order_item&.cancelled?) }
end

#has_valid_shipments?Boolean

Returns:

  • (Boolean)


3190
3191
3192
# File 'app/models/delivery.rb', line 3190

def has_valid_shipments?
  shipments.packed_or_measured.present? && shipments.packed_or_measured.all?(&:has_dimensions?)
end

#incurs_oversized_penalty?Boolean

Returns:

  • (Boolean)


3643
3644
3645
# File 'app/models/delivery.rb', line 3643

def incurs_oversized_penalty?
  (ships_ltl_freight? != true) && (is_warehouse_pickup? != true) && shipments.packed_or_measured.any? { |s| s.incurs_oversized_penalty? }
end

#indexObject



1228
1229
1230
# File 'app/models/delivery.rb', line 1228

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.

Parameters:

  • logger (Logger) (defaults to: nil)

    Optional logger (defaults to Rails.logger)



3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
# File 'app/models/delivery.rb', line 3439

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
      error_message = "#{Time.current}: Exception!!! Processing delivery id: #{id}, ref/name: #{name}: #{e}"
      logger.error error_message
      logger.error e
    end
  end
end

#instant_quote?Boolean

Returns:

  • (Boolean)


2509
2510
2511
# File 'app/models/delivery.rb', line 2509

def instant_quote?
  resource.respond_to?(:opportunity) && resource.opportunity && (resource.opportunity.opportunity_reception_type == 'IQ')
end

#instantiate_shipping_insuranceObject



3853
3854
3855
3856
3857
3858
3859
# File 'app/models/delivery.rb', line 3853

def instantiate_shipping_insurance
  if ships_ltl_freight?
    Shipping::LtlShippingInsurance.new
  else
    Shipping::PackageShippingInsurance.new
  end
end

#insured_shipmentsObject



753
754
755
# File 'app/models/delivery.rb', line 753

def insured_shipments
  shipments.label_complete.where(is_ship_insured: true)
end

#insured_valueObject



1871
1872
1873
# File 'app/models/delivery.rb', line 1871

def insured_value
  chosen_shipping_method&.insured_value
end

#invoicesActiveRecord::Relation<Invoice>

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



118
# File 'app/models/delivery.rb', line 118

has_many :invoices

#is_amazon_seller_central?Object

Alias for Customer#is_amazon_seller_central?

Returns:

  • (Object)

    Customer#is_amazon_seller_central?

See Also:



156
# File 'app/models/delivery.rb', line 156

delegate :catalog, :is_amazon_seller_central?, to: :customer

#is_amazon_seller_central_veeqo?Boolean

Returns:

  • (Boolean)


2067
2068
2069
# File 'app/models/delivery.rb', line 2067

def is_amazon_seller_central_veeqo?
  is_amazon_seller_central? && override_shipping_method?
end

#is_cross_border?Boolean

Returns:

  • (Boolean)


3367
3368
3369
# File 'app/models/delivery.rb', line 3367

def is_cross_border?
  origin_address && destination_address && (origin_address.country_iso3 != destination_address.country_iso3) && %w[USA CAN].include?(origin_address.country_iso3) && %w[USA CAN].include?(destination_address.country_iso3)
end

#is_default_ltl_freight?Boolean

Returns:

  • (Boolean)


2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
# File 'app/models/delivery.rb', line 2071

def is_default_ltl_freight?
  # country_iso3 = "USA"
  # address = self.shipping_address or self.customer.first_address
  # country_iso3 = address.country_iso3 if address
  if order&.is_store_transfer? && order.shipping_method&.index('freight') && order.shipping_method.index('ltl')
    true
  elsif subtotal_for_ltl_threshold > LTL_FREIGHT_DOLLAR_THRESHOLD
    true
  elsif ship_weight > LTL_FREIGHT_WEIGHT_THRESHOLD
    true
  elsif line_items.parents_only.any? { |li| li.item.ships_via_freight? }
    true
  elsif canadian_tire_special_check?
    true
  elsif customer&.billing_entity&.is_build_com? && order&.shipping_method&.index('freight')
    true
  else
    false
  end

  # for now we only have USA freight implemented but allow PurolatorFreight as an unsupported carrier
end

#is_domestic?Boolean

Returns:

  • (Boolean)


3359
3360
3361
# File 'app/models/delivery.rb', line 3359

def is_domestic?
  origin_address && destination_address && origin_address.country_iso3 == destination_address.country_iso3
end

#is_international?Boolean

Returns:

  • (Boolean)


3363
3364
3365
# File 'app/models/delivery.rb', line 3363

def is_international?
  !is_domestic?
end

#is_international_and_ups_or_fedex?Boolean

Returns:

  • (Boolean)


3371
3372
3373
# File 'app/models/delivery.rb', line 3371

def is_international_and_ups_or_fedex?
  is_international? && %w[UPS FedEx].include?(carrier)
end

#is_part_of_manifest?Boolean

Returns:

  • (Boolean)


3194
3195
3196
# File 'app/models/delivery.rb', line 3194

def is_part_of_manifest?
  shipments.any?(&:manifest) || shipments.any?(&:speedee_manifest_shipment)
end

#is_rma_returnObject Also known as: is_rma_return?



2048
2049
2050
# File 'app/models/delivery.rb', line 2048

def is_rma_return
  rma_for_return.present?
end

#is_smart_service?Boolean

Returns:

  • (Boolean)


2053
2054
2055
# File 'app/models/delivery.rb', line 2053

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

Returns:

  • (Boolean)


2014
2015
2016
2017
2018
2019
# File 'app/models/delivery.rb', line 2014

def is_sww_shipping_cost?(sc = nil)
  sc ||= selected_shipping_cost
  return false unless sc&.rate_data.is_a?(Hash)

  sc.rate_data['sww_carrier_id'].present? || sc.rate_data[:sww_carrier_id].present?
end

#is_www?Boolean

Returns:

  • (Boolean)


3816
3817
3818
# File 'app/models/delivery.rb', line 3816

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_entriesActiveRecord::Relation<ItemLedgerEntry>

Returns:

See Also:



123
# File 'app/models/delivery.rb', line 123

has_many :item_ledger_entries

#ledger_transactionsActiveRecord::Relation<LedgerTransaction>

Returns:

See Also:



122
# File 'app/models/delivery.rb', line 122

has_many :ledger_transactions

#line_allocation_status_hashObject



832
833
834
835
836
837
838
# File 'app/models/delivery.rb', line 832

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_discountsActiveRecord::Relation<LineDiscount>

Returns:

See Also:



112
# File 'app/models/delivery.rb', line 112

has_many :line_discounts, through: :line_items

#line_itemsActiveRecord::Relation<LineItem>

Returns:

See Also:



111
# File 'app/models/delivery.rb', line 111

has_many :line_items, extend: LineItemExtension, autosave: true, dependent: :nullify

#line_items_eligible_for_packingObject



778
779
780
# File 'app/models/delivery.rb', line 778

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_numberObject



3271
3272
3273
# File 'app/models/delivery.rb', line 3271

def line_items_requiring_serial_number
  line_items.reject(&:marked_for_destruction?).select(&:require_serial_number?)
end

#line_items_with_countersObject



733
734
735
# File 'app/models/delivery.rb', line 733

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

Returns:

  • (Boolean)


828
829
830
# File 'app/models/delivery.rb', line 828

def lines_overallocated?
  line_allocation_status_hash.values.any?(&:negative?)
end


3566
3567
3568
# File 'app/models/delivery.rb', line 3566

def link_serial_numbers_to_line_items
  line_items.each(&:link_serial_numbers)
end

#linked_return_deliveriesObject



2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
# File 'app/models/delivery.rb', line 2790

def linked_return_deliveries
  return_deliveries = []
  rmas = []
  rmas << order.rma if order&.is_rma_replacement?
  rmas << precreated_rma if precreated_rma.present?
  rmas.each do |rma|
    rma.credit_orders.each do |co|
      co.deliveries.each do |d|
        return_deliveries << d
      end
    end
  end
  return_deliveries
end

#locked_for_fba?Boolean

Returns:

  • (Boolean)


2102
2103
2104
# File 'app/models/delivery.rb', line 2102

def locked_for_fba?
  order&.is_fba? && order&.shipment_reference_number =~ CustomerConstants::AMAZON_FBA_ID_REGEX && shipments.any? && shipments.all? { |s| s.locked_for_fba? }
end

#ltl_freight_has_changed?Boolean

Returns:

  • (Boolean)


2132
2133
2134
2135
# File 'app/models/delivery.rb', line 2132

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

#mark_multi_shipments_manifested(manifest, shipment) ⇒ Object



3582
3583
3584
# File 'app/models/delivery.rb', line 3582

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

Returns:

  • (Boolean)


2646
2647
2648
2649
2650
# File 'app/models/delivery.rb', line 2646

def matches?(existing_item, item, quantity)
  existing_item.line_item.nil? &&
    existing_item.item == item &&
    existing_item.unit_quantity == quantity
end

#messaging_logsActiveRecord::Relation<MessagingLog>

Returns:

See Also:



113
# File 'app/models/delivery.rb', line 113

has_many :messaging_logs, dependent: :destroy, as: :resource

#name(short = false, filename = false) ⇒ Object



1232
1233
1234
1235
1236
1237
1238
1239
1240
# File 'app/models/delivery.rb', line 1232

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) ⇒ Object



2773
2774
2775
2776
2777
# File 'app/models/delivery.rb', line 2773

def new_shipment_attributes=(shipment_attributes)
  shipment_attributes.each do |attributes|
    shipments.build(attributes)
  end
end

#no_empty_shipmentsObject



3600
3601
3602
3603
3604
# File 'app/models/delivery.rb', line 3600

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_storeObject



774
775
776
# File 'app/models/delivery.rb', line 774

def notify_store
  store.notify_of_new_delivery(self)
end

#open_activities_counterObject



717
718
719
# File 'app/models/delivery.rb', line 717

def open_activities_counter
  all_activities.open_activities.count
end

#orderOrder

Returns:

See Also:



97
# File 'app/models/delivery.rb', line 97

belongs_to :order, inverse_of: :deliveries, optional: true

#origin_addressAddress

Returns:

See Also:

Validations:



102
# File 'app/models/delivery.rb', line 102

belongs_to :origin_address, class_name: 'Address', validate: true, optional: true

#override_shipping_method?Boolean

Returns:

  • (Boolean)


1969
1970
1971
# File 'app/models/delivery.rb', line 1969

def override_shipping_method?
  chosen_shipping_method&.is_override?
end

#packable_active_linesObject



3198
3199
3200
# File 'app/models/delivery.rb', line 3198

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) ⇒ Object



3202
3203
3204
3205
3206
# File 'app/models/delivery.rb', line 3202

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_hashObject



3208
3209
3210
# File 'app/models/delivery.rb', line 3208

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) ⇒ Object



3212
3213
3214
# File 'app/models/delivery.rb', line 3212

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?

Returns:

  • (Boolean)


2044
2045
2046
# File 'app/models/delivery.rb', line 2044

def packageable?
  shipments.packageable.present?
end

#pallet_weight_matchingObject



3606
3607
3608
3609
3610
# File 'app/models/delivery.rb', line 3606

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

#pick_slip_file_name(with_extension = true) ⇒ Object



900
901
902
903
904
905
906
907
# File 'app/models/delivery.rb', line 900

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



910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
# File 'app/models/delivery.rb', line 910

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

#po_numberObject

Alias for Order#po_number

Returns:

  • (Object)

    Order#po_number

See Also:



158
# File 'app/models/delivery.rb', line 158

delegate :po_number, to: :order, allow_nil: true

#precreated_rmaRma

Returns:

See Also:



107
# File 'app/models/delivery.rb', line 107

has_one :precreated_rma, class_name: 'Rma', foreign_key: 'precreate_from_delivery_id', dependent: :nullify

#preferred_shipping_optionObject

These methods below are from the model previously known as delivery_quote



1244
1245
1246
1247
1248
1249
1250
1251
# File 'app/models/delivery.rb', line 1244

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_requesterParty

Returns:

See Also:



124
# File 'app/models/delivery.rb', line 124

belongs_to :prepack_requester, class_name: 'Party', optional: true

#preset_jobsActiveRecord::Relation<PresetJob>

Returns:

See Also:



119
# File 'app/models/delivery.rb', line 119

has_many :preset_jobs, inverse_of: :order

#primary_partyObject

Alias for Resource_or_rma_for_delivery#primary_party

Returns:

  • (Object)

    Resource_or_rma_for_delivery#primary_party

See Also:



154
# File 'app/models/delivery.rb', line 154

delegate :customer, :primary_party, to: :resource_or_rma_for_delivery

Returns:

  • (Boolean)


630
631
632
633
634
635
636
637
638
639
640
641
642
643
# File 'app/models/delivery.rb', line 630

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

#quantities_remaining_to_allocateObject



816
817
818
# File 'app/models/delivery.rb', line 816

def quantities_remaining_to_allocate
  line_allocation_status_hash.values.sum
end

#quoteQuote

Returns:

See Also:



96
# File 'app/models/delivery.rb', line 96

belongs_to :quote, inverse_of: :deliveries, optional: true

#ready_to_choose_ships_economy_carrier?Boolean

Returns:

  • (Boolean)


3833
3834
3835
# File 'app/models/delivery.rb', line 3833

def ready_to_choose_ships_economy_carrier?
  ships_economy_package? && pending_ship_labels?
end

#ready_to_print_amazon_fba_line_itemsObject



3586
3587
3588
# File 'app/models/delivery.rb', line 3586

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!Object



1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
# File 'app/models/delivery.rb', line 1209

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_numberObject Also known as: to_s



841
842
843
# File 'app/models/delivery.rb', line 841

def reference_number
  "DE#{id}"
end

#reference_number_for_labelObject



3322
3323
3324
3325
3326
3327
3328
# File 'app/models/delivery.rb', line 3322

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_numbersObject



3562
3563
3564
# File 'app/models/delivery.rb', line 3562

def rejoin_serial_numbers
  line_items.select(&:require_reservation?).each(&:rejoin_serial_numbers)
end

#relevant_changesObject



2128
2129
2130
# File 'app/models/delivery.rb', line 2128

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


3405
3406
3407
# File 'app/models/delivery.rb', line 3405

def relink_payments
  Payment.where(id: payment_ids).update_all(delivery_id: id)
end

#remap_legacy_shipping_options_if_anyObject



3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
# File 'app/models/delivery.rb', line 3544

def remap_legacy_shipping_options_if_any
  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_carrierObject



1973
1974
1975
# File 'app/models/delivery.rb', line 1973

def reported_carrier
  (carrier || (override_shipping_method? && (shipments&.completed&.top_level&.first&.carrier || chosen_shipping_method&.description_override))).presence
end

#reported_master_tracking_numberObject



1988
1989
1990
# File 'app/models/delivery.rb', line 1988

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

Returns:

  • (Boolean)


3719
3720
3721
# File 'app/models/delivery.rb', line 3719

def requires_manifest_completion?
  CARRIERS_REQUIRING_MANIFEST_COMPLETION.include?(carrier)
end

#reserved_serial_numbersActiveRecord::Relation<ReservedSerialNumber>

Returns:

See Also:



120
# File 'app/models/delivery.rb', line 120

has_many :reserved_serial_numbers, through: :line_items

#reset_early_label_flag_on_orderObject

Reset purchase_label_early flag on the order so next ship-label goes through normal flow



3166
3167
3168
3169
3170
3171
3172
# File 'app/models/delivery.rb', line 3166

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_ship_labeled_atObject



3244
3245
3246
# File 'app/models/delivery.rb', line 3244

def reset_ship_labeled_at
  update_attribute(:ship_labeled_at, nil) if ship_labeled_at.present?
end

#reset_shipping_costObject



1890
1891
1892
1893
# File 'app/models/delivery.rb', line 1890

def reset_shipping_cost
  clear_shipping_costs_safely
  self.shipping_cost = 0 if respond_to? :shipping_cost # REFACTOR
end

#reset_ships_economy_if_unselectedObject



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
# File 'app/models/delivery.rb', line 3765

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 do |sc|
                                                                                                                                                       sc.name
                                                                                                                                                     end.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 { |sc| sc.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

#resourceObject



682
683
684
# File 'app/models/delivery.rb', line 682

def resource
  order || quote
end

#resource_or_rma_for_deliveryObject



686
687
688
# File 'app/models/delivery.rb', line 686

def resource_or_rma_for_delivery
  resource || rma_for_return
end

#resource_presentObject



721
722
723
# File 'app/models/delivery.rb', line 721

def resource_present
  resource.present?
end

#resource_shipping_methodObject



3861
3862
3863
3864
3865
3866
3867
3868
# File 'app/models/delivery.rb', line 3861

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"



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
# File 'app/models/delivery.rb', line 1926

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
      if (begin
        sc.
      rescue StandardError
        false
      end) && show_customer_pays_info
        customer_pays = " (cust. acct.: #{sc..})" unless for_www
        customer_pays = " (using your linked #{carrier} account: #{sc..})" 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

Parameters:

  • rate_ship_date (Date, nil) (defaults to: nil)

    Optional date to use for rate calculation (user can override)



1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
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
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
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
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
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
# File 'app/models/delivery.rb', line 1259

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
  return_message = +''
  packages_message = +''
  shipping_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?
      shipments.pallets.packed.each(&:unpack) unless ltl_freight # here we are moving from LTL to Package, unpack pallets
      # 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
    options = calculate_shipping_options(www: order.try(:is_www))
    packages_arr = []
    options[:shipping_weights].each_with_index do |weight, i|
      packages_arr << {
        length: options[:shipping_dimensions][i][0].to_f,
        width: options[:shipping_dimensions][i][1].to_f,
        height: options[:shipping_dimensions][i][2].to_f,
        weight: weight.to_f,
        flat_rate_package_type: options[:flat_rate_package_types][i],
        container_type: options[:container_types][i]
      }
    end
    carrier_responses_hash = {
      date: Time.current.to_s,
      packages: packages_arr,
      origin_address: get_address_hash_from_address(options[:origin_address]),
      destination_address: (if options[:destination_address].present?
                              get_address_hash_from_address(options[:destination_address])
                            else
                              {
                                postal_code: options[:postal_code],
                                state_code: options[:state],
                                country_iso: options[:country_iso]
                              }
                            end
                           ),
      rates: []
    }
    options[:delivery] = self

    # Calculate effective ship date for rate requests
    # This is when we plan to ship - user can override, otherwise defaults to:
    # - Today if within business hours
    # - Next business day if after hours
    # 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
    ship_date = @rate_ship_date_override if @rate_ship_date_override && @rate_ship_date_override >= Date.current
    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
    options[: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')
      options[: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')
      options[: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
    options[:catalog] = catalog if order&.customer&.catalog_id.present?

    shipping_options = WyShipping.calculate_shipping_from_options(options)
    Rails.logger.debug { "Delivery id: #{id}, retrieve_shipping_costs, shipping_options: #{shipping_options.inspect}" }

    if shipping_options.any?
      return_message << shipping_options.first[:status_description]
      if shipping_options.first[:status_code] == '1'
        packages_message += shipping_options.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 = shipping_options.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 = []
        shipping_options.last.each do |option, value|
          san_id = nil
          if bill_shipping_to_customer && origin_address.supports_third_party_shipping
            san = customer.(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
            service_options_charges = 0.0
          else
            cost_to_use = value[:cost]
            transportation_charges = value[:transportation_charges]
            service_options_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: 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.message}"
            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
        return_message << "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
      return_message = "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: return_message, packages: packages_message, 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) ⇒ Object



2021
2022
2023
# File 'app/models/delivery.rb', line 2021

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) ⇒ Object



2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
# File 'app/models/delivery.rb', line 2025

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&.&.})" if sc&..present?
    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) ⇒ Object



1736
1737
1738
1739
1740
1741
1742
1743
# File 'app/models/delivery.rb', line 1736

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_returnRma

Returns:

See Also:



108
# File 'app/models/delivery.rb', line 108

has_one :rma_for_return, class_name: 'Rma', foreign_key: 'return_delivery_id', dependent: :nullify

#save_purchase_order_if_needed(po) ⇒ Object



2671
2672
2673
# File 'app/models/delivery.rb', line 2671

def save_purchase_order_if_needed(po)
  po.save! unless po.purchase_order_items.empty?
end

#schedule_pickup_if_necessaryObject



3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
# File 'app/models/delivery.rb', line 3805

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_packagingObject



3761
3762
3763
# File 'app/models/delivery.rb', line 3761

def schedule_request_estimated_packaging
  DeliveryRequestPrePackWorker.perform_in(5.seconds, id)
end

#selected_shipping_costShippingCost



100
# File 'app/models/delivery.rb', line 100

belongs_to :selected_shipping_cost, class_name: 'ShippingCost', optional: true

#send_address_type_issue_notificationObject



2675
2676
2677
# File 'app/models/delivery.rb', line 2675

def send_address_type_issue_notification
  InternalMailer.address_type_issue_notification(self).deliver
end

#send_canada_post_manual_void_email(tracking_numbers) ⇒ Object



2693
2694
2695
# File 'app/models/delivery.rb', line 2693

def send_canada_post_manual_void_email(tracking_numbers)
  Mailer.canada_post_manual_void_email(self, tracking_numbers).deliver
end

#send_commercial_invoice_to_carrierObject



2701
2702
2703
# File 'app/models/delivery.rb', line 2701

def send_commercial_invoice_to_carrier
  InternalMailer.commercial_invoice_to_carrier(self).deliver if should_send_commercial_invoice_to_carrier?
end

#send_delivery_pre_pack_cancelled_notification(cancelled_by: nil) ⇒ Object



2688
2689
2690
2691
# File 'app/models/delivery.rb', line 2688

def send_delivery_pre_pack_cancelled_notification(cancelled_by: nil)
  update_column(:suggested_packaging_text, nil)
  InternalMailer.delivery_pre_pack_cancelled_notification(self, cancelled_by: cancelled_by).deliver_later
end

#send_delivery_pre_packed_notificationObject



2683
2684
2685
2686
# File 'app/models/delivery.rb', line 2683

def send_delivery_pre_packed_notification
  update_column(:suggested_packaging_text, nil) # reset this text field after pre-pack
  InternalMailer.delivery_pre_packed_notification(self).deliver_later
end

#send_dropship_delivery_notificationObject



2679
2680
2681
# File 'app/models/delivery.rb', line 2679

def send_dropship_delivery_notification
  InternalMailer.dropship_delivery_notification(self).deliver
end

#send_purolator_manual_void_email(tracking_numbers) ⇒ Object



2697
2698
2699
# File 'app/models/delivery.rb', line 2697

def send_purolator_manual_void_email(tracking_numbers)
  Mailer.purolator_manual_void_email(self, tracking_numbers).deliver
end

#serial_numbers_file_nameObject



985
986
987
# File 'app/models/delivery.rb', line 985

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_printObject



989
990
991
992
993
994
995
996
# File 'app/models/delivery.rb', line 989

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_cogsObject



2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
# File 'app/models/delivery.rb', line 2539

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_neededObject



3494
3495
3496
3497
3498
3499
3500
3501
# File 'app/models/delivery.rb', line 3494

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) ⇒ Object



3711
3712
3713
3714
3715
3716
3717
# File 'app/models/delivery.rb', line 3711

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 = {}) ⇒ Object



3216
3217
3218
# File 'app/models/delivery.rb', line 3216

def set_packaged_items_md5_hash(options = {})
  Shipping::DeliveryMd5Extractor.new(options).process(self)
end

#set_proper_shipping_costObject



2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
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
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
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
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
# File 'app/models/delivery.rb', line 2137

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
  sorted_costs = sorted_shipping_costs(uniq_by_shipping_option_id: true, filter_by_ltl_freight: ships_ltl_freight?).compact
  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?

  # 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? }
    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 { |sc| sc.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]&.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
      real_options = sorted_costs.reject { |sc| sc.is_override? }
      # AMZBS orders must use Amazon Buy Shipping rates — Heatwave rates are
      # irrelevant because the label is purchased from Amazon's API.
      real_options = real_options.select { |sc| sc.is_amzbs? } if edi_requires_amz_bs

      on_time_candidates = real_options.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&.&.any?
          preferred_carriers = customer..map { |san| san.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 = real_options.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 { |sc| sc.is_override? }
  elsif ships_ltl_freight?
    # For LTL freight, always prefer cheapest non-override freight option
    non_override_freight = sorted_costs.reject { |sc| sc.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_ship_labeled_atObject



3240
3241
3242
# File 'app/models/delivery.rb', line 3240

def set_ship_labeled_at
  update_attribute(:ship_labeled_at, Time.current) if ship_labeled_at.blank?
end

#set_shipped_dateObject



3236
3237
3238
# File 'app/models/delivery.rb', line 3236

def set_shipped_date
  update_attribute(:shipped_date, Time.current) if shipped_date.blank?
end

#ship_ci_pdfObject



3074
3075
3076
# File 'app/models/delivery.rb', line 3074

def ship_ci_pdf
  uploads.order(:id).reverse_order.find_by(category: 'ship_ci_pdf')
end

#ship_from_attributesObject



3318
3319
3320
# File 'app/models/delivery.rb', line 3318

def ship_from_attributes
  order&.ship_from_attributes(self) || rma_for_return&.ship_from_attributes
end

#ship_labeled_via_heatwave?Boolean

Returns:

  • (Boolean)


645
646
647
# File 'app/models/delivery.rb', line 645

def ship_labeled_via_heatwave?
  supported_shipping_carrier? && shipments.completed.any? && shipments.completed.all? { |s| s.state == 'label_complete' }
end

#ship_labeled_via_heatwave_or_manual_and_ship_insuring?Boolean

Returns:

  • (Boolean)


649
650
651
# File 'app/models/delivery.rb', line 649

def ship_labeled_via_heatwave_or_manual_and_ship_insuring?
  ship_labeled_via_heatwave? || is_amazon_seller_central_veeqo?
end

#ship_natively_keyObject



3228
3229
3230
3231
3232
3233
3234
# File 'app/models/delivery.rb', line 3228

def ship_natively_key
  key = nil
  if chosen_shipping_method&. && chosen_shipping_method..ship_natively? && chosen_shipping_method...present?
    key = chosen_shipping_method...to_sym
  end
  key
end

#ship_to_attributesObject



3314
3315
3316
# File 'app/models/delivery.rb', line 3314

def ship_to_attributes
  order&.ship_to_attributes || rma_for_return&.ship_to_attributes
end

#shipment_contents_editable?(current_user = nil) ⇒ Boolean

Returns:

  • (Boolean)


2759
2760
2761
2762
2763
# File 'app/models/delivery.rb', line 2759

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

#shipmentsActiveRecord::Relation<Shipment>

Returns:

See Also:



115
# File 'app/models/delivery.rb', line 115

has_many :shipments, -> { order(:created_at) }, autosave: true, dependent: :destroy

#shipments_for_packingObject



782
783
784
# File 'app/models/delivery.rb', line 782

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



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
# File 'app/models/delivery.rb', line 1670

def shipments_to_packages_hash(use_shipments = nil)
  # Packed packages are used first, top level, ie not cartons packed on pallets, etc... if none present, we look at suggested
  use_shipments ||= shipments.top_level.where(state: 'packed').presence
  use_shipments ||= shipments.top_level.where(state: 'suggested')
  shipping_weights = []
  shipping_dimensions = []
  flat_rate_package_types = []
  container_types = []
  package_values = []
  use_shipments.each do |shp|
    # Convert BigDecimal to float to prevent JSON serialization as strings in carrier_responses jsonb
    shipping_weights << shp.weight.to_f
    shipping_dimensions << [shp.length.to_f, shp.width.to_f, shp.height.to_f]
    flat_rate_package_types << shp.flat_rate_package_type
    container_types << shp.container_type
    package_values << shp.compute_shipment_declared_value
  end
  {
    shipping_weights:,
    shipping_dimensions:,
    flat_rate_package_types:,
    container_types:,
    package_values:
  }
end

#shipments_voidable?Boolean

Returns:

  • (Boolean)


2765
2766
2767
# File 'app/models/delivery.rb', line 2765

def shipments_voidable?
  SHIPPING_STATES.include?(state.to_sym)
end

#shipping?Boolean

Returns:

  • (Boolean)


2769
2770
2771
# File 'app/models/delivery.rb', line 2769

def shipping?
  %i[pending_ship_confirm shipped].include?(state.to_sym)
end

#shipping_account_numberShippingAccountNumber



98
# File 'app/models/delivery.rb', line 98

belongs_to :shipping_account_number, optional: true

#shipping_costsActiveRecord::Relation<ShippingCost>

dependent destroy handled by trigger

Returns:

See Also:



110
# File 'app/models/delivery.rb', line 110

has_many :shipping_costs, -> { order(:cost) }, autosave: true

#shipping_line_itemObject



1253
1254
1255
# File 'app/models/delivery.rb', line 1253

def shipping_line_item
  line_items.shipping_only.first
end

#shipping_method_friendlyObject



1009
1010
1011
1012
1013
1014
1015
1016
# File 'app/models/delivery.rb', line 1009

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) ⇒ Object



2106
2107
2108
2109
2110
2111
2112
2113
# File 'app/models/delivery.rb', line 2106

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. ? " (using your linked account: #{sc..})" : ''} #{verbose ? "(#{sc.shipping_option.delivery_commitment})" : ''} ", sc.shipping_option.id
    ]
  end
end

#shipping_optionShippingOption



99
# File 'app/models/delivery.rb', line 99

belongs_to :shipping_option, optional: true

#shipping_option_matches?(so_name) ⇒ Boolean

Returns:

  • (Boolean)


1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
# File 'app/models/delivery.rb', line 1018

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

Returns:

  • (Boolean)


3820
3821
3822
# File 'app/models/delivery.rb', line 3820

def ships_economy? # don't know why, but need to do it this way, can't use delegate
  resource&.ships_economy? || false
end

#ships_economy_ltl?Boolean

Returns:

  • (Boolean)


3829
3830
3831
# File 'app/models/delivery.rb', line 3829

def ships_economy_ltl?
  ships_economy && ships_ltl_freight?
end

#ships_economy_package?Boolean

Returns:

  • (Boolean)


3825
3826
3827
# File 'app/models/delivery.rb', line 3825

def ships_economy_package?
  ships_economy && !ships_ltl_freight?
end

#ships_ltl_freight?Boolean

Returns:

  • (Boolean)


2094
2095
2096
# File 'app/models/delivery.rb', line 2094

def ships_ltl_freight?
  !is_warehouse_pickup? && (ltl_freight.present? || ltl_freight_guaranteed.present?)
end

#should_have_electronic_commercial_invoice?Boolean

Returns:

  • (Boolean)


3082
3083
3084
# File 'app/models/delivery.rb', line 3082

def should_have_electronic_commercial_invoice?
  is_international? && (carrier == 'UPS' || carrier == 'FedEx') && shipments_voidable? # only return true on international deliveries using UPS when in shipping states, i.e. not quoting invoiced, etc
end

#should_print_heating_element_labels?Boolean

Returns:

  • (Boolean)


653
654
655
656
# File 'app/models/delivery.rb', line 653

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

Returns:

  • (Boolean)


2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
# File 'app/models/delivery.rb', line 2705

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&.attachment_name.present? && carrier_qualifies && ltl_pro_number.present? # we must have an international delivery, with a CI attached, with a qualifying carrier and an LTL Pro number
end

#should_ship_ltl_freight?Boolean

Returns:

  • (Boolean)


2098
2099
2100
# File 'app/models/delivery.rb', line 2098

def should_ship_ltl_freight?
  is_default_ltl_freight? || ltl_freight.present? || ltl_freight_guaranteed.present?
end

#show_packaging_on_pick_slip?Boolean

Returns:

  • (Boolean)


1081
1082
1083
# File 'app/models/delivery.rb', line 1081

def show_packaging_on_pick_slip?
  destination_address&.is_amazon?
end

#simple_shipping_description_for_shipping_cost(sc = nil) ⇒ Object



1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
# File 'app/models/delivery.rb', line 1992

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? && (is_www? || 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

Returns:

  • (Boolean)


2515
2516
2517
# File 'app/models/delivery.rb', line 2515

def skip_destination_address_validation?
  instant_quote? || resource.try(:cart?)
end

#sorted_ground_shipping_costs(skip_override = false) ⇒ Object



1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
# File 'app/models/delivery.rb', line 1766

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) ⇒ Object



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
# File 'app/models/delivery.rb', line 1789

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{|sc| sc.id}.reverse.uniq{|sc| sc.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) ⇒ Object



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
# File 'app/models/delivery.rb', line 1817

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_numbersObject



3558
3559
3560
# File 'app/models/delivery.rb', line 3558

def split_serial_numbers
  line_items.select(&:require_reservation?).each(&:split_serial_numbers)
end

#state_listObject



3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
# File 'app/models/delivery.rb', line 3375

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

#storeObject

Alias for Resource#store

Returns:

  • (Object)

    Resource#store

See Also:



157
# File 'app/models/delivery.rb', line 157

delegate :store, to: :resource

#supplierSupplier

Returns:

See Also:



101
# File 'app/models/delivery.rb', line 101

belongs_to :supplier, optional: true

#supported_shipping_carrier?Boolean

Returns:

  • (Boolean)


3330
3331
3332
3333
3334
3335
3336
3337
3338
# File 'app/models/delivery.rb', line 3330

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


3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
# File 'app/models/delivery.rb', line 3174

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_itemsObject



3256
3257
3258
# File 'app/models/delivery.rb', line 3256

def uncommit_catalog_items
  Item::InventoryCommitter.crm_uncommit(line_items)
end

#uncommit_reserved_serial_numbersObject



3260
3261
3262
# File 'app/models/delivery.rb', line 3260

def uncommit_reserved_serial_numbers
  line_items.each(&:uncommit_reserved_serial_numbers)
end


3570
3571
3572
# File 'app/models/delivery.rb', line 3570

def unlink_serial_numbers_to_line_items
  line_items.each(&:unlink_serial_numbers)
end

#update_line_items_qty_shippedObject

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).



3267
3268
3269
# File 'app/models/delivery.rb', line 3267

def update_line_items_qty_shipped
  line_items.update_all('qty_shipped = quantity')
end

#update_serial_numbers_shipped_countObject



3578
3579
3580
# File 'app/models/delivery.rb', line 3578

def update_serial_numbers_shipped_count
  line_items.each(&:update_serial_numbers_shipped_count)
end

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



116
# File 'app/models/delivery.rb', line 116

has_many :uploads, as: :resource, dependent: :destroy

#valid_for_generating_return_labels?Boolean

Returns:

  • (Boolean)


3352
3353
3354
3355
3356
3357
# File 'app/models/delivery.rb', line 3352

def valid_for_generating_return_labels?
  return true if pending_ship_labels? && supported_shipping_carrier? && is_domestic? && shipments.packed_or_awaiting_labels.any?

  errors.add(:base, 'needs be in state pending ship labels, shipping domestically, and with a supported carrier and shipments ready to label')
  false
end

#valid_for_generating_ship_labels?Boolean

Returns:

  • (Boolean)


3340
3341
3342
3343
3344
3345
3346
# File 'app/models/delivery.rb', line 3340

def valid_for_generating_ship_labels?
  return false unless pending_ship_labels? && supported_shipping_carrier?
  return true if is_domestic?
  return true if shipping_option.supported_for_st && order&.is_store_transfer?

  false
end

#valid_for_voiding_ship_labels?Boolean

Returns:

  • (Boolean)


3348
3349
3350
# File 'app/models/delivery.rb', line 3348

def valid_for_voiding_ship_labels?
  pending_ship_confirm? && shipments.label_complete.any? && !is_part_of_manifest?
end

#validate_all_contents_allocatedObject



3594
3595
3596
3597
3598
# File 'app/models/delivery.rb', line 3594

def validate_all_contents_allocated
  return if all_lines_allocated_to_shipments?

  errors.add(:base, 'Not all lines are allocated properly to containers')
end

#versions_for_audit_trail(_params = {}) ⇒ Object



3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
# File 'app/models/delivery.rb', line 3647

def versions_for_audit_trail(_params = {})
  query_sql = %q{
                (
                  item_type = 'LineItem'
                    AND reference_data @> '{"resource_type": "Order"}'
                    AND reference_data @> '{"delivery_id": :id}'
                )
                OR (
                  item_type = 'LineItem'
                    AND reference_data @> '{"resource_type": "Quote"}'
                    AND reference_data @> '{"delivery_id": :id}'
                )
                OR
                (item_type = 'Delivery' and item_id = :id)
              }
  RecordVersion.where(query_sql, id:)
end

#void_early_label_on_orderBoolean

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)

Returns:

  • (Boolean)

    true if an early label was voided, false otherwise



3139
3140
3141
3142
3143
3144
3145
3146
3147
# File 'app/models/delivery.rb', line 3139

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_existsObject

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.



3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
# File 'app/models/delivery.rb', line 3154

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_labelsObject

Void marketplace labels (Walmart SWW, Amazon, etc.) for all shipments with labels



874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
# File 'app/models/delivery.rb', line 874

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.message}")
      # Continue anyway - don't block the cancel operation
    end
  end
end

#void_shipmentsObject



3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
# File 'app/models/delivery.rb', line 3090

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)
      # 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'
      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)
      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