Class: MailActivity

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable
Defined in:
app/models/mail_activity.rb

Overview

== Schema Information

Table name: mail_activities
Database name: primary

id :integer not null, primary key
actual_shipping_cost :decimal(, )
postage_rates :hstore is an Array
service_code :string
state :string
activity_id :integer
address_id :integer
mailing_id :integer

Indexes

index_mail_activities_on_activity_id (activity_id)
index_mail_activities_on_address_id (address_id)
index_mail_activities_on_mailing_id (mailing_id)
index_mail_activities_on_state (state)

Foreign Keys

fk_rails_... (activity_id => activities.id) ON DELETE => cascade
fk_rails_... (address_id => addresses.id)
fk_rails_... (mailing_id => mailings.id)

Constant Summary

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

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

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Class Method Details

.completeActiveRecord::Relation<MailActivity>

A relation of MailActivities that are complete. Active Record Scope

Returns:

See Also:



51
# File 'app/models/mail_activity.rb', line 51

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

.openActiveRecord::Relation<MailActivity>

A relation of MailActivities that are open. Active Record Scope

Returns:

See Also:



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

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

.packagedActiveRecord::Relation<MailActivity>

A relation of MailActivities that are packaged. Active Record Scope

Returns:

See Also:



48
# File 'app/models/mail_activity.rb', line 48

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

.postage_labeledActiveRecord::Relation<MailActivity>

A relation of MailActivities that are postage labeled. Active Record Scope

Returns:

See Also:



50
# File 'app/models/mail_activity.rb', line 50

scope :postage_labeled, -> { where("mail_activities.state = 'postage_labeled'") }

.postage_ratedActiveRecord::Relation<MailActivity>

A relation of MailActivities that are postage rated. Active Record Scope

Returns:

See Also:



49
# File 'app/models/mail_activity.rb', line 49

scope :postage_rated, -> { where("mail_activities.state = 'postage_rated'") }

Instance Method Details

#activityActivity

Returns:

See Also:

Validations:



32
# File 'app/models/mail_activity.rb', line 32

belongs_to :activity, optional: true

#addressObject



311
312
313
# File 'app/models/mail_activity.rb', line 311

def address
  activity.party.main_address
end

#all_labels_pdfObject



295
296
297
# File 'app/models/mail_activity.rb', line 295

def all_labels_pdf
  uploads.find_by(category: 'all_labels_pdf')
end

#calculate_postage_ratesObject



173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'app/models/mail_activity.rb', line 173

def calculate_postage_rates
  res = WyShipping.find_mail_activity_shipping_rates(self)
  rate_hashes = []
  rate_hashes = (res[:request_response][:rates] || []).sort_by { |r| r[:total_charges].to_f } if res[:request_response]
  if (res[:status_code] == :ok) && rate_hashes.any?
    self.postage_rates = rate_hashes # this will stringify the keys in the hash array
    self.service_code = rate_hashes.first[:service_code] if rate_hashes.any?
    # @mail_activity.service_code = "MediaMail" if rate_hashes.detect{|rh| rh[:service_code] == "MediaMail"}
    postage_rate_mail_activity!
    reload
  end
  res
end

#carrierObject Also known as: reported_carrier



168
169
170
# File 'app/models/mail_activity.rb', line 168

def carrier
  selected_carrier
end

#company_nameObject

Sanitized name for mailing



93
94
95
96
97
98
99
# File 'app/models/mail_activity.rb', line 93

def company_name
  cn = activity&.party&.full_name unless activity&.party&.is_person?
  cn ||= address&.party&.full_name unless address&.party&.is_person?
  return unless cn

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

#default_carrierObject

The default postal carrier for the store's country (used when no rate has been selected yet)



148
149
150
# File 'app/models/mail_activity.rb', line 148

def default_carrier
  store&.country_iso3 == 'CAN' ? 'Canadapost' : 'USPS'
end

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



305
306
307
308
309
# File 'app/models/mail_activity.rb', line 305

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

  a.full_address(true, separator, person_name)
end

#generate_all_labels_pdfObject



256
257
258
259
260
261
262
263
264
265
266
# File 'app/models/mail_activity.rb', line 256

def generate_all_labels_pdf
  all_labels = shipments.completed.map(&: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}" }
  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

#generate_labelsObject



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
252
253
254
# File 'app/models/mail_activity.rb', line 187

def generate_labels
  # Guard the ShipEngine call: a MailActivity with no shipments awaiting a
  # label sends an empty `shipment.packages` payload, which ShipEngine
  # rejects with a `ShipEngineRb::Exceptions::ValidationError` that then
  # round-trips through `WyShipping.ship_mail_activity` -> `ErrorReporting.error`
  # to AppSignal (#5307). Surface a user-friendly status instead.
  if shipments.awaiting_label.empty?
    Rails.logger.warn("MailActivity #{id}: generate_labels called with no shipments awaiting label; skipping ShipEngine call")
    return { status_code: :error, status_message: 'No shipments are awaiting a label for this mail activity.' }
  end

  shipping_result = WyShipping.ship_mail_activity(self)
  Rails.logger.info("#{Time.current}: MailActivity 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", mail_activity_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.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}: MailActivity ID #{id}, labels generated END LOG $$$$$$$$$$$$$$$$$$$$$$$$$$$$")
    # save this mail_activity
    save
    # second pass generate the labels from the temporary paths
    label_exceptions = []
    status_code = :ok
    shipments.label_complete.each_with_index do |shipment, i|
      ship_result_label = shipping_result.dig(:shipment, :labels, i)
      old_label_path = ship_result_label[:image]&.path
      # skip when label path is nil
      next unless old_label_path

      label_exceptions << (i + 1) unless shipment.generate_ship_label_pdf(old_label_path, carrier)
    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 = +"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

