Class: MailActivity
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- MailActivity
- 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 Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Belongs to collapse
-
#activity ⇒ Activity
CRM activity this mailing fulfills.
-
#mailing ⇒ Mailing
Mailing batch this activity belongs to.
Methods included from Models::Auditable
Has many collapse
-
#shipments ⇒ ActiveRecord::Relation<Shipment>
belongs_to :address, optional: true Shipments (postage labels) created for this mail activity.
-
#uploads ⇒ ActiveRecord::Relation<Upload>
Uploaded files (e.g. combined label PDFs) for this activity.
Class Method Summary collapse
-
.complete ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are complete.
-
.open ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are open.
-
.packaged ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are packaged.
-
.postage_labeled ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are postage labeled.
-
.postage_rated ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are postage rated.
Instance Method Summary collapse
-
#address ⇒ Address?
Destination address — the activity party's main address.
-
#all_labels_pdf ⇒ Upload?
Uploaded combined-labels PDF for this activity.
-
#calculate_postage_rates ⇒ Hash
Fetches postage rates from the shipping provider and stores them.
-
#carrier ⇒ String
(also: #reported_carrier)
Carrier for this activity (delegates to #selected_carrier).
-
#company_name ⇒ Object
Sanitized name for mailing.
-
#default_carrier ⇒ Object
The default postal carrier for the store's country (used when no rate has been selected yet).
-
#formatted_address(separator: '<br>') ⇒ String?
Multi-line formatted destination address.
-
#generate_all_labels_pdf ⇒ void
Combines all completed shipment labels into one uploaded PDF.
-
#generate_labels ⇒ Hash
Purchases postage labels for all awaiting shipments via the shipping provider.
-
#manually_complete? ⇒ Boolean
Whether the activity was completed manually (no printed labels).
-
#name ⇒ String
Human-readable name: task type, activity id, and destination address.
-
#ok_to_delete? ⇒ Boolean
Whether the activity can be deleted (open, with no labeled shipments).
-
#person_name ⇒ Object
Sanitized name for mailing.
-
#ready_to_complete? ⇒ Boolean
Whether any shipment has a completed label (ready to complete the activity).
-
#ready_to_label? ⇒ Boolean
Whether rates are quoted and a shipment is awaiting a label.
-
#ready_to_manually_complete? ⇒ Boolean
Whether no shipment is labeled or awaiting a label (manual completion path).
-
#ready_to_print_labels? ⇒ Boolean
Whether the combined labels PDF exists and can be printed.
-
#ready_to_print_postage_label? ⇒ Boolean
Whether the combined postage label PDF is ready to print.
-
#ready_to_rate? ⇒ Boolean
Whether any shipment is awaiting a rate quote.
-
#ready_to_reset? ⇒ Boolean
Whether all shipments can be deleted so the activity can be reset.
-
#selected_carrier ⇒ Object
Resolve the carrier from a composite service_code like "UPS::03" or "FedEx::FEDEX_GROUND".
-
#selected_service_code ⇒ Object
Extract the raw carrier service code from a composite service_code.
-
#send_canada_post_manual_void_email(tracking_numbers) ⇒ void
Emails admins the tracking numbers that must be voided manually on Canada Post.
-
#store ⇒ Store?
Store the mailing is sent from.
-
#store_country_iso3 ⇒ String?
ISO3 country code of the store's mailing address.
-
#void_shipments ⇒ Hash
Voids all labeled shipments with the carrier and resets the activity.
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Class Method Details
.complete ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are complete. Active Record Scope
55 |
# File 'app/models/mail_activity.rb', line 55 scope :complete, -> { where("mail_activities.state = 'complete'") } |
.open ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are open. Active Record Scope
51 |
# File 'app/models/mail_activity.rb', line 51 scope :open, -> { where("mail_activities.state = 'open'") } |
.packaged ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are packaged. Active Record Scope
52 |
# File 'app/models/mail_activity.rb', line 52 scope :packaged, -> { where("mail_activities.state = 'packaged'") } |
.postage_labeled ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are postage labeled. Active Record Scope
54 |
# File 'app/models/mail_activity.rb', line 54 scope :postage_labeled, -> { where("mail_activities.state = 'postage_labeled'") } |
.postage_rated ⇒ ActiveRecord::Relation<MailActivity>
A relation of MailActivities that are postage rated. Active Record Scope
53 |
# File 'app/models/mail_activity.rb', line 53 scope :postage_rated, -> { where("mail_activities.state = 'postage_rated'") } |
Instance Method Details
#activity ⇒ Activity
CRM activity this mailing fulfills.
Validations:
33 |
# File 'app/models/mail_activity.rb', line 33 belongs_to :activity, optional: true |
#address ⇒ Address?
Destination address — the activity party's main address.
360 361 362 |
# File 'app/models/mail_activity.rb', line 360 def address activity.party.main_address end |
#all_labels_pdf ⇒ Upload?
Uploaded combined-labels PDF for this activity.
337 338 339 |
# File 'app/models/mail_activity.rb', line 337 def all_labels_pdf uploads.find_by(category: 'all_labels_pdf') end |
#calculate_postage_rates ⇒ Hash
Fetches postage rates from the shipping provider and stores them.
203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'app/models/mail_activity.rb', line 203 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 |
#carrier ⇒ String Also known as: reported_carrier
Carrier for this activity (delegates to #selected_carrier).
196 197 198 |
# File 'app/models/mail_activity.rb', line 196 def carrier selected_carrier end |
#company_name ⇒ Object
Sanitized name for mailing
97 98 99 100 101 102 103 |
# File 'app/models/mail_activity.rb', line 97 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_carrier ⇒ Object
The default postal carrier for the store's country (used when no rate has been selected yet)
174 175 176 |
# File 'app/models/mail_activity.rb', line 174 def default_carrier store&.country_iso3 == 'CAN' ? 'Canadapost' : 'USPS' end |
#formatted_address(separator: '<br>') ⇒ String?
Multi-line formatted destination address.
352 353 354 355 356 |
# File 'app/models/mail_activity.rb', line 352 def formatted_address(separator: '<br>') return unless (a = address) a.full_address(true, separator, person_name) end |
#generate_all_labels_pdf ⇒ void
This method returns an undefined value.
Combines all completed shipment labels into one uploaded PDF.
291 292 293 294 295 296 297 298 299 300 301 |
# File 'app/models/mail_activity.rb', line 291 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_labels ⇒ Hash
Purchases postage labels for all awaiting shipments via the shipping provider.
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 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'app/models/mail_activity.rb', line 219 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 = "Labels generated for #{shipments.completed.length} packages. Actual charge: #{store.catalogs.first.currency_symbol}#{actual_shipping_cost}. #{}" save { status_code: status_code, 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 |
#mailing ⇒ Mailing
Mailing batch this activity belongs to.
Validations:
35 |
# File 'app/models/mail_activity.rb', line 35 belongs_to :mailing, optional: true |
#manually_complete? ⇒ Boolean
Whether the activity was completed manually (no printed labels).
157 158 159 |
# File 'app/models/mail_activity.rb', line 157 def manually_complete? complete? and !ready_to_print_labels? end |
#name ⇒ String
Human-readable name: task type, activity id, and destination address.
107 108 109 |
# File 'app/models/mail_activity.rb', line 107 def name "Mail Activity #{activity.activity_type.task_type}, ID: #{activity.id}, to: #{address.full_address(true, ', ')}" end |
#ok_to_delete? ⇒ Boolean
Whether the activity can be deleted (open, with no labeled shipments).
343 344 345 346 347 |
# File 'app/models/mail_activity.rb', line 343 def ok_to_delete? ok = true ok = false unless open? && shipments.all?(&:ok_to_delete_with_mail_activity?) ok end |
#person_name ⇒ Object
Sanitized name for mailing
88 89 90 91 92 93 94 |
# File 'app/models/mail_activity.rb', line 88 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
Whether any shipment has a completed label (ready to complete the activity).
139 140 141 |
# File 'app/models/mail_activity.rb', line 139 def ready_to_complete? shipments.any?(&:label_complete?) end |
#ready_to_label? ⇒ Boolean
Whether rates are quoted and a shipment is awaiting a label.
133 134 135 |
# File 'app/models/mail_activity.rb', line 133 def ready_to_label? postage_rates&.any? and shipments.any?(&:awaiting_label?) end |
#ready_to_manually_complete? ⇒ Boolean
Whether no shipment is labeled or awaiting a label (manual completion path).
145 146 147 |
# File 'app/models/mail_activity.rb', line 145 def ready_to_manually_complete? shipments.none? { |s| s.label_complete? or s.awaiting_label? } end |
#ready_to_print_labels? ⇒ Boolean
Whether the combined labels PDF exists and can be printed.
151 152 153 |
# File 'app/models/mail_activity.rb', line 151 def ready_to_print_labels? all_labels_pdf.present? and shipments.label_complete.any? end |
#ready_to_print_postage_label? ⇒ Boolean
Whether the combined postage label PDF is ready to print.
113 114 115 116 117 |
# File 'app/models/mail_activity.rb', line 113 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
Whether any shipment is awaiting a rate quote.
127 128 129 |
# File 'app/models/mail_activity.rb', line 127 def ready_to_rate? shipments.any?(&:awaiting_label?) end |
#ready_to_reset? ⇒ Boolean
Whether all shipments can be deleted so the activity can be reset.
121 122 123 |
# File 'app/models/mail_activity.rb', line 121 def ready_to_reset? shipments.all?(&:ok_to_delete_with_mail_activity?) end |
#selected_carrier ⇒ Object
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.
180 181 182 183 184 |
# File 'app/models/mail_activity.rb', line 180 def selected_carrier return default_carrier unless service_code&.include?('::') service_code.split('::').first end |
#selected_service_code ⇒ Object
Extract the raw carrier service code from a composite service_code.
"UPS::03" → "03", "USPS::Priority" → "Priority", "Priority" → "Priority"
188 189 190 191 192 |
# File 'app/models/mail_activity.rb', line 188 def selected_service_code return service_code unless service_code&.include?('::') service_code.split('::').last end |
#send_canada_post_manual_void_email(tracking_numbers) ⇒ void
This method returns an undefined value.
Emails admins the tracking numbers that must be voided manually on Canada Post.
331 332 333 |
# File 'app/models/mail_activity.rb', line 331 def send_canada_post_manual_void_email(tracking_numbers) Mailer.canada_post_manual_void_email_for_mail_activity(self, tracking_numbers).deliver end |
#shipments ⇒ ActiveRecord::Relation<Shipment>
belongs_to :address, optional: true
Shipments (postage labels) created for this mail activity.
38 |
# File 'app/models/mail_activity.rb', line 38 has_many :shipments, dependent: :destroy |
#store ⇒ Store?
Store the mailing is sent from.
163 164 165 |
# File 'app/models/mail_activity.rb', line 163 def store @store ||= mailing.store end |
#store_country_iso3 ⇒ String?
ISO3 country code of the store's mailing address.
169 170 171 |
# File 'app/models/mail_activity.rb', line 169 def store_country_iso3 store&.mailing_address&.country_iso3 end |
#uploads ⇒ ActiveRecord::Relation<Upload>
Uploaded files (e.g. combined label PDFs) for this activity.
40 |
# File 'app/models/mail_activity.rb', line 40 has_many :uploads, as: :resource, dependent: :destroy |
#void_shipments ⇒ Hash
Voids all labeled shipments with the carrier and resets the activity.
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'app/models/mail_activity.rb', line 305 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 |