Class: Rma
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Rma
- Includes:
- Models::Auditable, Models::LiquidMethods, Models::RmaTransmittable, Models::SupportCaseLinkable
- Defined in:
- app/models/rma.rb,
app/subscribers/rma/creation_note_handler.rb,
app/subscribers/rma/items_added_note_handler.rb
Overview
== Schema Information
Table name: rmas
Database name: primary
id :integer not null, primary key
arrival_date :date
contact_name :string(255)
credited_date :date
customer_reference :string(255)
description :text
insure_return_shipping :boolean default(FALSE), not null
number_of_return_labels_required :integer default(0), not null
original_po_number :string(255)
payment_method :string(255)
replacement_order_type :enum default("SO")
return_declared_value_override :decimal(10, 2)
return_insurance_data :jsonb not null
return_shipping_carrier :string
returned_date :date
rma_number :string(255)
serial_number_state :string
skip_reminders :boolean default(FALSE), not null
state :string(255)
tracking_numbers :string default([]), is an Array
transmission_email :string(255) default([]), is an Array
transmission_fax :string(255) default([]), is an Array
transmission_state :string(255)
uploads_count :integer
created_at :datetime
updated_at :datetime
company_id :integer
creator_id :integer
customer_id :integer
original_invoice_id :integer
original_order_id :integer
precreate_from_delivery_id :integer
redesign_quote_id :integer
return_delivery_id :integer
return_shipping_address_id :integer
send_from_id :integer
ship_from_address_id :integer
shipping_option_id :integer
support_case_id :integer
updater_id :integer
Indexes
idx_creator_id (creator_id)
idx_rma_number (rma_number)
idx_rmas_original_order_id (original_order_id)
idx_trigram_rma_number (COALESCE((rma_number)::text, ''::text) gist_trgm_ops) USING gist
idx_tsearch_rma_number (to_tsvector('english'::regconfig, COALESCE((rma_number)::text, ''::text))) USING gin
index_rmas_on_customer_id (customer_id)
index_rmas_on_original_invoice_id (original_invoice_id)
index_rmas_on_precreate_from_delivery_id (precreate_from_delivery_id) USING hash
index_rmas_on_return_delivery_id (return_delivery_id) USING hash
index_rmas_on_shipping_option_id (shipping_option_id)
index_rmas_on_support_case_id (support_case_id)
index_rmas_on_tracking_numbers (tracking_numbers) USING gin
Foreign Keys
fk_rails_... (precreate_from_delivery_id => deliveries.id)
fk_rails_... (return_delivery_id => deliveries.id)
Defined Under Namespace
Classes: CreationNoteHandler, ItemsAddedNoteHandler, ReplacementOrderError
Constant Summary collapse
- PAYMENT_METHODS =
Payment methods.
['Original Payment Method', 'Check'].freeze
- DEFAULT_ORDER_REPLACEMENT_TYPE =
Default order replacement type.
'SO'- REFERENCE_NUMBER_PATTERN =
Regex pattern matching reference number.
/^RMA\d+$/i- PENDING_ORDERS_STATES =
Recognised pending orders states.
%w[pending pending_payment in_cr_hold profit_review crm_back_order pending_release_authorization needs_serial_number_reservation].freeze
- NOTIFICATIONS_RMA_STATES =
Recognised notifications rma states.
%w[auto_return_review awaiting_inspection partially_returned returned credit_in_process credited_partially_refunded].freeze
- EMAILS_FOR_NOTIFICATIONS =
Emails for notifications.
%w[bwinings@warmlyyours.com ar@warmlyyours.com].freeze
- URLS =
Urls.
{ 'US' => { 'UPS' => { url_name: 'www.ups.com/dropoff', url: 'https://www.ups.com/dropoff' }, 'FedEx' => { url_name: 'www.fedex.com/locate', url: 'https://www.fedex.com/locate' }, 'USPS' => { url_name: 'tools.usps.com/find-location.htm', url: 'https://tools.usps.com/find-location.htm' } }, 'CA' => { 'UPS' => { url_name: 'www.ups.com/ca/en/dropoff', url: 'https://www.ups.com/ca/en/dropoff' }, 'FedEx' => { url_name: 'www.fedex.com/locate', url: 'https://www.fedex.com/locate' }, 'Purolator' => { url_name: 'www.purolator.com/en/shipping-locations', url: 'https://www.purolator.com/en/shipping-locations' }, 'Canpar' => { url_name: 'www.canpar.com/en/ship/drop-off-locations.jsp', url: 'https://www.canpar.com/en/ship/drop-off-locations.jsp' }, 'Canadapost' => { url_name: 'www.canadapost-postescanada.ca/information/app/fpo/personal/findpostoffice', url: 'https://www.canadapost-postescanada.ca/information/app/fpo/personal/findpostoffice' } } }.freeze
- RETURN_OPEN_STATES =
RMA states in which the customer may still legitimately obtain a Canada
Post return label — i.e. goods are still expected back. Post-arrival and
terminal states (awaiting_inspection, returned, voided, credit_*) are
excluded so a stale or already-completed RMA number can't pull a new label. %w[requested awaiting_return auto_return_review partially_returned].freeze
- RETURN_SHIPPING_METHODS =
Return shipping methods.
{ '1' => 'ground', '2' => 'standard' }.freeze
- PAPERLESS_LABEL_BROKER_RETAIL_URL =
USPS Label Broker drop-off finders (counter / self-service kiosk).
'https://tools.usps.com/locations/home.htm?filters=LBRORETAIL'- PAPERLESS_LABEL_BROKER_KIOSK_URL =
'https://tools.usps.com/locations/home.htm?filters=LBROSSK'- CANADA_POST_RETURNS_POLICY_ID =
Canada Post Manage Returns policy backing our CA returns (No RA number
required; the customer enters their RMA in the Return No. field and Canada
Post issues the paperless QR). The policy ID alone is accepted at any Canada
Post outlet (Canada Post's "Method 2"), so it must be printable on its own —
not just buried inside the link — for customers who print their instructions. 'PR267361'- CANADA_POST_RETURNS_POLICY_URL =
"https://www.canadapost-postescanada.ca/pr-rc/en/get-return-label?id=#{CANADA_POST_RETURNS_POLICY_ID}"- PAPERLESS_BLOCK_RE =
Markers wrapping the paperless block in the rendered RMA email body, so
Rma#inject_paperless_block! can re-splice it after labels exist. /<!--paperless:start-->.*?<!--paperless:end-->/m
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#allow_invoice_link ⇒ Object
Returns the value of attribute allow_invoice_link.
-
#company_id ⇒ void
readonly
Validates company id, customer id, return shipping address id, payment method.
-
#customer_id ⇒ void
readonly
Validates company id, customer id, return shipping address id, payment method.
-
#customer_reference ⇒ void
readonly
Validates customer reference.
-
#original_invoice_id ⇒ void
readonly
Validates original invoice id.
-
#original_order_id ⇒ void
readonly
Validates original order id.
-
#payment_method ⇒ void
readonly
Validates company id, customer id, return shipping address id, payment method.
-
#return_shipping_address_id ⇒ void
readonly
Validates company id, customer id, return shipping address id, payment method.
-
#rma_number ⇒ void
readonly
Validates rma number.
-
#ship_from_address_id ⇒ void
readonly
Validates company id, customer id, return shipping address id, payment method.
-
#shipping_option_id ⇒ void
readonly
Validates shipping option id, should validate shipping option.
-
#skip_return_label_validation ⇒ Object
Returns the value of attribute skip_return_label_validation.
-
#skip_shipping_option_validation ⇒ Object
Returns the value of attribute skip_shipping_option_validation.
-
#transmission_email ⇒ void
readonly
Validates transmission email.
-
#transmission_fax ⇒ void
readonly
Validates transmission fax.
Attributes included from Models::SupportCaseLinkable
Belongs to collapse
-
#company ⇒ Company?
The associated company.
-
#creator ⇒ Employee?
The associated creator.
-
#customer ⇒ Customer?
The associated customer.
-
#original_invoice ⇒ Invoice?
The associated original invoice.
-
#original_order ⇒ Order?
The associated original order.
-
#precreate_from_delivery ⇒ Delivery?
The associated precreate from delivery.
-
#redesign_quote ⇒ Quote?
The associated redesign quote.
-
#return_delivery ⇒ Delivery?
The associated return delivery.
-
#return_shipping_address ⇒ Address?
The associated return shipping address.
-
#send_from ⇒ Employee?
The associated send from.
-
#ship_from_address ⇒ Address?
The associated ship from address.
-
#shipping_option ⇒ ShippingOption?
The associated shipping option.
Methods included from Models::Auditable
Has one collapse
-
#invoice ⇒ Invoice?
The associated invoice.
-
#quote ⇒ Quote?
The associated quote.
Has many collapse
-
#activities ⇒ ActiveRecord::Associations::CollectionProxy
The associated activities.
-
#communications ⇒ ActiveRecord::Associations::CollectionProxy
The associated communications.
-
#credit_deliveries ⇒ ActiveRecord::Associations::CollectionProxy
The associated credit deliveries.
-
#credit_memos ⇒ ActiveRecord::Associations::CollectionProxy
The associated credit memos.
-
#credit_order_line_items ⇒ ActiveRecord::Associations::CollectionProxy
The associated credit order line items.
-
#credit_orders ⇒ ActiveRecord::Associations::CollectionProxy
The associated credit orders.
-
#orders ⇒ ActiveRecord::Associations::CollectionProxy
The associated orders.
-
#payments ⇒ ActiveRecord::Associations::CollectionProxy
The associated payments.
-
#pending_orders ⇒ ActiveRecord::Associations::CollectionProxy
The associated pending orders.
-
#return_shipments ⇒ ActiveRecord::Associations::CollectionProxy
The associated return shipments.
-
#rma_items ⇒ ActiveRecord::Associations::CollectionProxy
The associated rma items.
-
#serial_numbers ⇒ ActiveRecord::Associations::CollectionProxy
The associated serial numbers.
-
#uploads ⇒ ActiveRecord::Associations::CollectionProxy
The associated uploads.
Delegated Instance Attributes collapse
-
#rma_inspect_email ⇒ Object
Alias for Company#rma_inspect_email.
Class Method Summary collapse
-
.awaiting_return ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are awaiting return.
-
.canada_post_return_valid?(rma_number) ⇒ Boolean
Canada Post Manage Returns RA-number validation.
-
.canada_post_returnable ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are canada post returnable.
-
.contains_tracking_number ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are contains tracking number.
-
.create_rma_from_delivery(d) ⇒ Object
Create rma from delivery.
-
.in_auto_return_review ⇒ Object
In auto return review.
-
.in_awaiting_inspection ⇒ Object
In awaiting inspection.
-
.return_labels_select_options ⇒ Object
Return labels select options.
-
.returned_and_need_attention_immediately ⇒ Object
Returned and need attention immediately.
-
.rma_active ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are rma active.
-
.rma_count(company_id = nil, where_conditions = nil, where_not_conditions = nil) ⇒ Object
Rma count.
-
.states_for_select ⇒ Object
States for select.
-
.transmission_state_for_select ⇒ Object
Transmission state for select.
-
.with_credit_memos_not_transmitted_and_are_not_fullyoffset ⇒ Object
With credit memos not transmitted and are not fullyoffset.
-
.with_credit_memos_transmitted_and_are_not_fullyoffset ⇒ Object
With credit memos transmitted and are not fullyoffset.
-
.with_external_tracking_number ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are with external tracking number.
Instance Method Summary collapse
-
#active_rma_items ⇒ Object
Active rma items.
-
#all_credit_memos_fully_offset? ⇒ Object
Whether all credit memos fully offset.
-
#all_credit_memos_partially_refunded? ⇒ Object
Whether all credit memos partially refunded.
-
#all_credit_orders_awaiting_return? ⇒ Object
Whether all credit orders awaiting return.
-
#all_items_requested? ⇒ Object
Whether all items requested.
-
#all_items_requested_and_ready_to_return? ⇒ Object
Whether all items requested and ready to return.
-
#all_items_returned? ⇒ Object
Whether all items returned.
-
#all_items_voided? ⇒ Object
Whether all items voided.
-
#all_items_will_not_be_returned? ⇒ Boolean
True when every top-level item still in play is flagged "Will be returned? = NO", so the customer is keeping all of it and no parcel is inbound.
-
#all_uploads ⇒ Object
All uploads.
-
#any_credit_memo_printed? ⇒ Object
Whether any credit memo printed.
-
#any_items_awaiting_inspection? ⇒ Object
Whether any items awaiting inspection.
-
#any_items_requested? ⇒ Object
Whether any items requested.
-
#any_rejected_items? ⇒ Boolean
True when any item or kit component was flagged non-resalable or not returned during inspection.
-
#append_non_kit_credit_line(order, rma_item, good_qty, credit_percentage, from_ci_invoice) ⇒ Object
Appends a single credit line for a non-kit item, crediting +good_qty+ units.
-
#arrival_datetime ⇒ Object
Arrival datetime.
-
#auto_return_rma_items ⇒ Object
Auto return rma items.
-
#build_activity ⇒ Object
Build activity.
-
#can_be_edited? ⇒ Object
Whether can be edited.
-
#can_be_received? ⇒ Object
Whether can be received.
-
#can_be_transmitted? ⇒ Object
Whether can be transmitted.
-
#can_be_unreturned? ⇒ Object
Whether can be unreturned.
-
#can_be_unvoided? ⇒ Object
Whether can be unvoided.
-
#can_be_voided? ⇒ Object
Whether can be voided.
-
#can_edit_return_labels? ⇒ Object
Whether can edit return labels.
-
#canada_post_return? ⇒ Boolean
True when the customer's return is being made via Canada Post — i.e.
-
#canada_post_return_html ⇒ Object
HTML block embedded in the customer return email via the single
{{ rma.canada_post_return_html }}tag in the RMA email template. -
#capture_seeded_credit_prices(order) ⇒ Hash
Snapshots the invoice-derived prices seeded on each credit line (list/discounted prices for plain items, prorated shares for kit components) before the autosave chain re-prices them to catalog amount.
-
#create_credit_order(rma_item_ids) ⇒ Object
Create credit order.
-
#create_documents ⇒ Object
Create documents.
-
#create_replacement_order ⇒ Object
Create replacement order.
-
#create_return_delivery ⇒ Object
Create return delivery.
-
#credit_available ⇒ Object
Credit available.
-
#credit_available_before_tax ⇒ Object
Credit available before tax.
-
#credit_order_shipments ⇒ Object
Credit order shipments.
-
#credit_total ⇒ Object
Credit total.
-
#credit_total_before_tax ⇒ Object
Credit total before tax.
-
#crm_link ⇒ Object
Crm link.
-
#customer_name ⇒ Object
Customer name.
-
#earliest_return_label_ship_date ⇒ Date?
Returns the earliest ship date across generated return labels.
-
#effective_return_delivery ⇒ Object
Effective return delivery.
-
#enqueue_external_tracking_registration_if_needed ⇒ void
Enqueues ShipEngine registration for new external numbers or numbers whose effective carrier changed.
-
#exclude_manually_initiated_event?(event) ⇒ Object
Whether exclude manually initiated event.
-
#external_return_labels? ⇒ Object
Whether external return labels.
-
#full_invoice_returned? ⇒ Object
Whether full invoice returned.
-
#generate_and_send_return_email_to_customer ⇒ Object
Generate and send return email to customer.
-
#generate_return_instructions_pdf ⇒ Object
Generate return instructions pdf.
-
#generate_return_labels ⇒ Object
Generate return labels.
-
#handle_return_shipment_tracking_state_updated(return_shipment) ⇒ Object
Handle return shipment tracking state updated.
-
#has_auto_return_items? ⇒ Object
Whether has auto return items.
-
#has_different_replacement_items? ⇒ Object
Whether has different replacement items.
-
#has_no_active_items? ⇒ Boolean
+true+ when there are no non-voided lines (+RmaItem.active+ / +where.not(state: 'voided')+).
-
#has_replacement_items? ⇒ Object
Whether has replacement items.
-
#has_rma_items_requested? ⇒ Object
Whether has rma items requested.
-
#has_same_replacement_items? ⇒ Object
Whether has same replacement items.
-
#inherit_ci_invoice_line_values(order) ⇒ Object
For CI invoices (e.g. Amazon Seller Central), the marketplace sets the per-unit price, promotional discount, and tax on the original invoice line.
-
#instructions_file_name(with_extension = true) ⇒ Object
Instructions file name.
-
#invoice_add_blocked_for_kit_component_line?(component_line_item) ⇒ Boolean
add-items UI: true if this kit component line cannot be added (the parent kit line is already on the RMA).
-
#invoice_add_blocked_for_kit_parent_line?(parent_line_item) ⇒ Boolean
add-items UI: true if this parent kit invoice line cannot be added (a component line is already on the RMA).
-
#invoice_line_already_on_return_list?(line_item) ⇒ Boolean
add-items UI: true if this invoice line already has a return row (each line at most once).
-
#items_partially_returned? ⇒ Object
Whether items partially returned.
-
#labels_file_name(with_extension = true) ⇒ Object
Labels file name.
-
#legacy_return_shipments ⇒ Object
Legacy return shipments.
-
#next_step_after_items_created ⇒ Object
Next step after items created.
-
#no_items_returned? ⇒ Object
Whether no items returned.
-
#original_invoice_select ⇒ Object
Original invoice select.
-
#original_order_ref ⇒ Object
Original order ref.
-
#original_order_ref=(ref) ⇒ Object
Original order ref.
-
#original_order_select ⇒ Object
Original order select.
-
#paperless_return? ⇒ Boolean
True when this return has USPS Label Broker paperless QR codes (one or more return shipments carry a
paperless_qr_png). -
#paperless_return_html ⇒ Object
HTML block embedded in the customer return email via the single
{{ rma.paperless_return_html }}tag in the RMA email template. -
#payment_status ⇒ Object
Payment status.
-
#possible_events ⇒ Object
Possible events.
-
#possible_events_for_select ⇒ Object
Possible events for select.
-
#post_communication_sent_hook ⇒ Object
Post communication sent hook.
-
#primary_party ⇒ Object
Primary party.
-
#receive_item_events_for_select ⇒ Object
Receive item events for select.
-
#record_canada_post_return_inquiry(additional_info: nil, rsa_number_issue_date: nil) ⇒ void
Records the external reference Canada Post forwards (the customer-entered order/reference number — our SO number, an Amazon or Home Depot order number, etc. — plus the RA issue date) as a note on this RMA, so staff can cross-reference the Canada Post return to the originating retailer order.
-
#record_creation_activity ⇒ void
Records a creation note on this RMA so every RMA carries a searchable baseline of who created it, what it links to, and the stated reason — the context warehouse staff otherwise have to dig out of emails, the original order, or the retailer portal.
-
#record_items_added_activity(items, added_ids: nil) ⇒ void
Records a note listing the RMA's current top-level items with their return reason codes (expanded to the reason-code description), so the activities tab answers "what is coming back and why" without opening the items tab.
-
#regenerate_return_documents! ⇒ Object
Regenerates the customer-facing return documents WITHOUT touching the purchased carrier labels (no void, no new label buy): each letter-format label PDF is rebuilt from the carrier label PDF already on file, then the return-instructions PDF is re-rendered from them.
-
#rejected_rma_items ⇒ Object
Items (including kit components, which are rows in the same table) that were flagged as non-resalable on inspection or were never returned.
-
#restore_seeded_credit_prices(order, seeded_credit_prices) ⇒ void
Re-applies the invoice-derived prices captured in +create_credit_order+ before the +disable_auto_coupon+ toggle's +reset_discount+ re-priced every parent line to its catalog amount.
-
#return_address ⇒ Object
Return address.
-
#return_instructions ⇒ Object
Return instructions.
-
#return_label_issues ⇒ Object
Return label issues.
-
#return_label_required? ⇒ Object
(also: #return_label_required)
Whether return label required.
-
#return_labels ⇒ Object
Return labels.
-
#return_labels_correctly_generated? ⇒ Object
Whether return labels correctly generated.
-
#return_labels_generated? ⇒ Boolean
True once at least one return label has actually been purchased.
-
#return_shipping_method ⇒ Object
Return shipping method.
-
#returned_rma_items ⇒ Object
Returned rma items.
-
#rma_items_awaiting_inspection ⇒ Object
Rma items awaiting inspection.
-
#rma_suggested_quotes_options ⇒ Object
Rma suggested quotes options.
-
#send_inspection_notification_to_customer ⇒ Object
Send inspection notification to customer.
-
#send_rejection_notification_to_customer ⇒ Object
Send rejection notification to customer.
-
#send_return_confirmation_email_to_customer ⇒ Object
Send return confirmation email to customer.
-
#send_return_email_to_customer(keep_as_draft: false) ⇒ Object
Send return email to customer.
-
#service_only? ⇒ Object
Whether service only.
-
#set_default_notification_channels ⇒ Object
Set default notification channels.
-
#ship_from_attributes ⇒ Object
Ship from attributes.
-
#ship_to_attributes ⇒ Object
Ship to attributes.
-
#shipment_tracking_numbers ⇒ Array<ShipmentTrackingNumber>
Combines generated-label and externally supplied inbound tracking numbers.
-
#should_validate_shipping_option? ⇒ Object
Whether should validate shipping option.
-
#split_line_discount_by_coupon(discount_total, source_discounts) ⇒ Hash{LineDiscount => BigDecimal}
Attributes a credit line's total discount back to the source invoice line's coupons, split in proportion to each coupon's amount so the credit mirrors the invoice's coupon breakdown.
-
#stores_for_return_select ⇒ Object
Stores for return select.
-
#suggested_contact_names ⇒ Object
Suggested contact names.
-
#to_s ⇒ Object
(also: #name, #reference_number)
To s.
-
#unreturn_all_items ⇒ Object
Unreturn all items.
-
#unvoid_all_items ⇒ Object
Unvoid all items.
-
#unvoid_all_items_and_self ⇒ Object
Unvoid all items and self.
-
#update_draft_communications_with_new_attachments ⇒ Object
Update draft communications with new attachments.
-
#update_linked_credit_orders(allow_transition_failure: true) ⇒ Object
This pushes the credit order to the return state.
-
#versions_for_audit_trail(_params = {}) ⇒ Object
Versions for audit trail.
-
#versions_for_dates_tracker ⇒ Object
Versions for dates tracker.
-
#versions_for_state_tracker ⇒ Object
Versions for state tracker.
-
#void_all_items ⇒ Object
Void all items.
-
#void_all_items_and_self ⇒ Object
Void all items and self.
Methods included from Models::SupportCaseLinkable
#support_case_ref, #support_case_ref=
Methods included from Models::RmaTransmittable
#rma_available_email_addresses, #rma_available_fax_numbers
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
Instance Attribute Details
#allow_invoice_link ⇒ Object
Returns the value of attribute allow_invoice_link.
393 394 395 |
# File 'app/models/rma.rb', line 393 def allow_invoice_link @allow_invoice_link end |
#company_id ⇒ void (readonly)
This method returns an undefined value.
Validates company id, customer id, return shipping address id, payment method.
Validations:
358 359 |
# File 'app/models/rma.rb', line 358 validates :company_id, :customer_id, :return_shipping_address_id, :payment_method, :rma_number, :ship_from_address_id, presence: true |
#customer_id ⇒ void (readonly)
This method returns an undefined value.
Validates company id, customer id, return shipping address id, payment method.
Validations:
358 359 |
# File 'app/models/rma.rb', line 358 validates :company_id, :customer_id, :return_shipping_address_id, :payment_method, :rma_number, :ship_from_address_id, presence: true |
#customer_reference ⇒ void (readonly)
This method returns an undefined value.
Validates customer reference.
372 373 374 |
# File 'app/models/rma.rb', line 372 validates :customer_reference, presence: { if: proc { |rma| rma.customer.present? && rma.customer.requires_rma_reference? }, message: 'is required for this customer' } |
#original_invoice_id ⇒ void (readonly)
This method returns an undefined value.
Validates original invoice id.
366 |
# File 'app/models/rma.rb', line 366 validates :original_invoice_id, numericality: { allow_nil: true } |
#original_order_id ⇒ void (readonly)
This method returns an undefined value.
Validates original order id.
363 |
# File 'app/models/rma.rb', line 363 validates :original_order_id, numericality: { allow_nil: true } |
#payment_method ⇒ void (readonly)
This method returns an undefined value.
Validates company id, customer id, return shipping address id, payment method.
Validations:
358 359 |
# File 'app/models/rma.rb', line 358 validates :company_id, :customer_id, :return_shipping_address_id, :payment_method, :rma_number, :ship_from_address_id, presence: true |
#return_shipping_address_id ⇒ void (readonly)
This method returns an undefined value.
Validates company id, customer id, return shipping address id, payment method.
Validations:
358 359 |
# File 'app/models/rma.rb', line 358 validates :company_id, :customer_id, :return_shipping_address_id, :payment_method, :rma_number, :ship_from_address_id, presence: true |
#rma_number ⇒ void (readonly)
This method returns an undefined value.
Validates rma number.
Validations:
355 |
# File 'app/models/rma.rb', line 355 validates :rma_number, uniqueness: true |
#ship_from_address_id ⇒ void (readonly)
This method returns an undefined value.
Validates company id, customer id, return shipping address id, payment method.
Validations:
358 359 |
# File 'app/models/rma.rb', line 358 validates :company_id, :customer_id, :return_shipping_address_id, :payment_method, :rma_number, :ship_from_address_id, presence: true |
#shipping_option_id ⇒ void (readonly)
This method returns an undefined value.
Validates shipping option id, should validate shipping option.
369 |
# File 'app/models/rma.rb', line 369 validates :shipping_option_id, presence: { if: :should_validate_shipping_option?, message: 'is required to generate return labels' } |
#skip_return_label_validation ⇒ Object
Returns the value of attribute skip_return_label_validation.
393 394 395 |
# File 'app/models/rma.rb', line 393 def skip_return_label_validation @skip_return_label_validation end |
#skip_shipping_option_validation ⇒ Object
Returns the value of attribute skip_shipping_option_validation.
393 394 395 |
# File 'app/models/rma.rb', line 393 def skip_shipping_option_validation @skip_shipping_option_validation end |
#transmission_email ⇒ void (readonly)
This method returns an undefined value.
Validates transmission email.
377 |
# File 'app/models/rma.rb', line 377 validates :transmission_email, email_format: true, allow_nil: true |
#transmission_fax ⇒ void (readonly)
This method returns an undefined value.
Validates transmission fax.
380 |
# File 'app/models/rma.rb', line 380 validates :transmission_fax, phone_format: true, allow_nil: true |
Class Method Details
.awaiting_return ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are awaiting return. Active Record Scope
197 |
# File 'app/models/rma.rb', line 197 scope :awaiting_return, -> { where(state: 'awaiting_return') } |
.canada_post_return_valid?(rma_number) ⇒ Boolean
Canada Post Manage Returns RA-number validation. True iff an RMA with this
number exists, is still open/eligible for return, and ships from Canada
(the only origin our Canada Post return policy can carry). Backs the
+Api::V1::CanadaPost::ReturnsController+ web service Canada Post calls
before issuing a box-free/paperless QR return label.
229 230 231 232 233 |
# File 'app/models/rma.rb', line 229 def self.canada_post_return_valid?(rma_number) return false if rma_number.blank? canada_post_returnable.exists?(rma_number: rma_number) end |
.canada_post_returnable ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are canada post returnable. Active Record Scope
216 217 218 |
# File 'app/models/rma.rb', line 216 scope :canada_post_returnable, lambda { joins(:ship_from_address).merge(Address.canada).where(state: RETURN_OPEN_STATES) } |
.contains_tracking_number ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are contains tracking number. Active Record Scope
201 202 203 204 205 206 |
# File 'app/models/rma.rb', line 201 scope :contains_tracking_number, ->(tracking_number) { left_outer_joins(:return_shipments).where.any_of( where.overlap(tracking_numbers: [tracking_number]), { shipments: { tracking_number: tracking_number } } ).distinct } |
.create_rma_from_delivery(d) ⇒ Object
Create rma from delivery.
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 |
# File 'app/models/rma.rb', line 1617 def self.create_rma_from_delivery(d) return d.precreated_rma if d.precreated_rma.present? && !d.precreated_rma.voided? ship_from_address = d.destination_address.is_warehouse? ? (d.order.customer.shipping_address || d.order.customer.billing_address || d.order.customer.addresses.first) : d.destination_address shipping_option_id = if d.shipping_option.country == 'US' 1 elsif d.shipping_option.country == 'CA' 4 end rma = Rma.new(company_id: d.order.company.id, customer_id: d.order.customer.id, original_order_id: d.order.id, ship_from_address_id: ship_from_address.id, return_shipping_address_id: d.order.store.warehouse_address_id, number_of_return_labels_required: d.shipments.where.not(state: %w[label_voided manually_voided]).size, shipping_option_id: shipping_option_id, payment_method: 'Original Payment Method', original_po_number: d.order.po_number, transmission_email: d.order.transmission_email, transmission_fax: d.order.transmission_fax, precreate_from_delivery_id: d.id, support_case_id: d.order.support_case_ids.first, contact_name: (d.order.contact.present? ? d.order.contact.full_name : d.order.customer.full_name), description: 'Precreated RMA', customer_reference: d.order.customer_reference) d.order.line_items.parents_only.non_shipping.each do |li| reason_code = li.item.sku == 'SHORTSTOP' ? 'SSR' : 'TBD' rma.rma_items << RmaItem.new(returned_item_id: li.item.id, returned_line_item_id: li.id, returned_item_quantity: li.quantity, returned_item_location: 'AVAILABLE', returned_reason: reason_code, liable: 'WARMLYYOURS', under_warranty: true, replacement_required: false) end added_items = rma.rma_items.select(&:new_record?) rma.save! rma.publish_event(Events::RmaItemsAdded, data: { rma_id: rma.id, added_rma_item_ids: added_items.map(&:id) }) rma.create_return_delivery rma.generate_return_labels if rma.return_label_required? rma end |
.in_auto_return_review ⇒ Object
In auto return review.
967 968 969 |
# File 'app/models/rma.rb', line 967 def self.in_auto_return_review where(state: 'auto_return_review') end |
.in_awaiting_inspection ⇒ Object
In awaiting inspection.
974 975 976 |
# File 'app/models/rma.rb', line 974 def self.in_awaiting_inspection where(state: 'awaiting_inspection') end |
.return_labels_select_options ⇒ Object
Return labels select options.
629 630 631 |
# File 'app/models/rma.rb', line 629 def self. [['None', 0], ['1', 1], ['2', 2], ['3', 3], ['4', 4], ['5', 5], ['Auto', '-1']] end |
.returned_and_need_attention_immediately ⇒ Object
Returned and need attention immediately.
981 982 983 984 985 986 987 988 |
# File 'app/models/rma.rb', line 981 def self.returned_and_need_attention_immediately returned = where(state: 'returned').where.not(returned_date: nil).order(returned_date: :desc) rmas_list = [] returned.each do |rma| rmas_list << rma if rma.returned_date&.working_days_until(Date.current).to_i > 3 end rmas_list end |
.rma_active ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are rma active. Active Record Scope
196 |
# File 'app/models/rma.rb', line 196 scope :rma_active, -> { where(state: %w[awaiting_return requested awaiting_inspection credit_in_process partially_returned returned]) } |
.rma_count(company_id = nil, where_conditions = nil, where_not_conditions = nil) ⇒ Object
Rma count.
956 957 958 959 960 961 962 |
# File 'app/models/rma.rb', line 956 def self.rma_count(company_id = nil, where_conditions = nil, where_not_conditions = nil) r = Rma.order(:id) r = r.where(company_id: company_id) unless company_id.nil? r = r.where(where_conditions) unless where_conditions.nil? r = r.where.not(where_not_conditions) unless where_not_conditions.nil? r.count end |
.states_for_select ⇒ Object
States for select.
1388 1389 1390 |
# File 'app/models/rma.rb', line 1388 def self.states_for_select state_machines[:state].states.sort_by(&:human_name).map { |s| [s.value.titleize, s.value] } end |
.transmission_state_for_select ⇒ Object
Transmission state for select.
1395 1396 1397 |
# File 'app/models/rma.rb', line 1395 def self.transmission_state_for_select %w[awaiting_transmission in_transmission_queue transmitted].map { |e| [e.titleize, e] } end |
.with_credit_memos_not_transmitted_and_are_not_fullyoffset ⇒ Object
With credit memos not transmitted and are not fullyoffset.
993 994 995 996 997 998 999 1000 1001 |
# File 'app/models/rma.rb', line 993 def self.with_credit_memos_not_transmitted_and_are_not_fullyoffset with_credit_memos_not_transmitted = where(id: CreditMemo.where(state: %w[printed processing_refund partially_offset]).where(transmission_state: 'awaiting_transmission').map(&:rma_id).uniq.compact).where.not(returned_date: nil).order(returned_date: :desc) rmas_list = [] with_credit_memos_not_transmitted.each do |rma| rmas_list << rma if rma.returned_date&.working_days_until(Date.current).to_i > 3 end rmas_list end |
.with_credit_memos_transmitted_and_are_not_fullyoffset ⇒ Object
With credit memos transmitted and are not fullyoffset.
1006 1007 1008 1009 1010 1011 1012 1013 |
# File 'app/models/rma.rb', line 1006 def self.with_credit_memos_transmitted_and_are_not_fullyoffset with_credit_memos_transmitted = where(id: CreditMemo.where(state: %w[printed processing_refund partially_offset]).where(transmission_state: 'transmitted').map(&:rma_id).uniq.compact).where.not(returned_date: nil).order(returned_date: :desc) rmas_list = [] with_credit_memos_transmitted.each do |rma| rmas_list << rma if rma.returned_date && (Date.current - rma.returned_date).to_i > 90 end rmas_list end |
.with_external_tracking_number ⇒ ActiveRecord::Relation<Rma>
A relation of Rmas that are with external tracking number. Active Record Scope
198 199 200 |
# File 'app/models/rma.rb', line 198 scope :with_external_tracking_number, ->(tracking_number) { where.overlap(tracking_numbers: [tracking_number]) } |
Instance Method Details
#active_rma_items ⇒ Object
Active rma items.
1718 1719 1720 |
# File 'app/models/rma.rb', line 1718 def active_rma_items rma_items.active.reload end |
#activities ⇒ ActiveRecord::Associations::CollectionProxy
The associated activities.
167 |
# File 'app/models/rma.rb', line 167 has_many :activities, as: :resource, dependent: :nullify |
#all_credit_memos_fully_offset? ⇒ Object
Whether all credit memos fully offset.
1148 1149 1150 |
# File 'app/models/rma.rb', line 1148 def all_credit_memos_fully_offset? credit_memos.present? && credit_memos.all?(&:fully_offset?) end |
#all_credit_memos_partially_refunded? ⇒ Object
Whether all credit memos partially refunded.
1141 1142 1143 |
# File 'app/models/rma.rb', line 1141 def all_credit_memos_partially_refunded? credit_memos.present? && (credit_memos.all?(&:partially_offset?) || credit_memos.any?(&:partially_offset?)) end |
#all_credit_orders_awaiting_return? ⇒ Object
Whether all credit orders awaiting return.
1416 1417 1418 |
# File 'app/models/rma.rb', line 1416 def all_credit_orders_awaiting_return? credit_orders.all? { |co| co.awaiting_return? || co.cancelled? } end |
#all_items_requested? ⇒ Object
Whether all items requested.
1694 1695 1696 |
# File 'app/models/rma.rb', line 1694 def all_items_requested? rma_items.reload.active.present? and rma_items.reload.active.all?(&:requested?) end |
#all_items_requested_and_ready_to_return? ⇒ Object
Whether all items requested and ready to return.
1423 1424 1425 1426 1427 1428 |
# File 'app/models/rma.rb', line 1423 def all_items_requested_and_ready_to_return? all_items_requested? && return_labels_correctly_generated? # any_items_requested? && credit_orders.all? do |co| # co.awaiting_return? || co.cancelled? # end && return_labels_correctly_generated? end |
#all_items_returned? ⇒ Object
Whether all items returned.
1680 1681 1682 |
# File 'app/models/rma.rb', line 1680 def all_items_returned? rma_items.reload.active.all? { |rma_item| rma_item.returned? || rma_item.voided? } end |
#all_items_voided? ⇒ Object
Whether all items voided.
1187 1188 1189 |
# File 'app/models/rma.rb', line 1187 def all_items_voided? rma_items.reload.all?(&:voided?) end |
#all_items_will_not_be_returned? ⇒ Boolean
True when every top-level item still in play is flagged
"Will be returned? = NO", so the customer is keeping all of it and no parcel
is inbound.
Kit components are excluded because +RmaItem#expand_kit_components!+ creates
children without copying the flag: they always read false and would mask a
kit parent the user did mark. Guarded on +any?+ so a brand-new RMA with no
items yet is not mistaken for one whose items are all staying put —
+at_least_one_rma_item_if_return_label_required+ must keep firing there.
Filtered in Ruby rather than by scope so unsaved nested-attribute changes
count, matching +all_items_voided?+.
1204 1205 1206 1207 |
# File 'app/models/rma.rb', line 1204 def all_items_will_not_be_returned? items = rma_items.reload.reject { |ri| ri.voided? || ri.child? } items.any? && items.all?(&:will_not_be_returned?) end |
#all_uploads ⇒ Object
All uploads.
943 944 945 946 947 948 |
# File 'app/models/rma.rb', line 943 def all_uploads all_uploads = [] all_uploads += uploads all_uploads += return_delivery&.uploads || [] all_uploads.uniq end |
#any_credit_memo_printed? ⇒ Object
Whether any credit memo printed.
1134 1135 1136 |
# File 'app/models/rma.rb', line 1134 def any_credit_memo_printed? credit_memos.present? && credit_memos.all?(&:printed?) end |
#any_items_awaiting_inspection? ⇒ Object
Whether any items awaiting inspection.
1219 1220 1221 |
# File 'app/models/rma.rb', line 1219 def any_items_awaiting_inspection? rma_items.reload.any?(&:awaiting_inspection?) end |
#any_items_requested? ⇒ Object
Whether any items requested.
1687 1688 1689 |
# File 'app/models/rma.rb', line 1687 def any_items_requested? rma_items.reload.active.where(state: 'requested').any? end |
#any_rejected_items? ⇒ Boolean
True when any item or kit component was flagged non-resalable or not
returned during inspection. Gates the RMAITEMREJECTED notification.
1345 1346 1347 |
# File 'app/models/rma.rb', line 1345 def any_rejected_items? rma_items.rejected.exists? end |
#append_non_kit_credit_line(order, rma_item, good_qty, credit_percentage, from_ci_invoice) ⇒ Object
Appends a single credit line for a non-kit item, crediting +good_qty+ units.
An item linked to its original invoice line credits the invoice price (prorated
for a kit component returned on its own line); an unlinked item credits the
current catalog amount. Both are scaled by +credit_percentage+.
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 |
# File 'app/models/rma.rb', line 2321 def append_non_kit_credit_line(order, rma_item, good_qty, credit_percentage, from_ci_invoice) if rma_item.returned_line_item.present? && (rma_item.returned_line_item.item == rma_item.returned_item) if rma_item.returned_line_item.parent_id.present? # Kit component returned on its own invoice line: credit its prorated # share of the original kit price from the invoice, not the component's # catalog cost. The prorated value always wins — whether the catalog cost # is higher or lower — so the credit reflects the invoice. Mirrors # create_kit_credit_lines, which handles the kit-parent expansion path. price = discounted_price = invoice_kit_component_credit_price(rma_item.returned_line_item) else price = rma_item.returned_line_item.price discounted_price = rma_item.returned_line_item.discounted_price end catalog_item = rma_item.returned_line_item.catalog_item if catalog_item.nil? catalog_item = rma_item.rma.customer.catalog.catalog_items.includes(:store_item).where(store_items: { item_id: rma_item.returned_item_id }).first raise "Can't find matching CatalogItem" if catalog_item.nil? end li_attrs = { catalog_item_id: catalog_item.id, quantity: -good_qty, price: price * credit_percentage, discounted_price: discounted_price * credit_percentage, credit_rma_item: rma_item } else catalog_item = customer.catalog.catalog_items.includes(:store_item).where(store_items: { item_id: rma_item.returned_item_id }).first raise "Can't find matching CatalogItem" if catalog_item.nil? li_attrs = { catalog_item_id: catalog_item.id, quantity: -good_qty, price: catalog_item.amount * credit_percentage, discounted_price: catalog_item.amount * credit_percentage, credit_rma_item: rma_item } end li = LineItem.new(li_attrs) li.do_not_calculate_tax = true if from_ci_invoice order.line_items << li end |
#arrival_datetime ⇒ Object
Arrival datetime.
845 846 847 |
# File 'app/models/rma.rb', line 845 def arrival_datetime rma_items.active.maximum(:arrival_datetime) end |
#auto_return_rma_items ⇒ Object
Auto return rma items.
1666 1667 1668 |
# File 'app/models/rma.rb', line 1666 def auto_return_rma_items rma_items.where(will_not_be_returned: true, state: 'requested').find_each(&:auto_return) end |
#build_activity ⇒ Object
Build activity.
1085 1086 1087 |
# File 'app/models/rma.rb', line 1085 def build_activity activities.new(resource: self, party: customer) end |
#can_be_edited? ⇒ Object
Whether can be edited.
1062 1063 1064 |
# File 'app/models/rma.rb', line 1062 def can_be_edited? new_record? || requested? || auto_return_review? || awaiting_return? || awaiting_inspection? || partially_returned? end |
#can_be_received? ⇒ Object
Whether can be received.
1027 1028 1029 |
# File 'app/models/rma.rb', line 1027 def can_be_received? requested? || awaiting_return? || awaiting_inspection? || partially_returned? end |
#can_be_transmitted? ⇒ Object
Whether can be transmitted.
1034 1035 1036 |
# File 'app/models/rma.rb', line 1034 def can_be_transmitted? awaiting_return? || credit_in_process? || credited_partially_refunded? || credited_fully_refunded? end |
#can_be_unreturned? ⇒ Object
Whether can be unreturned.
1055 1056 1057 |
# File 'app/models/rma.rb', line 1055 def can_be_unreturned? returned? end |
#can_be_unvoided? ⇒ Object
Whether can be unvoided.
1048 1049 1050 |
# File 'app/models/rma.rb', line 1048 def can_be_unvoided? voided? end |
#can_be_voided? ⇒ Object
Whether can be voided.
1041 1042 1043 |
# File 'app/models/rma.rb', line 1041 def can_be_voided? requested? || auto_return_review? || (awaiting_return? && no_items_returned?) end |
#can_edit_return_labels? ⇒ Object
Whether can edit return labels.
1069 1070 1071 1072 1073 |
# File 'app/models/rma.rb', line 1069 def can_edit_return_labels? return false if ship_from_address.is_warehouse? requested? || auto_return_review? || awaiting_return? end |
#canada_post_return? ⇒ Boolean
True when the customer's return is being made via Canada Post — i.e. the
selected return shipping option is Canada Post. Canada Post returns are
boxless/paperless QR returns issued by Canada Post's own Manage Returns
policy; we never generate a return label for them (ShipEngine cannot produce
a Canada Post return label, and attempting to would incur a chargeable
forward-label fallback), so the hosted QR policy link stands in for the
label. Drives both the no-label gate in +return_label_required?+ and the
customer-email block in +canada_post_return_html+.
See doc/tasks/202606121059_CANADA_POST_RETURNS_NOTIFICATION_CONTINGENCY.md.
1120 1121 1122 |
# File 'app/models/rma.rb', line 1120 def canada_post_return? shipping_option&.carrier == 'Canadapost' end |
#canada_post_return_html ⇒ Object
HTML block embedded in the customer return email via the single
{{ rma.canada_post_return_html }} tag in the RMA email template. Renders
ONLY when Canada Post is the selected return shipping option
(+canada_post_return?+), and stands in for a generated return label — never
alongside one (CP returns generate no label; see +return_label_required?+).
Unlike the USPS paperless block, the artifact is a static Canada Post Manage
Returns link (no per-box QR image, no CID attachment): the customer starts
their return on Canada Post's hosted page, enters their RMA number, and
Canada Post issues the QR. Inline styles only (email-client safe).
1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 |
# File 'app/models/rma.rb', line 1505 def canada_post_return_html return '' unless canada_post_return? <<~HTML.html_safe <div style="margin:16px 0;font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#222;"> <h3 style="margin:0 0 8px;">Canada Post — Paperless QR return</h3> <p style="margin:0 0 8px;">You do not need to print a shipping label at home — Canada Post can create and apply it for you.</p> <p style="margin:0 0 8px;"><strong>Before drop-off: write your RMA number (#{ERB::Util.html_escape(rma_number)}) on the outside of the box, and print this email (or the attached return instructions) and include a copy inside the box.</strong> The label Canada Post prints may not show your RMA number, and we need it to match your returned goods to this RMA.</p> <p style="margin:0 0 8px;"><strong>Return Policy ID: #{CANADA_POST_RETURNS_POLICY_ID}</strong><br />Take your packaged items to any Canada Post outlet and give staff this Return Policy ID — they will print the return label for you.</p> <p style="margin:0 0 8px;">Or start your return online:</p> <p style="margin:0 0 8px;"><a href="#{CANADA_POST_RETURNS_POLICY_URL}" style="display:inline-block;padding:10px 16px;background:#d62d20;color:#ffffff;text-decoration:none;border-radius:4px;font-weight:bold;">Start your Canada Post return</a></p> <p style="margin:0 0 12px;font-size:12px;word-break:break-all;color:#555;">#{CANADA_POST_RETURNS_POLICY_URL}</p> <p style="margin:0 0 8px;">When Canada Post asks for a <strong>Return No.</strong>, enter your RMA number <strong>#{ERB::Util.html_escape(rma_number)}</strong> so we can match your return to this RMA.</p> </div> HTML end |
#capture_seeded_credit_prices(order) ⇒ Hash
Snapshots the invoice-derived prices seeded on each credit line (list/discounted
prices for plain items, prorated shares for kit components) before the autosave
chain re-prices them to catalog amount. Keyed by [rma_item_id, catalog_item_id],
which is unique per credit line. Pairs with +restore_seeded_credit_prices+.
2422 2423 2424 2425 2426 2427 2428 |
# File 'app/models/rma.rb', line 2422 def capture_seeded_credit_prices(order) order.line_items.each_with_object({}) do |li, h| next unless li.credit_rma_item && li.catalog_item_id h[[li.credit_rma_item.id, li.catalog_item_id]] = { price: li.price, discounted_price: li.discounted_price } end end |
#communications ⇒ ActiveRecord::Associations::CollectionProxy
The associated communications.
170 |
# File 'app/models/rma.rb', line 170 has_many :communications, as: :resource, dependent: :nullify |
#company ⇒ Company?
The associated company.
112 |
# File 'app/models/rma.rb', line 112 belongs_to :company, optional: true |
#create_credit_order(rma_item_ids) ⇒ Object
Create credit order.
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 |
# File 'app/models/rma.rb', line 2172 def create_credit_order(rma_item_ids) # take only lines that are returned and which don't have a credit order already rma_items_list = rma_items.where(id: rma_item_ids.pluck(:rma_item_id)) items = rma_items_list.reject do |ri| ri.marked_for_destruction? || ri.voided? || ri.credit_order_line_item end return if items.blank? order = Order.new(order_type: Order::CREDIT_ORDER) transaction do order.customer = customer order.customer_reference = customer_reference if ship_from_address.present? order.shipping_address_id = ship_from_address_id else # Address was deleted - fall back to customer's current address fallback_address = customer.shipping_address || customer.billing_address || customer.addresses.first raise "Cannot create credit order: no valid shipping address available for RMA #{rma_number}" if fallback_address.nil? order.shipping_address_id = fallback_address.id Rails.logger.warn("RMA #{rma_number}: ship_from_address_id #{ship_from_address_id} not found, using fallback address #{fallback_address.id}") end order.shipped_date = original_invoice.try(:shipped_date) || original_invoice.try(:order).try(:shipped_date) order.currency = original_invoice.try(:currency) || customer.catalog.currency order.disable_auto_coupon = true order.tax_exempt = original_invoice.try(:tax_exempt).to_b order.rma_id = id order.tax_date = if rma_items.active.any?(&:replacement_required?) # if there is a replacement order, then we use current tax rates so they match up Date.current else original_invoice.try(:shipped_date) || original_invoice.try(:order).try(:shipped_date) || Date.current end from_ci_invoice = original_invoice&.invoice_type == Invoice::CI items.each do |rma_item| credit_percentage = rma_item.credit_percentage.to_f / 100 qty_to_receive = rma_item_ids.detect { |r| r[:rma_item_id] == rma_item.id }&.dig(:qty_to_receive) || 0 if rma_item.returned_item&.is_kit? && rma_item.children.any? create_kit_credit_lines(order, rma_item, qty_to_receive, credit_percentage) next end # Non-kit item: credit only the resalable (good) units. The non-resalable # damaged portion is booked to inventory but excluded from the credit, # matching kit-component handling. good_qty = rma_item.creditable_quantity(qty_to_receive) next if good_qty <= 0 append_non_kit_credit_line(order, rma_item, good_qty, credit_percentage, from_ci_invoice) end # The save below fires reset_discount (via the disable_auto_coupon toggle), # which re-prices every parent line to its catalog amount. Snapshot the # invoice-derived prices now so restore_seeded_credit_prices can put them back. seeded_credit_prices = capture_seeded_credit_prices(order) order.recalculate_shipping = false order.save! # Copy the original invoice discounts onto the credit lines. A line's discount is # the gap between its (list) price and its discounted/net price, both captured # before the save chain reset prices to catalog. For a kit component this gap is # already its prorated share of the kit discount, and components excluded from the # credit have no line — so their share is forfeited, never redistributed onto the # credited components. The gap is attributed back to the source line's coupons in # proportion to each coupon's amount. order.line_items.non_shipping.parents_only .joins(credit_rma_item: :returned_line_item) .includes(credit_rma_item: { returned_line_item: :line_discounts }) .find_each do |li| seeded = seeded_credit_prices[[li.credit_rma_item_id, li.catalog_item_id]] next unless seeded discount_total = ((seeded[:price].to_d - seeded[:discounted_price].to_d) * li.quantity).abs next unless discount_total.positive? split_line_discount_by_coupon(discount_total, li.credit_rma_item.returned_line_item.line_discounts).each do |ld, amount| next if amount.zero? li.line_discounts.create(coupon_id: ld.coupon_id, discount_id: ld.discount_id, amount: amount) end end effective_dates = {} original_invoice&.discounts&.each { |d| effective_dates[d.coupon_id] = d.effective_date } order.line_items.non_shipping.map(&:line_discounts).flatten.group_by(&:coupon_id).each do |coupon_id, line_discounts| discount = order.discounts.create(coupon_id: coupon_id, amount: line_discounts.sum(&:amount), user_amount: line_discounts.sum(&:amount), effective_date: effective_dates[coupon_id] || Date.current) line_discounts.each { |ld| ld.update_attribute!(:discount_id, discount.id) } end if original_invoice&.discounts&.any? && order.discounts.empty? # the original order has discounts but we couldn't work out the amounts as it was too old # so we just flag the credit order as needing an adjustment order.discount_adjustment_needed = true end unless order.valid? # this adds the errors to the RMA so they will show up on the RMA form errors.add :base, "Credit Order not valid. Exc: #{order.errors.}" end # adding this so that Itemizable.set_totals will be called, which will call # Itemizable.calculate_discounts, which will correctly set the discounted prices order.force_total_reset = true order.save! if from_ci_invoice inherit_ci_invoice_line_values(order) elsif original_invoice.present? # The save chain above re-priced every parent line to its catalog amount via # reset_discount (fired by the disable_auto_coupon toggle). Restore the # invoice-derived prices we seeded so the credit reflects the original invoice # — including prorated kit-component shares — instead of current catalog cost. # update_columns skips the autosave/reset callbacks; tax is recomputed below # from the restored prices. restore_seeded_credit_prices(order, seeded_credit_prices) if has_replacement_items? order.refresh_tax_rate else # No replacements: the tax rate was already inherited from the invoice in # set_initial_tax_rate. Re-fetching via refresh_tax_rate would overwrite it # with current TaxJar rates, which may differ from the original transaction. # Only apply the inherited rate to the (now corrected) line items. order.reload order.apply_tax_rate_to_line_items end else order.refresh_tax_rate end order.returned! end order end |
#create_documents ⇒ Object
Create documents.
1746 1747 1748 1749 1750 |
# File 'app/models/rma.rb', line 1746 def create_documents # return unless rma_items_changed? # create_credit_order create_replacement_order end |
#create_replacement_order ⇒ Object
Create replacement order.
2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 |
# File 'app/models/rma.rb', line 2078 def create_replacement_order return unless can_be_edited? # take only lines that are non voided and which don't have a replacement order already items = rma_items.select { |ri| ri.replacement_required? && ri.replacement_order.nil? } return if items.empty? res = {} transaction do order_type = replacement_order_type || items.first.rma_reason_code&.replacement_order_type || Order::SALES_ORDER order = Order.new(order_type: order_type, creator: creator) order.customer = customer order.customer_reference = customer_reference if original_invoice.present? order.opportunity_id = original_invoice.order.opportunity_id if original_invoice.order.present? order.shipping_address_id = original_invoice.shipping_address_id order.currency = original_invoice.currency else order.shipping_address = customer.shipping_address order.currency = company.currency end order.disable_auto_coupon = true order.rma_id = id # If this RMA is linked to a support case, auto-populate the tracking email # with the primary participant's email so they receive shipment notifications. # This is important for warranty replacements being shipped directly to end consumers. if support_case.present? participant_email = support_case.primary_party&.email order.tracking_email = [participant_email].compact if participant_email.present? end catalog = customer.catalog items.each do |rma_item| cat_item = CatalogItem.includes(:store_item).where(catalog_id: catalog.id, store_items: { item_id: rma_item.returned_item_id }).first raise ReplacementOrderError, "Unable to find replacement item in customer's catalog" if cat_item.nil? order.line_items << LineItem.new(catalog_item_id: cat_item.id, quantity: rma_item.returned_item_quantity, price: cat_item.amount, discounted_price: cat_item.amount, replacement_rma_item_id: rma_item.id) end errors.add :base, "Replacement Order not valid. Exc: #{order.errors.}" unless order.valid? order.do_not_set_totals = true order.save! order.reload order.retrieve_shipping_costs order.calculate_discounts order.save! order.reload # add the price match coupon coupon = Coupon.find_by(code: 'OPM') discount = Discount.new(itemizable: order, coupon_id: coupon.id, effective_date: Date.current) order.line_items.each do |li| unless (original_line = li.try(:replacement_rma_item).try(:returned_line_item)) && (li.discounted_price > original_line.discounted_price) next end # add a price match coupon to match the price on the original order coupon_amount = (li.discounted_price - original_line.discounted_price) * li.quantity discount.line_discounts.build(coupon_id: coupon.id, amount: -coupon_amount, line_item_id: li.id) end if discount.line_discounts.any? discount.amount = discount.user_amount = discount.line_discounts.to_a.sum(&:amount) discount.save! end items.each do |rma_item| rma_item.update(replacement_order_id: order.id) end order.reload.save # If the rma is tied to a support case, also link that order to the support case support_case.orders << order if support_case res[:order] = order end OpenStruct.new(res).freeze end |
#create_return_delivery ⇒ Object
Create return delivery.
1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 |
# File 'app/models/rma.rb', line 1755 def create_return_delivery return unless return_label_required? @return_packaging_overridden = false @return_packaging_shortfall = nil d = return_delivery if d.present? update_column(:return_delivery_id, nil) d.destroy end self.return_delivery = Delivery.create( rma_for_return: self, origin_address_id: ship_from_address_id, destination_address_id: customer.store.warehouse_address_id, bill_shipping_to_customer: customer.bill_shipping_to_customer, shipping_option_id: shipping_option_id, state: 'quoting' # here we force state quoting, overrides pre_pack ) items = (rma_items.reject { |ri| ri.marked_for_destruction? || ri.voided? }) items.each do |rma_item| credit_percentage = rma_item.credit_percentage.to_f / 100 if rma_item.returned_line_item.present? && (rma_item.returned_line_item.item == rma_item.returned_item) # it's linked to an existing line item, so get pricing from that if rma_item.returned_line_item.parent_id.present? price = discounted_price = rma_item.returned_line_item.catalog_item.amount else price = rma_item.returned_line_item.price discounted_price = rma_item.returned_line_item.discounted_price end catalog_item = rma_item.returned_line_item.catalog_item catalog_item ||= customer.catalog.catalog_items.by_skus(rma_item.returned_item.sku).first raise "Can't find matching CatalogItem" if catalog_item.nil? li = LineItem.new(catalog_item_id: catalog_item.id, quantity: -rma_item.returned_item_quantity, price: price * credit_percentage, discounted_price: discounted_price * credit_percentage) else # it's not linked to an existing line item, so need to get pricing from current item pricing catalog_item = customer.catalog.catalog_items.includes(:store_item).where(store_items: { item_id: rma_item.returned_item_id }).first raise "Can't find matching CatalogItem" if catalog_item.nil? li = LineItem.new(catalog_item_id: catalog_item.id, quantity: -rma_item.returned_item_quantity, price: catalog_item.amount * credit_percentage, discounted_price: catalog_item.amount * credit_percentage) end return_delivery.line_items << li end # Use packaging for shipping calculation but create shipments based on user's label requirement if original_order&.precreate_rma? && precreate_from_delivery_id == return_delivery.id # we are precreating this RMA, it is not a new manual RMA flow # grab the shipment info from the original order delivery which should now be properly packed return_delivery.create_shipments_from_equivalent_delivery(original_order.deliveries.first, 'awaiting_label') else # Use packaging algorithm for shipping cost calculation only return_delivery.search_deliveries_for_equivalent_packaging Rails.logger.info "RMA #{rma_number}: Packaging algorithm created #{return_delivery.shipments.count} shipments" end return_delivery.retrieve_shipping_costs # Handle user's label requirement vs packaging recommendation. In every mode # the original order's actual completed packing (when present) is the ground # truth for which items fit in which box, so prefer mirroring it over the # equivalent-packaging guess — which can return N identical max-size boxes # with every item dumped in box 1 (RMA60441; see # doc/tasks/202606052100_RMA_RETURN_PACKAGING_ALLOCATION.md). if number_of_return_labels_required == -1 # Auto: mirror the original order's packing when available; otherwise keep # the packaging-algorithm shipments (creating a default if it made none). if mirror_original_order_packaging(return_delivery).nil? && return_delivery.shipments.count.zero? Rails.logger.warn "RMA #{rma_number}: No shipments created by packaging, creating default shipment" return_delivery.shipments.create!( weight: return_delivery.ship_weight, length: 12, width: 12, height: 6, state: 'suggested', container_type: 'carton' ) distribute_line_items_across_shipments(return_delivery, return_delivery.shipments) end elsif number_of_return_labels_required.positive? # User specified a label count: mirror the original packing if it matches # that count exactly, otherwise honor the count by bin-packing the actual # returned items into that many boxes (see build_return_shipments_for_count). build_return_shipments_for_count(return_delivery, number_of_return_labels_required) end # If number_of_return_labels_required is 0 or nil, no return labels are required # Surface the resulting per-box packing on the return-labels tab instead of # leaving "Suggested packaging" blank; flag when the requested count # overrode the suggested packing. if (summary = build_return_packaging_summary(return_delivery)).present? if @return_packaging_overridden prefix = "Requested #{number_of_return_labels_required} labels (overrides suggestion)" prefix += " — ⚠ items likely need #{@return_packaging_shortfall} boxes" if @return_packaging_shortfall summary = "#{prefix} — #{summary}" end return_delivery.update_column(:suggested_packaging_text, summary) end return_delivery.return_labels_ready end |
#creator ⇒ Employee?
The associated creator.
145 |
# File 'app/models/rma.rb', line 145 belongs_to :creator, class_name: 'Employee', optional: true |
#credit_available ⇒ Object
Credit available.
1283 1284 1285 1286 |
# File 'app/models/rma.rb', line 1283 def credit_available credit = credit_total - payments..sum(:amount) credit.negative? ? 0 : credit.round(2) end |
#credit_available_before_tax ⇒ Object
Credit available before tax.
1291 1292 1293 1294 |
# File 'app/models/rma.rb', line 1291 def credit_available_before_tax credit = credit_total_before_tax - payments..sum(:amount) credit.negative? ? 0 : credit.round(2) end |
#credit_deliveries ⇒ ActiveRecord::Associations::CollectionProxy
The associated credit deliveries.
190 |
# File 'app/models/rma.rb', line 190 has_many :credit_deliveries, through: :credit_orders, source: :deliveries, class_name: 'Delivery' |
#credit_memos ⇒ ActiveRecord::Associations::CollectionProxy
The associated credit memos.
158 |
# File 'app/models/rma.rb', line 158 has_many :credit_memos |
#credit_order_line_items ⇒ ActiveRecord::Associations::CollectionProxy
The associated credit order line items.
182 |
# File 'app/models/rma.rb', line 182 has_many :credit_order_line_items, through: :rma_items |
#credit_order_shipments ⇒ Object
Credit order shipments.
1226 1227 1228 |
# File 'app/models/rma.rb', line 1226 def credit_order_shipments credit_deliveries.map { |d| d.shipments.where.not(tracking_number: nil) }.flatten end |
#credit_orders ⇒ ActiveRecord::Associations::CollectionProxy
The associated credit orders.
185 186 187 |
# File 'app/models/rma.rb', line 185 has_many :credit_orders, lambda { distinct }, through: :credit_order_line_items, source: :resource, source_type: 'Order', class_name: 'Order' |
#credit_total ⇒ Object
Credit total.
1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 |
# File 'app/models/rma.rb', line 1233 def credit_total BigDecimal(0) total_credit_orders = BigDecimal(0) total_credit_orders_line_items = BigDecimal(0) total_rma_items_requested = BigDecimal(0) credit_orders.each { |o| total_credit_orders += o.total } credit_orders.each do |co| co.line_items.non_shipping.parents_only.each { |li| total_credit_orders_line_items += (li.discounted_total + li.tax_total) unless li.credit_rma_item.is_customer_fault? } end rma_items.each do |ri| next unless !ri.is_customer_fault? && ri.returned_line_item.present? li = ri.returned_line_item # Calculate proportional amounts for partial returns proportion = BigDecimal(ri.returned_item_quantity) / BigDecimal(li.quantity) total_rma_items_requested += (li.discounted_total * proportion) + (li.tax_total * proportion) end total = total_rma_items_requested + total_credit_orders_line_items + total_credit_orders total.negative? ? -total : total end |
#credit_total_before_tax ⇒ Object
Credit total before tax.
1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 |
# File 'app/models/rma.rb', line 1258 def credit_total_before_tax BigDecimal(0) total_credit_orders = BigDecimal(0) total_credit_orders_line_items = BigDecimal(0) total_rma_items_requested = BigDecimal(0) credit_orders.each { |o| total_credit_orders += o.line_total } credit_orders.each do |co| co.line_items.non_shipping.parents_only.each { |li| total_credit_orders_line_items += li.discounted_total unless li.credit_rma_item.is_customer_fault? } end rma_items.each do |ri| next unless !ri.is_customer_fault? && ri.returned_line_item.present? li = ri.returned_line_item # Calculate proportional amount for partial returns proportion = BigDecimal(ri.returned_item_quantity) / BigDecimal(li.quantity) total_rma_items_requested += li.discounted_total * proportion end total = total_rma_items_requested + total_credit_orders_line_items + total_credit_orders total.negative? ? -total : total end |
#crm_link ⇒ Object
Crm link.
1381 1382 1383 |
# File 'app/models/rma.rb', line 1381 def crm_link UrlHelper.instance.rma_path(self) end |
#customer ⇒ Customer?
The associated customer.
121 |
# File 'app/models/rma.rb', line 121 belongs_to :customer, optional: true |
#customer_name ⇒ Object
Customer name.
1165 1166 1167 |
# File 'app/models/rma.rb', line 1165 def customer_name customer.try(:full_name) end |
#earliest_return_label_ship_date ⇒ Date?
Returns the earliest ship date across generated return labels.
777 778 779 |
# File 'app/models/rma.rb', line 777 def earliest_return_label_ship_date return_shipments.minimum(:date_shipped) end |
#effective_return_delivery ⇒ Object
Effective return delivery.
1440 1441 1442 |
# File 'app/models/rma.rb', line 1440 def effective_return_delivery return_delivery || legacy_return_shipments.last&.delivery end |
#enqueue_external_tracking_registration_if_needed ⇒ void
This method returns an undefined value.
Enqueues ShipEngine registration for new external numbers or numbers whose
effective carrier changed. Insurance-only and equivalent carrier edits do
not repeat registration or history polling. Generated return labels use
Shipment's equivalent after-commit callback.
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 |
# File 'app/models/rma.rb', line 797 def enqueue_external_tracking_registration_if_needed numbers = [] if saved_change_to_tracking_numbers? before, after = saved_change_to_tracking_numbers numbers.concat(Array(after) - Array(before)) end if saved_change_to_return_shipping_carrier? before_carrier, after_carrier = saved_change_to_return_shipping_carrier numbers.concat( tracking_numbers.reject do |tracking_number| before_resolved = Heatwave::Normalizers.resolve_shipping_carrier( before_carrier, tracking_number: tracking_number ) after_resolved = Heatwave::Normalizers.resolve_shipping_carrier( after_carrier, tracking_number: tracking_number ) before_resolved == after_resolved end ) end numbers.compact_blank.uniq.each do |tracking_number| RmaTrackingRegistrationWorker.perform_async( rma_id: id, tracking_number: tracking_number ) end end |
#exclude_manually_initiated_event?(event) ⇒ Object
Whether exclude manually initiated event.
936 937 938 |
# File 'app/models/rma.rb', line 936 def exclude_manually_initiated_event?(event) %i[returned returned_or_voided credit_in_process void transmit].include?(event.to_sym) end |
#external_return_labels? ⇒ Object
Whether external return labels.
1107 1108 1109 |
# File 'app/models/rma.rb', line 1107 def external_return_labels? number_of_return_labels_required.to_i.zero? end |
#full_invoice_returned? ⇒ Object
Whether full invoice returned.
1155 1156 1157 1158 1159 1160 |
# File 'app/models/rma.rb', line 1155 def full_invoice_returned? return false if credit_orders.blank? return true if original_invoice.blank? (original_invoice.line_total.abs == credit_orders.first.line_total.abs) && (original_invoice.line_items.non_shipping.parents_only.sum(&:quantity).abs == credit_orders.first.line_items.non_shipping.parents_only.sum(&:quantity).abs) end |
#generate_and_send_return_email_to_customer ⇒ Object
Generate and send return email to customer.
1311 1312 1313 1314 1315 1316 |
# File 'app/models/rma.rb', line 1311 def generate_and_send_return_email_to_customer return unless rma_items.active.will_be_returned.any? # Create draft communication - instructions will be generated and attached by controller send_return_email_to_customer(keep_as_draft: true) end |
#generate_return_instructions_pdf ⇒ Object
Generate return instructions pdf.
1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 |
# File 'app/models/rma.rb', line 1964 def generate_return_instructions_pdf uploads.in_category('rma_return_instructions_pdf').destroy_all pdf_result = Pdf::Document::ReturnInstructions.call(self, output_to_file: true) upload = Upload.uploadify(pdf_result.pdf_file_path, 'rma_return_instructions_pdf', self, pdf_result.file_name) uploads << upload # Update draft communications with new return instructions upload end |
#generate_return_labels ⇒ Object
Generate return labels.
2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 |
# File 'app/models/rma.rb', line 2010 def generate_return_labels status = :error errs = [] if return_delivery&. label_result = nil begin label_result = return_delivery.generate_labels rescue StandardError => e ErrorReporting.error(e, "Could not generate return labels, RMA ID: #{id}, RMA number: #{rma_number}, return_delivery ID: #{return_delivery.id}") status = :error errs << e.to_s end # Surface carrier-level rejections (e.g. PO Box guards) — Delivery#generate_labels # writes them to shipping_api_log but otherwise they're hidden behind the # generic "N labels requested but M labels generated" count message. errs << label_result[:status_message] if label_result.is_a?(Hash) && label_result[:status_code] == :error && label_result[:status_message].present? status = :ok unless return_labels_correctly_generated? status = :error errs += return_label_issues end end # Update draft communications with new labels if generation was successful if status == :ok awaiting_return end { status: status, errors: errs.uniq } end |
#handle_return_shipment_tracking_state_updated(return_shipment) ⇒ Object
Handle return shipment tracking state updated.
833 834 835 836 837 838 839 840 |
# File 'app/models/rma.rb', line 833 def handle_return_shipment_tracking_state_updated(return_shipment) # method is called when return_shipment tracking_state transitions to one of: # state :in_transit # state :delivered # state :exception # state :delivery_attempt # state :delivered_to_collection_location end |
#has_auto_return_items? ⇒ Object
Whether has auto return items.
869 870 871 |
# File 'app/models/rma.rb', line 869 def has_auto_return_items? rma_items.where(will_not_be_returned: true, state: 'requested').any? end |
#has_different_replacement_items? ⇒ Object
Whether has different replacement items.
1711 1712 1713 |
# File 'app/models/rma.rb', line 1711 def has_different_replacement_items? !has_same_replacement_items? end |
#has_no_active_items? ⇒ Boolean
+true+ when there are no non-voided lines (+RmaItem.active+ / +where.not(state: 'voided')+).
881 882 883 |
# File 'app/models/rma.rb', line 881 def has_no_active_items? rma_items.active.none? end |
#has_replacement_items? ⇒ Object
Whether has replacement items.
1541 1542 1543 |
# File 'app/models/rma.rb', line 1541 def has_replacement_items? rma_items.reload.active.any?(&:replacement_required?) end |
#has_rma_items_requested? ⇒ Object
Whether has rma items requested.
876 877 878 |
# File 'app/models/rma.rb', line 876 def has_rma_items_requested? rma_items.where(state: 'requested').any? end |
#has_same_replacement_items? ⇒ Object
Whether has same replacement items.
1701 1702 1703 1704 1705 1706 |
# File 'app/models/rma.rb', line 1701 def has_same_replacement_items? # check that all replacement items are linked to the rma_item and match quantity rma_items.replacement_required.all? do |ri| ri.replacement_order_line_item.present? && ri.returned_item_quantity == ri.replacement_order_line_item.quantity end end |
#inherit_ci_invoice_line_values(order) ⇒ Object
For CI invoices (e.g. Amazon Seller Central), the marketplace sets the
per-unit price, promotional discount, and tax on the original invoice
line. Inherit those exactly on the credit order instead of recalculating
via current catalog pricing or tax rates:
create_credit_orderseeds the correct values, but the Order autosave
chain (reset_discount_on_auto_coupon_toggle→reset_discount)
rewritespricetocatalog_item.amount, andcalculate_discounts
then resetsdiscounted_priceto the same.- Tax on a credit must also carry the sign of the negative credit
quantity so grand totals andtaxes_grouped_by_typeare negative. taxable_amountis inherited HERE rather than seeded on the new line,
because it is only ever consistent with the restoreddiscounted_price.
Seeding it up front left it stranded above the catalog amount the resets
write in the meantime, andtaxable_amount_not_greater_than_discounted_price
then aborted the whole receive for any item Amazon sold above catalog.
2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 |
# File 'app/models/rma.rb', line 2381 def inherit_ci_invoice_line_values(order) order.reload order.line_items.non_shipping.includes(credit_rma_item: :returned_line_item).each do |li| source_line = li.credit_rma_item&.returned_line_item next unless source_line credit_pct = li.credit_rma_item.credit_percentage.to_f / 100 source_qty = source_line.quantity.to_d per_unit_tax = source_qty.zero? ? 0 : source_line.tax_total.to_d / source_qty inherited_tax = (per_unit_tax * li.quantity.to_d * credit_pct).round(2) source_taxable = source_line.taxable_amount li.update_columns( price: (source_line.price.to_d * credit_pct).round(2), discounted_price: (source_line.discounted_price.to_d * credit_pct).round(2), taxable_amount: source_taxable.present? ? (source_taxable.to_d * credit_pct).round(2) : nil, tax_total: inherited_tax ) end end |
#instructions_file_name(with_extension = true) ⇒ Object
Instructions file name.
1526 1527 1528 |
# File 'app/models/rma.rb', line 1526 def instructions_file_name(with_extension = true) "#{rma_number}_return_instructions#{'.pdf' if with_extension}" end |
#invoice ⇒ Invoice?
The associated invoice.
149 |
# File 'app/models/rma.rb', line 149 has_one :invoice, class_name: 'Invoice', foreign_key: :id, primary_key: :original_invoice_id |
#invoice_add_blocked_for_kit_component_line?(component_line_item) ⇒ Boolean
add-items UI: true if this kit component line cannot be added (the parent kit line is already on the RMA).
2598 2599 2600 2601 2602 2603 |
# File 'app/models/rma.rb', line 2598 def invoice_add_blocked_for_kit_component_line?(component_line_item) pid = component_line_item.parent_id return false if pid.blank? selected_returned_line_item_ids_for_invoice_kit_rules.include?(pid) end |
#invoice_add_blocked_for_kit_parent_line?(parent_line_item) ⇒ Boolean
add-items UI: true if this parent kit invoice line cannot be added (a component line is already on the RMA).
Callers should preload +parent_line_item.children+ (e.g. +includes(children: :item)+) so child ids are read from memory.
2587 2588 2589 2590 2591 2592 2593 2594 |
# File 'app/models/rma.rb', line 2587 def invoice_add_blocked_for_kit_parent_line?(parent_line_item) children = parent_line_item.children return false if children.blank? child_ids = children.loaded? ? children.map(&:id) : children.ids selected_line_ids = selected_returned_line_item_ids_for_invoice_kit_rules child_ids.any? { |cid| selected_line_ids.include?(cid) } end |
#invoice_line_already_on_return_list?(line_item) ⇒ Boolean
add-items UI: true if this invoice line already has a return row (each line at most once).
2607 2608 2609 2610 |
# File 'app/models/rma.rb', line 2607 def invoice_line_already_on_return_list?(line_item) line_item.id.present? && selected_returned_line_item_ids_for_invoice_kit_rules.include?(line_item.id) end |
#items_partially_returned? ⇒ Object
Whether items partially returned.
1673 1674 1675 |
# File 'app/models/rma.rb', line 1673 def items_partially_returned? rma_items.reload.active.any?(&:returned?) && rma_items.reload.active.any?(&:requested?) end |
#labels_file_name(with_extension = true) ⇒ Object
Labels file name.
1534 1535 1536 |
# File 'app/models/rma.rb', line 1534 def labels_file_name(with_extension = true) "#{rma_number}_return_labels#{'.pdf' if with_extension}" end |
#legacy_return_shipments ⇒ Object
Legacy return shipments.
1433 1434 1435 |
# File 'app/models/rma.rb', line 1433 def legacy_return_shipments Shipment.label_complete.joins(:order).merge(credit_orders).order(:id) end |
#next_step_after_items_created ⇒ Object
Next step after items created.
2560 2561 2562 2563 2564 2565 2566 |
# File 'app/models/rma.rb', line 2560 def next_step_after_items_created if return_label_required? 'return_label_options' else 'show' end end |
#no_items_returned? ⇒ Object
Whether no items returned.
1078 1079 1080 |
# File 'app/models/rma.rb', line 1078 def no_items_returned? rma_items.none?(&:returned?) end |
#orders ⇒ ActiveRecord::Associations::CollectionProxy
The associated orders.
173 |
# File 'app/models/rma.rb', line 173 has_many :orders, inverse_of: :rma |
#original_invoice ⇒ Invoice?
The associated original invoice.
118 |
# File 'app/models/rma.rb', line 118 belongs_to :original_invoice, class_name: 'Invoice', optional: true |
#original_invoice_select ⇒ Object
Original invoice select.
906 907 908 909 910 |
# File 'app/models/rma.rb', line 906 def original_invoice_select return [] unless original_invoice [[original_invoice.selection_name_for_rmas, original_invoice.id]] end |
#original_order ⇒ Order?
The associated original order.
115 |
# File 'app/models/rma.rb', line 115 belongs_to :original_order, class_name: 'Order', optional: true |
#original_order_ref ⇒ Object
Original order ref.
1172 1173 1174 |
# File 'app/models/rma.rb', line 1172 def original_order_ref original_order.try(:reference_number) end |
#original_order_ref=(ref) ⇒ Object
Original order ref.
1180 1181 1182 |
# File 'app/models/rma.rb', line 1180 def original_order_ref=(ref) self.original_order = Order.find_by(reference_number: ref) if ref.present? end |
#original_order_select ⇒ Object
Original order select.
897 898 899 900 901 |
# File 'app/models/rma.rb', line 897 def original_order_select return [] unless original_order [[original_order.selection_name, original_order.id]] end |
#paperless_return? ⇒ Boolean
True when this return has USPS Label Broker paperless QR codes (one or more
return shipments carry a paperless_qr_png).
1460 1461 1462 |
# File 'app/models/rma.rb', line 1460 def paperless_return? return_shipments.any? { |s| s.paperless_qr_png.present? } end |
#paperless_return_html ⇒ Object
HTML block embedded in the customer return email via the single
{{ rma.paperless_return_html }} tag in the RMA email template. Returns an
empty string for non-paperless returns. Each box's QR is referenced by
Content-ID (cid:rma-qr-<upload id>); CommunicationMailer attaches the
matching paperless_qr_png uploads inline so Gmail/Outlook render them in
the body. Inline styles + table layout only (email-client safe).
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 |
# File 'app/models/rma.rb', line 1470 def paperless_return_html qr_shipments = return_shipments.select { |s| s.paperless_qr_png.present? } return '' if qr_shipments.empty? cells = qr_shipments.each_with_index.map do |shipment, i| cid = "rma-qr-#{shipment.paperless_qr_png.id}" <<~CELL <td style="padding:8px;text-align:center;vertical-align:top;"> <div style="font-weight:bold;font-size:13px;margin-bottom:4px;">Box #{i + 1}</div> <img src="cid:#{cid}" alt="USPS Label Broker QR code for Box #{i + 1}" width="150" style="display:block;margin:0 auto;border:0;" /> </td> CELL end grid = cells.each_slice(5).map { |row| "<tr>#{row.join}</tr>" }.join <<~HTML.html_safe <div style="margin:16px 0;font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#222;"> <h3 style="margin:0 0 8px;">USPS Label Broker — Paperless QR return</h3> <p style="margin:0 0 8px;">No printer needed. From your smartphone, show each QR code below at the USPS counter and the clerk will scan it and print the shipping label.</p> <p style="margin:0 0 8px;"><strong>Write your RMA number (#{ERB::Util.html_escape(rma_number)}) and the box number on the matching box</strong> before drop-off — it is not encoded in the QR code, and we need it to match your returned goods to this RMA.</p> <p style="margin:0 0 12px;">Find a Post Office that offers Label Broker at the <a href="#{PAPERLESS_LABEL_BROKER_RETAIL_URL}">counter</a> or a <a href="#{PAPERLESS_LABEL_BROKER_KIOSK_URL}">self-service kiosk</a>.</p> <table role="presentation" cellpadding="0" cellspacing="0" border="0"><tbody>#{grid}</tbody></table> </div> HTML end |
#payment_status ⇒ Object
Payment status.
915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 |
# File 'app/models/rma.rb', line 915 def payment_status return nil unless %w[returned credit_in_process credited_partially_refunded credited_fully_refunded].include?(state) statuses = [] credit_orders.each do |co| cm = co.try(:credit_memo) statuses << if co.pending_review? || co.ready_for_printing? { status: 'Under Review', color: 'amber', ref: co.reference_number } elsif cm cm.payment_status else { status: 'Unknown', color: 'red', ref: co.reference_number } end end statuses end |
#payments ⇒ ActiveRecord::Associations::CollectionProxy
The associated payments.
161 |
# File 'app/models/rma.rb', line 161 has_many :payments |
#pending_orders ⇒ ActiveRecord::Associations::CollectionProxy
The associated pending orders.
176 |
# File 'app/models/rma.rb', line 176 has_many :pending_orders, -> { where(state: PENDING_ORDERS_STATES) }, class_name: 'Order' |
#possible_events ⇒ Object
Possible events.
636 637 638 |
# File 'app/models/rma.rb', line 636 def possible_events state_transitions.map(&:event).sort end |
#possible_events_for_select ⇒ Object
Possible events for select.
643 644 645 |
# File 'app/models/rma.rb', line 643 def possible_events_for_select possible_events.map { |evt| [evt.to_s.titleize, evt] } end |
#post_communication_sent_hook ⇒ Object
Post communication sent hook.
1373 1374 1375 1376 |
# File 'app/models/rma.rb', line 1373 def post_communication_sent_hook transmit if awaiting_transmission? true end |
#precreate_from_delivery ⇒ Delivery?
The associated precreate from delivery.
133 |
# File 'app/models/rma.rb', line 133 belongs_to :precreate_from_delivery, class_name: 'Delivery', optional: true |
#primary_party ⇒ Object
Primary party.
2580 2581 2582 |
# File 'app/models/rma.rb', line 2580 def primary_party customer end |
#receive_item_events_for_select ⇒ Object
Receive item events for select.
650 651 652 653 654 655 656 |
# File 'app/models/rma.rb', line 650 def receive_item_events_for_select res = [] res << ["Keep current status: #{human_state_name.titleize}", ''] res << ['Inspect Return', 'inspect_return'] if (awaiting_return? || partially_returned?) && can_inspect_return? res << ['Complete Return', 'returned'] if awaiting_return? || partially_returned? res end |
#record_canada_post_return_inquiry(additional_info: nil, rsa_number_issue_date: nil) ⇒ void
This method returns an undefined value.
Records the external reference Canada Post forwards (the customer-entered
order/reference number — our SO number, an Amazon or Home Depot order
number, etc. — plus the RA issue date) as a note on this RMA, so staff can
cross-reference the Canada Post return to the originating retailer order.
No-op when there's nothing to record or the same reference was already noted
(Canada Post pings the endpoint repeatedly). Never raises: a note-write
failure must not fail the validation response.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'app/models/rma.rb', line 247 def record_canada_post_return_inquiry(additional_info: nil, rsa_number_issue_date: nil) additional_info = additional_info.to_s.strip.presence rsa_number_issue_date = rsa_number_issue_date.to_s.strip.presence return if additional_info.nil? && rsa_number_issue_date.nil? note = +'Canada Post return label requested via Manage Returns.' note << " Customer-provided reference: #{additional_info}." if additional_info note << " RA issue date: #{rsa_number_issue_date}." if rsa_number_issue_date return if activities.exists?(notes: note) activities.create(notes: note) rescue StandardError => e Rails.logger.warn("[CanadaPostRA] failed to note inquiry on RMA #{id}: #{e.class}: #{e.}") nil end |
#record_creation_activity ⇒ void
This method returns an undefined value.
Records a creation note on this RMA so every RMA carries a
searchable baseline of who created it, what it links to, and the stated
reason — the context warehouse staff otherwise have to dig out of emails,
the original order, or the retailer portal. Lands on the RMA's activities
tab and, via +primary_party+, in the customer's activity feed. Invoked
async by CreationNoteHandler on Events::RmaCreated (published
after_create_commit), so a note-write failure can never break RMA
creation. Never raises.
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'app/models/rma.rb', line 274 def record_creation_activity lines = ["RMA #{rma_number} created by #{creator&.full_name.presence || 'system'}."] refs = [] refs << "order #{original_order.reference_number}" if original_order refs << "invoice #{original_invoice.reference_number}" if original_invoice refs << "PO #{original_po_number}" if original_po_number.present? refs << "customer ref #{customer_reference}" if customer_reference.present? lines << "Original #{refs.join(' / ')}." if refs.any? grouping = customer && Customer::ReportGrouping.get_report_grouping_for_customer(customer) lines << "Etailer channel: #{grouping}." if Customer::ReportGrouping::ETAILER_GROUPINGS.include?(grouping) lines << "Reason given: #{description}" if description.present? lines << "Support case: #{support_case.case_number}." if support_case note = lines.join("\n") # Deduped + locked: replayed or concurrent async deliveries of # Events::RmaCreated must not stack duplicate feed entries. with_lock do return if activities.exists?(notes: note) activities.create!(notes: note) end rescue StandardError => e Rails.logger.warn("[Rma] failed to record creation note on RMA #{id}: #{e.class}: #{e.}") nil end |
#record_items_added_activity(items, added_ids: nil) ⇒ void
This method returns an undefined value.
Records a note listing the RMA's current top-level items with their return
reason codes (expanded to the reason-code description), so the activities
tab answers "what is coming back and why" without opening the items tab.
Invoked async by ItemsAddedNoteHandler on Events::RmaItemsAdded
(published by create_items / bulk_add_items / create_rma_from_delivery).
No-op when the identical note was already recorded (re-saves with
unchanged items). Never raises.
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'app/models/rma.rb', line 316 def record_items_added_activity(items, added_ids: nil) top_level = items.reject(&:child?) return if top_level.empty? added_ids = Array(added_ids).map(&:to_i) added, existing = added_ids.any? ? top_level.partition { |ri| added_ids.include?(ri.id) } : [top_level, []] return if added.empty? codes_by_code = RmaReasonCode.where(code: top_level.map(&:returned_reason)).index_by(&:code) describe = lambda do |ri| reason = ri.returned_reason.presence || '—' reason += " (#{codes_by_code[ri.returned_reason].description})" if codes_by_code[ri.returned_reason]&.description.present? "- #{ri.returned_item&.sku || '—'} x #{ri.returned_item_quantity} — #{reason}" end heading = added_ids.any? ? "Items added to RMA #{rma_number}:" : "Items on RMA #{rma_number}:" parts = [heading] + added.map(&describe) parts += ['Already on the RMA:'] + existing.map(&describe) if existing.any? note = parts.join("\n") # Deduped + locked: replayed or concurrent async deliveries of # Events::RmaItemsAdded must not stack duplicate feed entries. with_lock do return if activities.exists?(notes: note) activities.create!(notes: note) end rescue StandardError => e Rails.logger.warn("[Rma] failed to record items note on RMA #{id}: #{e.class}: #{e.}") nil end |
#redesign_quote ⇒ Quote?
The associated redesign quote.
130 |
# File 'app/models/rma.rb', line 130 belongs_to :redesign_quote, class_name: 'Quote', optional: true |
#regenerate_return_documents! ⇒ Object
Regenerates the customer-facing return documents WITHOUT touching the
purchased carrier labels (no void, no new label buy): each letter-format
label PDF is rebuilt from the carrier label PDF already on file, then the
return-instructions PDF is re-rendered from them. Exists so layout fixes
in the PDF generators (e.g. native-size 4x6 label pages) can be applied to
RMAs whose documents predate the fix.
1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 |
# File 'app/models/rma.rb', line 1985 def regenerate_return_documents! # Only the PDF-merge carriers need a rebuild — UPS/FedEx/USPS embed the # rotated PNG at native size already, and rebuilding those without the # PNG temp file would drop the label image entirely. rebuildable = return_shipments.select do |shipment| shipment.carrier.to_s.downcase.match?(/speedeedelivery|canadapost|canpar/) end # Preflight before deleting or regenerating anything: a merge-carrier # shipment without its source label PDF would lose its letter-format # label, so fail the whole run and surface the error instead. missing = rebuildable.find { |shipment| shipment.ship_label_pdf&.&.path.blank? } raise "Cannot regenerate return documents: shipment #{missing.id} has no carrier label PDF on file" if missing rebuildable.each do |shipment| # Replace (not append to) the old letter-format labels so # `ship_label_pdf(true)` / `return_labels` pick up the rebuilt one. shipment.uploads.in_category('letter_ship_label_pdf').destroy_all shipment.generate_letter_ship_label_pdf(nil, shipment.carrier.to_s) end generate_return_instructions_pdf end |
#rejected_rma_items ⇒ Object
Items (including kit components, which are rows in the same table) that
were flagged as non-resalable on inspection or were never returned.
Surfaced in the RMAITEMREJECTED notification email body.
1739 1740 1741 |
# File 'app/models/rma.rb', line 1739 def rejected_rma_items rma_items.rejected.reload end |
#restore_seeded_credit_prices(order, seeded_credit_prices) ⇒ void
This method returns an undefined value.
Re-applies the invoice-derived prices captured in +create_credit_order+ before
the +disable_auto_coupon+ toggle's +reset_discount+ re-priced every parent line
to its catalog amount. The non-CI counterpart to +inherit_ci_invoice_line_values+.
Keyed by [credit_rma_item_id, catalog_item_id] (unique per credit line, including
each kit-component line). Uses +update_columns+ so it neither re-triggers the
reset nor recalculates discounts; tax is recomputed by the caller afterward.
2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 |
# File 'app/models/rma.rb', line 2442 def restore_seeded_credit_prices(order, seeded_credit_prices) return if seeded_credit_prices.blank? order.reload order.line_items.non_shipping.where.not(credit_rma_item_id: nil).find_each do |li| seeded = seeded_credit_prices[[li.credit_rma_item_id, li.catalog_item_id]] next unless seeded li.update_columns(price: seeded[:price], discounted_price: seeded[:discounted_price]) end end |
#return_address ⇒ Object
Return address.
1409 1410 1411 |
# File 'app/models/rma.rb', line 1409 def return_address return_shipping_address.full_address(true, "\n") end |
#return_delivery ⇒ Delivery?
The associated return delivery.
136 |
# File 'app/models/rma.rb', line 136 belongs_to :return_delivery, class_name: 'Delivery', optional: true |
#return_instructions ⇒ Object
Return instructions.
1454 1455 1456 |
# File 'app/models/rma.rb', line 1454 def return_instructions uploads.in_category('rma_return_instructions_pdf').order(created_at: :desc).first end |
#return_label_issues ⇒ Object
Return label issues.
2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 |
# File 'app/models/rma.rb', line 2046 def return_label_issues issues = [] return issues unless requested? || awaiting_return? if return_label_required? if number_of_return_labels_required.to_i >= 0 num_labels_requested = number_of_return_labels_required.to_i elsif number_of_return_labels_required.to_i == -1 num_labels_requested = return_delivery&.shipments&.size || 1 end num_labels_generated = return_delivery&.shipments&.label_complete&.size if return_delivery&.return_labels_complete? || return_delivery&. issues << "#{num_labels_requested} labels requested but #{num_labels_generated} labels generated." unless num_labels_requested.positive? && num_labels_requested == num_labels_generated else issues << (return_delivery&.errors&.&.join('. ').presence || 'Return delivery could not be generated!') end end issues.compact end |
#return_label_required? ⇒ Object Also known as: return_label_required
Whether return label required.
1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 |
# File 'app/models/rma.rb', line 1092 def return_label_required? return false if all_items_voided? # this is to prevent this validation from blocking RMAs from being voided # Nothing is coming back, so there is nothing to put a label on — unless one # was already bought. That label stays live and customer-usable, so the RMA # must keep reporting and validating it rather than silently disowning it. return false if all_items_will_not_be_returned? && !return_labels_generated? return false if canada_post_return? # CP returns use the hosted QR policy; we never generate a label for them number_of_return_labels_required.to_i.positive? || (number_of_return_labels_required == -1) # here, -1 means let Heatwave decide end |
#return_labels ⇒ Object
Return labels.
1447 1448 1449 |
# File 'app/models/rma.rb', line 1447 def return_labels return_shipments.filter_map { |s| s.ship_label_pdf(true) } end |
#return_labels_correctly_generated? ⇒ Object
Whether return labels correctly generated.
2069 2070 2071 2072 2073 |
# File 'app/models/rma.rb', line 2069 def return_labels_correctly_generated? return true if skip_return_label_validation return_label_issues.empty? end |
#return_labels_generated? ⇒ Boolean
True once at least one return label has actually been purchased.
1212 1213 1214 |
# File 'app/models/rma.rb', line 1212 def return_labels_generated? return_delivery&.shipments&.label_complete&.any? || false end |
#return_shipments ⇒ ActiveRecord::Associations::CollectionProxy
The associated return shipments.
152 |
# File 'app/models/rma.rb', line 152 has_many :return_shipments, -> { label_complete }, source: :shipments, class_name: 'Shipment', through: :return_delivery |
#return_shipping_address ⇒ Address?
The associated return shipping address.
124 |
# File 'app/models/rma.rb', line 124 belongs_to :return_shipping_address, class_name: 'Address', optional: true |
#return_shipping_method ⇒ Object
Return shipping method.
1127 1128 1129 |
# File 'app/models/rma.rb', line 1127 def return_shipping_method RETURN_SHIPPING_METHODS[customer.store.id.to_s] end |
#returned_rma_items ⇒ Object
Returned rma items.
1725 1726 1727 |
# File 'app/models/rma.rb', line 1725 def returned_rma_items rma_items.returned.reload end |
#rma_inspect_email ⇒ Object
Alias for Company#rma_inspect_email
391 |
# File 'app/models/rma.rb', line 391 delegate :rma_inspect_email, to: :company |
#rma_items ⇒ ActiveRecord::Associations::CollectionProxy
The associated rma items.
155 |
# File 'app/models/rma.rb', line 155 has_many :rma_items, inverse_of: :rma, dependent: :destroy, before_add: :set_rma_items_defaults |
#rma_items_awaiting_inspection ⇒ Object
Rma items awaiting inspection.
1732 1733 1734 |
# File 'app/models/rma.rb', line 1732 def rma_items_awaiting_inspection rma_items.awaiting_inspection.reload end |
#rma_suggested_quotes_options ⇒ Object
Rma suggested quotes options.
852 853 854 855 856 857 858 859 860 861 862 863 864 |
# File 'app/models/rma.rb', line 852 def quotes = [] if customer quotes += customer.quotes.order('quotes.reference_number desc').map do |q| [q.selection_name, q.id] end end if redesign_quote new_selection = [redesign_quote.selection_name, redesign_quote.id] quotes |= [new_selection] end quotes end |
#send_from ⇒ Employee?
The associated send from.
142 |
# File 'app/models/rma.rb', line 142 belongs_to :send_from, class_name: 'Employee', optional: true |
#send_inspection_notification_to_customer ⇒ Object
Send inspection notification to customer.
1330 1331 1332 1333 |
# File 'app/models/rma.rb', line 1330 def send_inspection_notification_to_customer CommunicationBuilder.new(resource: self, sender_party: send_from || customer.primary_sales_rep, template_system_code: 'RMAINSPECT').create end |
#send_rejection_notification_to_customer ⇒ Object
Send rejection notification to customer.
1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 |
# File 'app/models/rma.rb', line 1352 def send_rejection_notification_to_customer # Proof photos live in two categories: `photo` for non-resalable items, # `not_returned_photo` for items that never came back. Attach the ones # that match each item's flags. rejected_uploads = rma_items.rejected.includes(:uploads).flat_map do |ri| ri.uploads.select do |upload| (ri.non_resalable? && upload.category == 'photo') || (ri.not_returned? && upload.category == 'not_returned_photo') end end CommunicationBuilder.new( resource: self, sender_party: send_from || customer.primary_sales_rep, template_system_code: 'RMAITEMREJECTED', uploads: rejected_uploads ).create end |
#send_return_confirmation_email_to_customer ⇒ Object
Send return confirmation email to customer.
1338 1339 1340 1341 |
# File 'app/models/rma.rb', line 1338 def send_return_confirmation_email_to_customer CommunicationBuilder.new(resource: self, sender_party: send_from || customer.primary_sales_rep, template_system_code: 'RMARC').create end |
#send_return_email_to_customer(keep_as_draft: false) ⇒ Object
Send return email to customer.
1322 1323 1324 1325 |
# File 'app/models/rma.rb', line 1322 def send_return_email_to_customer(keep_as_draft: false) CommunicationBuilder.new(resource: self, sender_party: send_from || customer.primary_sales_rep, template_system_code: 'RMA').create(keep_as_draft: keep_as_draft) end |
#serial_numbers ⇒ ActiveRecord::Associations::CollectionProxy
The associated serial numbers.
179 |
# File 'app/models/rma.rb', line 179 has_many :serial_numbers, through: :rma_items |
#service_only? ⇒ Object
Whether service only.
1402 1403 1404 |
# File 'app/models/rma.rb', line 1402 def service_only? rma_items.all? { |rma_item| rma_item.returned_item.tax_class == 'svc' } end |
#set_default_notification_channels ⇒ Object
Set default notification channels.
1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 |
# File 'app/models/rma.rb', line 1593 def set_default_notification_channels return nil if customer.nil? notification_channels = [] notification_channels.concat(customer.notification_channels.rmas.map(&:contact_point)) notification_channels.concat(customer.billing_entity.notification_channels.rmas.map(&:contact_point)) self.transmission_email ||= [] self.transmission_fax ||= [] notification_channels.uniq.each do |cp| next if ((cp.category == 'email') && transmission_email.include?(cp.detail)) || ((cp.category == 'fax') && transmission_fax.include?(cp.detail)) case cp.category when 'email' transmission_email << cp.detail when 'fax' transmission_fax << cp.detail end end end |
#ship_from_address ⇒ Address?
The associated ship from address.
127 |
# File 'app/models/rma.rb', line 127 belongs_to :ship_from_address, class_name: 'Address', optional: true |
#ship_from_attributes ⇒ Object
Ship from attributes.
2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 |
# File 'app/models/rma.rb', line 2530 def ship_from_attributes res = {} # this is an RMA return label # address is original order address res[:address] = ship_from_address # attention_name cannot be blank!!!! res[:attention_name] = res[:address].person_name res[:attention_name] = 'Customer' if res[:attention_name].blank? if res[:address].company_name res[:name] = res[:address].company_name res[:name] = 'Customer' if res[:name].blank? else res[:name] = res[:address].person_name if res[:name].blank? res[:name] = 'Customer' if res[:name].blank? end res[:phone] = (original_order&.shipping_phone || customer.phone || customer.cell_phone || customer.sales_reps.first&.direct_phone || SHIPPING_SHIPPER_CONFIGURATION[customer.country.iso3.to_sym][:shipper_phone]) res[:email] = transmission_email if res[:email].to_s.strip.blank? res[:email] = customer.primary_sales_rep&.email || SHIPPING_SHIPPER_CONFIGURATION[customer.country.iso3.to_sym][:shipper_email] end res[:phone] = res[:phone] if res[:phone] res end |
#ship_to_attributes ⇒ Object
Ship to attributes.
2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 |
# File 'app/models/rma.rb', line 2507 def ship_to_attributes res = {} res[:address] = return_shipping_address if res[:address].company_name # attention_name cannot be blank!!!! res[:attention_name] = res[:address].person_name res[:attention_name] = 'Returns Department' if res[:attention_name].blank? res[:name] = res[:address].company_name else res[:attention_name] = attention_name res[:attention_name] = res[:address].person_name if res[:attention_name].blank? res[:name] = attention_name res[:name] = res[:address].person_name if res[:name].blank? end # sort of a kludge but for now use shipping configuration's sender phone for legacy matching res[:phone] = SHIPPING_SHIPPER_CONFIGURATION[customer.country.iso3.to_sym][:shipper_phone] res[:email] = SHIPPING_SHIPPER_CONFIGURATION[customer.country.iso3.to_sym][:shipper_email] res end |
#shipment_tracking_numbers ⇒ Array<ShipmentTrackingNumber>
Combines generated-label and externally supplied inbound tracking numbers.
784 785 786 787 788 789 |
# File 'app/models/rma.rb', line 784 def shipment_tracking_numbers stn = [] stn += return_shipments.flat_map(&:shipment_tracking_number) stn += tracking_numbers.map { |tn_raw| ShipmentTrackingNumber.new(tn_raw) } stn.uniq end |
#shipping_option ⇒ ShippingOption?
The associated shipping option.
139 |
# File 'app/models/rma.rb', line 139 belongs_to :shipping_option, optional: true |
#should_validate_shipping_option? ⇒ Object
Whether should validate shipping option.
2571 2572 2573 2574 2575 |
# File 'app/models/rma.rb', line 2571 def should_validate_shipping_option? return false if skip_shipping_option_validation number_of_return_labels_required.present? && !number_of_return_labels_required.zero? end |
#split_line_discount_by_coupon(discount_total, source_discounts) ⇒ Hash{LineDiscount => BigDecimal}
Attributes a credit line's total discount back to the source invoice line's coupons,
split in proportion to each coupon's amount so the credit mirrors the invoice's
coupon breakdown. Returns positive amounts (the invoice's discounts are negative).
2408 2409 2410 2411 2412 2413 |
# File 'app/models/rma.rb', line 2408 def split_line_discount_by_coupon(discount_total, source_discounts) total_source = source_discounts.sum { |ld| ld.amount.to_d } return {} unless total_source.nonzero? source_discounts.index_with { |ld| (discount_total * (ld.amount.to_d / total_source)).round(2) } end |
#stores_for_return_select ⇒ Object
Stores for return select.
888 889 890 891 892 |
# File 'app/models/rma.rb', line 888 def stores_for_return_select return Address.none unless company_id Address.joins(:warehouse_store).where(stores: { company_id: company_id }) end |
#suggested_contact_names ⇒ Object
Suggested contact names.
1299 1300 1301 1302 1303 1304 1305 1306 |
# File 'app/models/rma.rb', line 1299 def suggested_contact_names contact_names = [] contact_names += customer.contacts.active.distinct.pluck(:full_name) if customer contact_names << original_order.contact.full_name if original_order&.contact&.full_name.present? contact_names << original_invoice.shipping_address.person_name if original_invoice&.shipping_address&.person_name.present? contact_names << contact_name if contact_name.present? contact_names.uniq.sort end |
#to_s ⇒ Object Also known as: name, reference_number
To s.
1018 1019 1020 |
# File 'app/models/rma.rb', line 1018 def to_s rma_number end |
#unreturn_all_items ⇒ Object
Unreturn all items.
1586 1587 1588 |
# File 'app/models/rma.rb', line 1586 def unreturn_all_items rma_items.where(state: 'returned').find_each(&:unreturn) end |
#unvoid_all_items ⇒ Object
Unvoid all items.
1578 1579 1580 1581 |
# File 'app/models/rma.rb', line 1578 def unvoid_all_items rma_items.each(&:unvoid) return_shipments.update_all(tracking_state: 'not_yet_in_system') if return_shipments.present? && return_shipments.all?(&:tracking_ignore?) end |
#unvoid_all_items_and_self ⇒ Object
Unvoid all items and self.
1559 1560 1561 1562 1563 1564 1565 |
# File 'app/models/rma.rb', line 1559 def unvoid_all_items_and_self Rma.transaction do unvoid_all_items self.skip_reminders = false sync_state end end |
#update_draft_communications_with_new_attachments ⇒ Object
Update draft communications with new attachments.
1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 |
# File 'app/models/rma.rb', line 1878 def # Find draft communications for this RMA draft_communications = Communication.where( resource: self, state: 'draft' ) return if draft_communications.empty? # Get the new attachments that should be included new_uploads = [] if awaiting_return? new_uploads << return_instructions if return_instructions # Also include return labels if available return_labels_uploads = return_labels new_uploads += return_labels_uploads if return_labels_uploads.present? # Paperless QR codes: attach so CommunicationMailer can embed them inline # (CID) in the email body (see Rma#paperless_return_html). new_uploads += return_shipments.filter_map(&:paperless_qr_png) end return if new_uploads.empty? new_uploads = new_uploads.compact.uniq(&:id) # Update each draft communication draft_communications.each do |communication| communication.with_lock do # Remove old RMA-related attachments (return instructions, labels, QR codes) communication.uploads.where( category: %w[rma_return_instructions_pdf ship_label_pdf paperless_qr_png] ).each do |upload| communication.uploads.delete(upload) end # Add new attachments; duplicate rows can happen under concurrent submits. new_uploads.each do |upload| next if communication.uploads.exists?(id: upload.id) begin communication.uploads << upload rescue ActiveRecord::RecordNotUnique # Another request attached this upload first; join already exists. next end end # Refresh the paperless block between its comment markers — touching ONLY # that block, not the curated body (re-rendering the whole body would # regenerate the signature and trip unrelated content validations). Run # unconditionally: for a paperless return it injects the QR HTML (the body # was first rendered before labels existed, so it came out empty); for a # return that lost paperless support it injects an empty block, clearing # any stale QR/CID markup left from a prior generation. inject_paperless_block!(communication) communication.save! end end end |
#update_linked_credit_orders(allow_transition_failure: true) ⇒ Object
This pushes the credit order to the return state
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 |
# File 'app/models/rma.rb', line 2456 def update_linked_credit_orders(allow_transition_failure: true) Order.transaction do credit_orders.each do |credit_order| # If a shipping line already exists, don't touch unless credit_order.line_items.shipping_only.exists? # First we will copy over the shipping line from the original order shipping_lines = [] credit_order.line_items.joins(credit_rma_item: :returned_line_item).find_each do |coli| shipping_lines += coli.credit_rma_item.returned_line_item.resource.line_items.shipping_only end shipping_lines.uniq! shipping_lines.each do |shipping_line| co_shipping_line = shipping_line.dup # It's always one for shipping co_shipping_line.quantity = co_shipping_line.qty_shipped = -1 # Detaching to see if we can eliminate it co_shipping_line.shipping_cost_id = nil # co_shipping_line.price = -co_shipping_line.price # co_shipping_line.discounted_price = -co_shipping_line.discounted_price co_shipping_line.resource = credit_order co_shipping_line.delivery_id = nil # Let's see if this takes co_shipping_line.save! # Now copy the discounts from the original shipping_line.line_discounts.each do |ld| credit_order_discount = credit_order.discounts.find_by(coupon_id: ld.discount.coupon_id) unless credit_order_discount # If the discount doesn't already exist, we clone and create it credit_order_discount = ld.discount.dup credit_order_discount.itemizable = credit_order credit_order_discount.amount = credit_order_discount.user_amount = -credit_order_discount.amount credit_order_discount.save! end # Now line discount can be cloned co_shipping_line.line_discounts.create!(discount_id: credit_order_discount.id, coupon_id: ld.coupon_id, amount: -ld.amount) end end end # Then mark the credit order returned if allow_transition_failure credit_order.returned else credit_order.returned! end end end end |
#uploads ⇒ ActiveRecord::Associations::CollectionProxy
The associated uploads.
164 |
# File 'app/models/rma.rb', line 164 has_many :uploads, as: :resource, dependent: :destroy |
#versions_for_audit_trail(_params = {}) ⇒ Object
Versions for audit trail.
662 663 664 665 666 667 668 669 670 671 |
# File 'app/models/rma.rb', line 662 def versions_for_audit_trail(_params = {}) query_sql = " (item_type = 'Rma' AND item_id = :id) OR ( item_type = 'RmaItem' AND reference_data @> :rma_json ) " RecordVersion.where(query_sql, id: id, rma_json: { rma_id: id }.to_json) end |
#versions_for_dates_tracker ⇒ Object
Versions for dates tracker.
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 |
# File 'app/models/rma.rb', line 714 def versions_for_dates_tracker shipment_id = return_shipments.first&.id shipment_sql = if shipment_id.present? "select object_changes #>> '{" + '"tracking_state"' + ",1}' as state,timezone('America/Chicago', timestamptz(created_at))::date from versions where (object_changes->'tracking_state'->>1 in ('in_transit','delivered')) and item_type = 'Shipment' and item_id = #{shipment_id} union all" else '' end query_sql = <<-SQL.squish #{shipment_sql} select object_changes #>> '{"state",1}' as state,timezone('America/Chicago', timestamptz(created_at))::date from versions where (object_changes->'state'->>1 in ('awaiting_inspection','returned','credit_in_process','credited_partially_refunded','credited_fully_refunded')) and item_type = 'Rma' and item_id = #{id} SQL # Audit trail is supplementary here — if the versions DB blips, render an empty # tracker rather than 500 the whole RMA tab (AppSignal #4929). results = RecordVersionBase.safe_read([]) do RecordVersion.lease_connection.execute(query_sql).to_a.map(&:symbolize_keys) end dates_tracker = if return_shipments.present? { dropped_off: nil, warehouse_received: nil, submitted_for_inspection: nil, returned: nil, process_refund: nil, refunded: nil, fully_refunded: nil } else { warehouse_received: nil, submitted_for_inspection: nil, returned: nil, process_refund: nil, refunded: nil, fully_refunded: nil } end results.each do |r| dates_tracker.each do |k, v| if r[:state] == k.to_s dates_tracker[k] = r[:timezone] elsif r[:state] == 'in_transit' dates_tracker[:dropped_off] = r[:timezone] elsif r[:state] == 'delivered' dates_tracker[:warehouse_received] = r[:timezone] elsif r[:state] == 'awaiting_inspection' dates_tracker[:submitted_for_inspection] = r[:timezone] elsif k.to_s == 'warehouse_received' && v.nil? && r[:state] == 'returned' dates_tracker[:warehouse_received] = r[:timezone] elsif k.to_s == 'submitted_for_inspection' && v.nil? && r[:state] == 'returned' dates_tracker[:submitted_for_inspection] = r[:timezone] elsif r[:state] == 'credit_in_process' dates_tracker[:process_refund] = r[:timezone] elsif r[:state] == 'credited_partially_refunded' dates_tracker[:refunded] = r[:timezone] elsif r[:state] == 'credited_fully_refunded' dates_tracker[:refunded] = r[:timezone] dates_tracker[:fully_refunded] = r[:timezone] end end end dates_tracker end |
#versions_for_state_tracker ⇒ Object
Versions for state tracker.
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 |
# File 'app/models/rma.rb', line 676 def versions_for_state_tracker where_sql = "object_changes ? 'state'" select_sql = %q{ id,object_changes #>> '{"state",1}' as state,created_at, (lag(object_changes #>> '{"state",1}') OVER (ORDER BY id desc)) as new_state, (lag(created_at) OVER (ORDER BY id desc)) as new_state_created_at } # Supplementary on the RMA tab — degrade to an empty tracker (rather than 500 the page) # if the versions DB blips. `.to_a` forces the load inside the guard. Same surface as #4929. version_records = RecordVersionBase.safe_read([]) do versions_for_audit_trail.where(where_sql).select(select_sql).order(:id, :created_at).to_a end state_tracker = [] version_records.each do |vr| NOTIFICATIONS_RMA_STATES.each do |nrs| state_info = {} next unless vr.state == nrs && vr.state != vr.new_state new_state_created_at = vr.new_state_created_at.present? ? vr.new_state_created_at.to_date : Date.current state_info[:state] = vr.state state_info[:date] = vr.created_at state_info[:days] = vr.created_at.to_date.working_days_until(new_state_created_at) if %w[auto_return_review awaiting_inspection].include?(vr.state) state_info[:sent_to] = vr.state == 'auto_return_review' ? EMAILS_FOR_NOTIFICATIONS.second : EMAILS_FOR_NOTIFICATIONS.first state_info[:number_of_notifications] = vr.created_at.working_days_until(new_state_created_at) elsif vr.created_at.working_days_until(new_state_created_at) > 3 state_info[:sent_to] = EMAILS_FOR_NOTIFICATIONS.second state_info[:number_of_notifications] = vr.created_at.working_days_until(new_state_created_at) - 3 end state_tracker << state_info if state_info[:number_of_notifications].present? end end state_tracker end |
#void_all_items ⇒ Object
Void all items.
1570 1571 1572 1573 |
# File 'app/models/rma.rb', line 1570 def void_all_items rma_items.each(&:void) return_shipments.update_all(tracking_state: 'tracking_ignore') if return_shipments.present? && return_shipments.all?(&:not_yet_in_system?) end |
#void_all_items_and_self ⇒ Object
Void all items and self.
1548 1549 1550 1551 1552 1553 1554 |
# File 'app/models/rma.rb', line 1548 def void_all_items_and_self Rma.transaction do void_all_items self.skip_reminders = true # prevent email from triggering sync_state # some items might have been received already end end |