Class: StandaloneDelivery

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable, Models::ShipMeasurable
Defined in:
app/models/standalone_delivery.rb

Overview

== Schema Information

Table name: standalone_deliveries
Database name: primary

id :bigint not null, primary key
actual_shipping_cost :decimal(5, 2)
billing_postal_code :string
reference_number :string
service_code :string
shipping_account_number :string
shipping_rates :hstore is an Array
state :string default("open"), not null
created_at :datetime not null
updated_at :datetime not null
creator_id :integer
destination_address_id :integer
origin_address_id :integer
store_id :integer not null
updater_id :integer

Indexes

index_standalone_deliveries_on_destination_address_id (destination_address_id)
index_standalone_deliveries_on_origin_address_id (origin_address_id)
index_standalone_deliveries_on_service_code (service_code)
index_standalone_deliveries_on_store_id (store_id)

Constant Summary

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Class Method Summary collapse

Instance Method Summary collapse

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::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

Class Method Details

.completeActiveRecord::Relation<StandaloneDelivery>

A relation of StandaloneDeliveries that are complete. Active Record Scope

Returns:

See Also:



47
# File 'app/models/standalone_delivery.rb', line 47

scope :complete, -> { where("standalone_deliveries.state = 'complete'") }

.openActiveRecord::Relation<StandaloneDelivery>

A relation of StandaloneDeliveries that are open. Active Record Scope

Returns:

See Also:



43
# File 'app/models/standalone_delivery.rb', line 43

scope :open, -> { where("standalone_deliveries.state = 'open'") }

.packagedActiveRecord::Relation<StandaloneDelivery>

A relation of StandaloneDeliveries that are packaged. Active Record Scope

Returns:

See Also:



44
# File 'app/models/standalone_delivery.rb', line 44

scope :packaged, -> { where("standalone_deliveries.state = 'packaged'") }

.ship_labeledActiveRecord::Relation<StandaloneDelivery>

A relation of StandaloneDeliveries that are ship labeled. Active Record Scope

Returns:

See Also:



46
# File 'app/models/standalone_delivery.rb', line 46

scope :ship_labeled, -> { where("standalone_deliveries.state = 'ship_labeled'") }

.ship_ratedActiveRecord::Relation<StandaloneDelivery>

A relation of StandaloneDeliveries that are ship rated. Active Record Scope

Returns:

See Also:



45
# File 'app/models/standalone_delivery.rb', line 45

scope :ship_rated, -> { where("standalone_deliveries.state = 'ship_rated'") }

Instance Method Details

#addressObject



328
329
330
# File 'app/models/standalone_delivery.rb', line 328

def address
  destination_address
end

#all_labels_pdfObject



304
305
306
# File 'app/models/standalone_delivery.rb', line 304

def all_labels_pdf
  uploads.where(category: 'all_labels_pdf').order(id: :desc).first
end

#calculate_declared_valueObject