#mailingMailing

Returns:

See Also:

Validations:



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

belongs_to :mailing, optional: true

#manually_complete?Boolean

Returns:

  • (Boolean)


135
136
137
# File 'app/models/mail_activity.rb', line 135

def manually_complete?
  complete? and !ready_to_print_labels?
end

#nameObject



101
102
103
# File 'app/models/mail_activity.rb', line 101

def name
  "Mail Activity #{activity.activity_type.task_type}, ID: #{activity.id}, to: #{address.full_address(true, ', ')}"
end

#ok_to_delete?Boolean

Returns:

  • (Boolean)


299
300
301
302
303
# File 'app/models/mail_activity.rb', line 299

def ok_to_delete?
  ok = true
  ok = false unless open? && shipments.all?(&:ok_to_delete_with_mail_activity?)
  ok
end

#person_nameObject

Sanitized name for mailing



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

def person_name
  pn = activity.party.full_name if activity.party.is_person?
  pn ||= address.party.full_name if address.party&.is_person?
  return unless pn

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

#ready_to_complete?Boolean

Returns:

  • (Boolean)


123
124
125
# File 'app/models/mail_activity.rb', line 123

def ready_to_complete?
  shipments.any?(&:label_complete?)
end

#ready_to_label?Boolean

Returns:

  • (Boolean)


119
120
121
# File 'app/models/mail_activity.rb', line 119

def ready_to_label?
  postage_rates&.any? and shipments.any?(&:awaiting_label?)
end

#ready_to_manually_complete?Boolean

Returns:

  • (Boolean)


127
128
129
# File 'app/models/mail_activity.rb', line 127

def ready_to_manually_complete?
  shipments.none? { |s| s.label_complete? or s.awaiting_label? }
end

#ready_to_print_labels?Boolean

Returns:

  • (Boolean)


131
132
133
# File 'app/models/mail_activity.rb', line 131

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

#ready_to_print_postage_label?Boolean

Returns:

  • (Boolean)


105
106
107
108
109
# File 'app/models/mail_activity.rb', line 105

def ready_to_print_postage_label?
  (postage_labeled? or complete?) and
    all_labels_pdf.present? and
    shipments.label_complete.any?
end

#ready_to_rate?Boolean

Returns:

  • (Boolean)


115
116
117
# File 'app/models/mail_activity.rb', line 115

def ready_to_rate?
  shipments.any?(&:awaiting_label?)
end

#ready_to_reset?Boolean

Returns:

  • (Boolean)


111
112
113
# File 'app/models/mail_activity.rb', line 111

def ready_to_reset?
  shipments.all?(&:ok_to_delete_with_mail_activity?)
end

#selected_carrierObject

Resolve the carrier from a composite service_code like "UPS::03" or "FedEx::FEDEX_GROUND".
Falls back to the default postal carrier for legacy service codes without a prefix.



154
155
156
157
158
# File 'app/models/mail_activity.rb', line 154

def selected_carrier
  return default_carrier unless service_code&.include?('::')

  service_code.split('::').first
end

#selected_service_codeObject

Extract the raw carrier service code from a composite service_code.
"UPS::03" → "03", "USPS::Priority" → "Priority", "Priority" → "Priority"



162
163
164
165
166
# File 'app/models/mail_activity.rb', line 162

def selected_service_code
  return service_code unless service_code&.include?('::')

  service_code.split('::').last
end

#send_canada_post_manual_void_email(tracking_numbers) ⇒ Object



291
292
293
# File 'app/models/mail_activity.rb', line 291

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

#shipmentsActiveRecord::Relation<Shipment>

belongs_to :address, optional: true

Returns:

See Also:



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

has_many :shipments, dependent: :destroy

#storeObject



139
140
141
# File 'app/models/mail_activity.rb', line 139

def store
  @store ||= mailing.store
end

#store_country_iso3Object



143
144
145
# File 'app/models/mail_activity.rb', line 143

def store_country_iso3
  store&.mailing_address&.country_iso3
end

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



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

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

#void_shipmentsObject



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'app/models/mail_activity.rb', line 268

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_mail_activity(self)
    # going to go merrily along but send admin notification if this didn't properly void
    if shipping_result[:status_code] != :ok
      Mailer.admin_notification("mail_activity#void_shipments for mail_activity #{id} returned error shipping_result[:status_code]: #{shipping_result[:status_code]}",
"mail_activity#void_shipments for mail_activity #{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(&:label_voided!)
    self.postage_rates = []
    self.service_code = nil
    reset_mail_activity!
    { 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 mail activity has no postage label(s)." }
  end
end