Total insured/declared value across all packages (mirrors Delivery#calculate_declared_value intent).
Used by Shipment#compute_shipment_declared_value when splitting value per package for rate/label APIs.
Aggregate via ShipmentContent + includes to avoid N+1 when many packages exist (CodeRabbit PR #571).



144
145
146
147
148
149
150
151
152
153
154
# File 'app/models/standalone_delivery.rb', line 144

def calculate_declared_value
  ShipmentContent.joins(:shipment)
    .where(shipments: { standalone_delivery_id: id })
    .includes(line_item: { item: { supplier_items: :supplier_item_prices } })
    .sum do |sc|
      li = sc.line_item
      next 0.0 unless li

      (li.unit_value_for_commercial_invoice || 0) * sc.quantity.to_f
    end
end

#calculate_ship_ratesObject



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'app/models/standalone_delivery.rb', line 161

def calculate_ship_rates
  result = {status_code: :error, status_message: 'no rates returned'}
  all_rate_hashes = []
  rate_hashes_array = WyShipping.find_standalone_delivery_shipping_rates(self)
  rate_hashes_array.each do |res|
    rate_hashes = (res[:request_response][:rates] || []).sort_by { |r| r[:total_charges].to_f } if res[:request_response]
    if res[:status_code] == :ok and rate_hashes.any?
      result[:status_code] = :ok
      result[:status_message] = ''
      all_rate_hashes += rate_hashes
    end
  end
  if all_rate_hashes.any?
    all_rate_hashes.uniq!{|h| h[:service_code]}
    all_rate_hashes.sort_by! { |r| r[:total_charges].to_f }
    self.shipping_rates = all_rate_hashes # this will stringify the keys in the hash array
    previous_carrier = shipments.where.not(carrier: [nil, '']).pick(:carrier)
    preferred = previous_carrier && shipping_rates.detect { |r| r['carrier'] == previous_carrier }
    self.service_code = (preferred || shipping_rates.first)['service_code']
    ship_rate_standalone_delivery!
    reload
  end
  result
end

#carrierObject Also known as: reported_carrier



156
157
158
# File 'app/models/standalone_delivery.rb', line 156

def carrier
  @carrier ||= (service_code.present? ? shipping_rates.detect{ |sr| sr['service_code'] == service_code }&.dig('carrier') : nil)
end

#company_nameObject



91
92
93
94
95
96
97
# File 'app/models/standalone_delivery.rb', line 91

def company_name
  cn = destination_address&.party&.full_name unless destination_address&.party&.is_person?
  cn ||= destination_address&.company_name_override
  return unless cn

  cn.to_s.gsub(/[^0-9a-z\s]/i, '')[0..34]
end

#countryObject



318
319
320
# File 'app/models/standalone_delivery.rb', line 318

def country
  origin_address.country
end

#destination_addressAddress

Returns:

See Also:



35
# File 'app/models/standalone_delivery.rb', line 35

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

#formatted_address(separator: '<br>') ⇒ Object



322
323
324
325
326
# File 'app/models/standalone_delivery.rb', line 322

def formatted_address(separator: '<br>')
  return unless a = address

  a.full_address(true, separator, person_name)
end

#generate_all_labels_pdfObject



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'app/models/standalone_delivery.rb', line 253

def generate_all_labels_pdf
  uploads.where(category: 'all_labels_pdf').destroy_all

  all_labels = shipments.completed.includes(:uploads).filter_map { |s|
    s.uploads.detect { |u| u.category == 'ship_label_pdf' }
  }
  file_name = "#{id}_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 { "self.generate_all_labels_pdf: all_labels_path: #{all_labels_path}, all_labels: #{all_labels.inspect}" }

  if all_labels.size == 1
    FileUtils.cp(all_labels.first.to_file, all_labels_path)
  else
    PdfTools.combine(all_labels, output_file_path: all_labels_path)
  end

  upload = Upload.uploadify(all_labels_path, 'all_labels_pdf')
  raise 'Combo label was not generated' unless upload

  uploads << upload
end

#generate_labelsObject



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'app/models/standalone_delivery.rb', line 186

def generate_labels
  shipping_result = WyShipping.ship_standalone_delivery(self)
  Rails.logger.info("#{Time.current}: StandaloneDelivery ID #{id}, labels generated START LOG $$$$$$$$$$$$$$$$$$$$$$$$$$$$, status_code: #{shipping_result[:status_code]}")
  if shipping_result[:status_code] == :error
    Rails.logger.info("#{carrier}, request/response:")
    Rails.logger.info("#{begin
      shipping_result[:shipment][:ship_reply_xml]
    rescue StandardError
      nil
    end}
")
    { status_code: shipping_result[:status_code], 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", standalone_delivery_id: id)
    self.actual_shipping_cost = shipping_result[:shipment][:total_charges]
    save # pure hacking to get this to work
    reload # pure hacking to get this to work
    shipments.awaiting_label.each_with_index do |shipment, i|
      ship_result_label = shipping_result.dig(:shipment,:labels,i)

      Rails.logger.debug "shipment: #{shipment.inspect}"
      Rails.logger.debug "shipping_result[:shipment][:labels][i]: #{ship_result_label.inspect}"
      Rails.logger.debug "shipping_result[:shipment][:labels][i][:tracking_number]: #{ship_result_label[:tracking_number] || 'not there'}"

      shipment.carrier = carrier
      shipment.tracking_number = ship_result_label[:tracking_number]
      shipment.shipengine_label_id = ship_result_label[:shipengine_label_id]
      shipment.label_generated!
    end
    Rails.logger.info("#{Time.current}: StandaloneDelivery ID #{id}, labels generated END LOG $$$$$$$$$$$$$$$$$$$$$$$$$$$$")
    # save this standalone_delivery
    save
    # second pass generate the labels from the temporary paths
    label_exceptions = []
    status_code = :ok
    shipments.label_complete.each_with_index do |shipment, i|
      if carrier == 'SpeedeeDelivery'
        unless shipment.generate_speedee_label_pdf(shipping_result[:shipment][:rate_data])
          label_exceptions << (i + 1)
        end
      else
        ship_result_label = shipping_result.dig(:shipment,:labels,i)
        old_label_path = ship_result_label[:image]&.path
        if old_label_path
          unless shipment.generate_ship_label_pdf(old_label_path, carrier)
            label_exceptions << (i + 1)
          end
        end
      end
    end
    if label_exceptions.empty? && status_code == :ok
      generate_all_labels_pdf
      status_message = "Labels generated for #{shipments.completed.length} packages. Actual charge: #{store.catalogs.first.currency_symbol}#{actual_shipping_cost}. #{status_message}"
      save
      { status_code: status_code, status_message: 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 << ", status code returned:  #{status_code}" unless status_code == :ok
      msg << '.'
      { status_code: :error, status_message: msg }
    end
  end
end

#manually_complete?Boolean

Returns:

  • (Boolean)


133
134
135
# File 'app/models/standalone_delivery.rb', line 133

def manually_complete?
  complete? and !ready_to_print_labels?
end

#nameObject



99
100
101
# File 'app/models/standalone_delivery.rb', line 99

def name
  "Standalone Delivery, ID: #{id}, from: #{origin_address&.recipient || 'n/a'} to: #{destination_address&.recipient || 'n/a'}"
end

#ok_to_delete?Boolean

Returns:

  • (Boolean)


308
309
310
311
312
# File 'app/models/standalone_delivery.rb', line 308

def ok_to_delete?
  ok = true
  ok = false unless open? and shipments.all? { |s| s.ok_to_delete_with_standalone_delivery? }
  ok
end

#origin_addressAddress

Returns:

See Also:



34
# File 'app/models/standalone_delivery.rb', line 34

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

#person_nameObject

destination_address&.person_name_override&.to_s.gsub(/[^0-9a-z\s]/i, '')[0..34]
destination_address&.company_name_override&.to_s.gsub(/[^0-9a-z\s]/i, '')[0..34]



83
84
85
86
87
88
89
# File 'app/models/standalone_delivery.rb', line 83

def person_name
  pn = destination_address&.party&.full_name if destination_address.party&.is_person?
  pn ||= destination_address&.person_name_override
  return unless pn

  pn.to_s.gsub(/[^0-9a-z\s]/i, '')[0..34]
end

#ready_to_complete?Boolean

Returns:

  • (Boolean)


121
122
123
# File 'app/models/standalone_delivery.rb', line 121

def ready_to_complete?
  shipments.any? { |s| s.label_complete? }
end

#ready_to_label?Boolean

Returns:

  • (Boolean)


117
118
119
# File 'app/models/standalone_delivery.rb', line 117

def ready_to_label?
  shipping_rates and shipping_rates.any? and shipments.any? { |s| s.awaiting_label? }
end

#ready_to_manually_complete?Boolean

Returns:

  • (Boolean)


125
126
127
# File 'app/models/standalone_delivery.rb', line 125

def ready_to_manually_complete?
  !shipments.any? { |s| (s.label_complete? or s.awaiting_label?) }
end

#ready_to_print_labels?Boolean

Returns:

  • (Boolean)


129
130
131
# File 'app/models/standalone_delivery.rb', line 129

def ready_to_print_labels?
  all_labels_pdf.present? and shipments.label_complete.any?
end

#ready_to_print_ship_label?Boolean

Returns:

  • (Boolean)


103
104
105
106
107
# File 'app/models/standalone_delivery.rb', line 103

def ready_to_print_ship_label?
  (ship_labeled? or complete?) and
    all_labels_pdf.present? and
    shipments.label_complete.any?
end

#ready_to_rate?Boolean

Returns:

  • (Boolean)


113
114
115
# File 'app/models/standalone_delivery.rb', line 113

def ready_to_rate?
  shipments.any? { |s| s.awaiting_label? }
end

#ready_to_reset?Boolean

Returns:

  • (Boolean)


109
110
111
# File 'app/models/standalone_delivery.rb', line 109

def ready_to_reset?
  shipments.all? { |s| s.ok_to_delete_with_standalone_delivery? } && !ready_to_rate?
end

#send_canada_post_manual_void_email(tracking_numbers) ⇒ Object



300
301
302
# File 'app/models/standalone_delivery.rb', line 300

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

#ship_natively_keyObject



314
315
316
# File 'app/models/standalone_delivery.rb', line 314

def ship_natively_key
  nil
end

#shipmentsActiveRecord::Relation<Shipment>

Returns:

See Also:



36
# File 'app/models/standalone_delivery.rb', line 36

has_many :shipments, dependent: :destroy

#storeStore

Returns:

See Also:



33
# File 'app/models/standalone_delivery.rb', line 33

belongs_to :store, optional: true

#store_country_iso3Object



137
138
139
# File 'app/models/standalone_delivery.rb', line 137

def store_country_iso3
  store&.mailing_address&.country_iso3
end

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



37
# File 'app/models/standalone_delivery.rb', line 37

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

#void_shipmentsObject



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'app/models/standalone_delivery.rb', line 275

def void_shipments
  if shipments.label_complete.any?
    tracking_numbers = shipments.label_complete.pluck(:tracking_number)
    shipping_result = {}
    shipping_result[:status_code] = :ok
    shipping_result = WyShipping.void_standalone_delivery(self)
    if shipping_result[:status_code] != :ok
      Mailer.admin_notification("standalone_delivery#void_shipments for standalone_delivery #{id} returned error shipping_result[:status_code]: #{shipping_result[:status_code]}", "standalone_delivery#void_shipments for standalone_delivery #{id} returned error shipping_result[:status_code]: #{shipping_result[:status_code]}, shipping_result[:status_message]: #{shipping_result[:status_message]}, shipment tracking numbers: #{tracking_numbers.join(', ')}").deliver_now
      send_canada_post_manual_void_email(tracking_numbers) if carrier == 'Canadapost'
    end
    shipments.label_complete.each do |s|
      s.label_voided!
      s.ready_for_label!
    end
    self.shipping_rates = []
    self.service_code = nil
    self[:state] = 'packaged'
    save!(validate: false)
    { status_code: shipping_result[:status_code],
      status_message: "Please ensure you manually void manual shipments. Shipments voided. #{shipping_result[:status_message]}" }
  else
    { status_code: :error, status_message: "Can't void postage label(s) because the standalone delivery has no postage label(s)." }
  end
end