Class: CreditMemo
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- CreditMemo
- Includes:
- Models::AccountingDocumentTransmittable, Models::Auditable, Models::Itemizable, Models::LiquidMethods, Models::SupportCaseLinkable, Models::TaxableResource, Models::TaxjarSubmittable, PgSearch::Model
- Defined in:
- app/models/credit_memo.rb
Overview
== Schema Information
Table name: credit_memos
Database name: primary
id :integer not null, primary key
category :string(255)
currency :string(255)
discount :decimal(8, 2)
document_date :date
gl_date :date
line_offset :decimal(8, 2)
line_total :decimal(10, 2)
line_total_discounted :decimal(8, 2)
old_line_total :decimal(8, 2)
old_tax_total :decimal(8, 2)
old_total :decimal(8, 2)
order_type :string(255)
original_order_missing_reason :string(255)
original_po_number :string(255)
reference_number :string(255) not null
remark :text
report_grouping :string
request_date :date
shipping_cost :decimal(8, 2)
shipping_coupon :decimal(8, 2)
state :string(255)
tax_date :date
tax_exempt :boolean
tax_offset :decimal(8, 2)
tax_total :decimal(8, 2)
taxable_total :decimal(12, 2)
taxjar_state :string
total :decimal(10, 2)
transmission_state :string(255)
uploads_count :integer
created_at :datetime
updated_at :datetime
account_specialist_id :integer
billing_address_id :integer
billing_customer_id :integer
company_id :integer
creator_id :integer
credit_order_id :integer
customer_id :integer
local_sales_rep_id :integer
original_invoice_id :integer
original_order_id :integer
primary_sales_rep_id :integer
purchase_order_id :integer
resource_tax_rate_id :integer
rma_id :integer
secondary_sales_rep_id :integer
shipping_address_id :integer
support_case_id :integer
updater_id :integer
Indexes
idx_billing_address_id (billing_address_id)
idx_company_id_customer_id_gl_date (company_id,customer_id,gl_date)
idx_credit_order_id (credit_order_id)
idx_customer_id_state (customer_id,state)
idx_original_order_id (original_order_id)
idx_rma_id (rma_id)
idx_shipping_address_id (shipping_address_id)
index_credit_memos_on_billing_customer_id (billing_customer_id)
index_credit_memos_on_local_sales_rep_id (local_sales_rep_id)
index_credit_memos_on_original_invoice_id (original_invoice_id)
index_credit_memos_on_reference_number (reference_number) UNIQUE
index_credit_memos_on_report_grouping (report_grouping)
index_credit_memos_on_resource_tax_rate_id (resource_tax_rate_id)
index_credit_memos_on_state_and_transmission_state (state,transmission_state)
index_credit_memos_on_support_case_id (support_case_id)
Foreign Keys
fk_credit_memos_resource_tax_rates (resource_tax_rate_id => resource_tax_rates.id) ON DELETE => cascade
fk_rails_... (billing_address_id => addresses.id)
fk_rails_... (billing_customer_id => parties.id)
fk_rails_... (customer_id => parties.id)
fk_rails_... (local_sales_rep_id => parties.id)
fk_rails_... (shipping_address_id => addresses.id)
fk_rails_... (support_case_id => support_cases.id)
Defined Under Namespace
Classes: PdfGenerator, SubmitToTaxjar
Constant Summary collapse
- REFERENCE_NUMBER_PATTERN =
Regex matching the canonical
CM…reference-number format (case-insensitive). /^CM\d+/i- CATEGORIES =
Allowed categories:
rma(issued against an existing return / RMA) or
standalone(issued without an originating invoice — warranties, rebates). %w[rma standalone]
- LINE_ITEM_CATEGORIES =
Mapping from CRM-facing line-item category labels to the JDE return /
warranty GL account they post against and the business unit on the
offset side. Drives the "Add line item" dropdown on credit memos and
the GL split when Models::AccountingDocumentTransmittable transmits. [{ name: 'Warranty', account_number: WARRANTY_CLAIMS_ACCOUNT, business_unit: 'default' }, { name: 'Rebate', account_number: REBATES_STANDALONE_ACCOUNT, business_unit: 'default' }, { name: 'Lead Protection', account_number: RETURN_COUPONS_ACCOUNT, business_unit: 'default' }, { name: 'Trade Discount', account_number: RETURN_COUPONS_ACCOUNT, business_unit: 'default' }, { name: 'Coupon (Goods)', account_number: RETURN_COUPONS_ACCOUNT, business_unit: 'default' }, { name: 'Coupon (Freight)', account_number: RETURN_FREIGHT_COUPONS_ACCOUNT, business_unit: 'sales' }, { name: 'Freight', account_number: RETURN_FREIGHT_COUPONS_ACCOUNT, business_unit: 'sales' }, { name: 'Misc', account_number: RETURN_COUPONS_ACCOUNT, business_unit: 'default' }, { name: 'Item', account_number: CUSTOMER_RETURNS, business_unit: 'default' }]
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
- #billing_address_id ⇒ Object readonly
- #category ⇒ Object readonly
- #company_id ⇒ Object readonly
- #credit_order_id ⇒ Object readonly
-
#customer_company ⇒ Object
Returns the value of attribute customer_company.
- #customer_id ⇒ Object readonly
-
#customer_name ⇒ Object
Returns the value of attribute customer_name.
-
#do_not_set_totals ⇒ Object
Returns the value of attribute do_not_set_totals.
- #document_date ⇒ Object readonly
- #gl_date ⇒ Object readonly
- #order_type ⇒ Object readonly
- #original_invoice_id ⇒ Object readonly
-
#original_invoice_ref ⇒ Object
Returns the value of attribute original_invoice_ref.
- #original_order_id ⇒ Object readonly
- #original_order_missing_reason ⇒ Object readonly
-
#original_order_ref ⇒ Object
Returns the value of attribute original_order_ref.
- #request_date ⇒ Object readonly
- #shipping_address_id ⇒ Object readonly
- #tax_date ⇒ Object readonly
Attributes included from Models::Itemizable
#force_total_reset, #total_reset
Belongs to collapse
- #billing_address ⇒ Address
- #billing_customer ⇒ Customer
- #company ⇒ Company
- #credit_order ⇒ Order
- #customer ⇒ Customer
- #original_invoice ⇒ Invoice
- #original_order ⇒ Order
- #rma ⇒ Rma
- #shipping_address ⇒ Address (also: #destination_address)
Methods included from Models::SupportCaseLinkable
Methods included from Models::Auditable
Methods included from Models::TaxableResource
Methods included from Models::Itemizable
#account_specialist, #local_sales_rep, #primary_sales_rep, #secondary_sales_rep
Has one collapse
Has many collapse
- #activities ⇒ ActiveRecord::Relation<Activity>
- #communications ⇒ ActiveRecord::Relation<Communication>
- #ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
- #line_discounts ⇒ ActiveRecord::Relation<LineDiscount>
- #line_items ⇒ ActiveRecord::Relation<LineItem>
- #outgoing_payment_items ⇒ ActiveRecord::Relation<OutgoingPaymentItem>
- #outgoing_payments ⇒ ActiveRecord::Relation<OutgoingPayment>
- #payments ⇒ ActiveRecord::Relation<Payment>
- #receipt_details ⇒ ActiveRecord::Relation<ReceiptDetail>
- #receipts ⇒ ActiveRecord::Relation<Receipt>
-
#uploads ⇒ ActiveRecord::Relation<Upload>
has_many :shipping_costs, -> { order(:cost) }, :as => :resource, :dependent => :destroy.
Methods included from Models::Itemizable
Class Method Summary collapse
-
.available_to_apply ⇒ ActiveRecord::Relation<CreditMemo>
A relation of CreditMemos that are available to apply.
-
.credit_memo_count(company_id = nil, where_conditions = nil, where_not_conditions = nil) ⇒ Integer
Count credit memos optionally scoped to a company plus arbitrary
where/where.notclauses. -
.for_company_id ⇒ ActiveRecord::Relation<CreditMemo>
A relation of CreditMemos that are for company id.
-
.new_credit_memo_from_order(order) ⇒ void
Builds a complete RMA-category credit memo from a credit-Order — copies the line items, applies the source order's tax (preserving marketplace-set rates from CI invoices), copies discounts and line-discount allocations, then transitions the memo through
approve!andprinted!to post the ledger transactions. -
.printed ⇒ ActiveRecord::Relation<CreditMemo>
A relation of CreditMemos that are printed.
-
.states_for_select ⇒ Array<Array(String, Symbol)>
State-machine states formatted for a Rails
selecthelper —[[human_name, machine_value], …], sorted alphabetically.
Instance Method Summary collapse
-
#available_to_refund ⇒ BigDecimal
Dollar amount that can still be refunded back to the original payment instrument(s) — sum of
available_to_refundacross the original invoice's refundable Payments. -
#balance ⇒ BigDecimal
Outstanding balance — credit-memo total (a negative amount) minus what's already been offset/refunded.
-
#build_activity ⇒ Activity
Builds (but does not save) a new Activity attached to this credit memo with the customer party pre-populated.
- #can_be_refunded? ⇒ Boolean
-
#crm_link ⇒ String
CRM URL for the credit-memo show page.
-
#currency_symbol ⇒ String
Currency symbol (e.g.
$,€) for this credit memo's #currency. -
#disable_auto_coupon ⇒ true
Models::Itemizable hook — credit memos never auto-apply customer coupons (a credit is the customer being made whole, not a new sale).
-
#do_not_detect_shipping ⇒ true
Models::Itemizable hook — credit memos never re-detect shipping automatically (the credit is for the original shipping cost, not a newly-quoted rate).
- #editing_locked? ⇒ Boolean
-
#file_name ⇒ String
Filename used when attaching the credit-memo PDF to email or storing in S3.
- #funds_fully_offset? ⇒ Boolean
-
#funds_offset ⇒ BigDecimal
Net dollars already offset against this credit memo — sum of applied ReceiptDetails (credit applied to invoices, write-offs) minus any outgoing payments (refund checks).
- #funds_partially_offset? ⇒ Boolean
-
#generate_pdf ⇒ Upload
Renders a fresh credit-memo PDF via PdfGenerator, writes it to temp storage, uploads it to S3 under
credit_memo_pdfand attaches the Upload. -
#get_or_regen_pdf(logger = nil) ⇒ Upload
Returns the persisted credit-memo PDF Upload, regenerating it via #generate_pdf if the upload row is missing or its file is gone from S3.
- #is_build_com? ⇒ Boolean
- #is_edi? ⇒ Boolean
-
#name ⇒ String
Display name used in lists and links.
- #no_funds_offset? ⇒ Boolean
- #payment_status ⇒ Hash
- #prevent_recalculate_shipping? ⇒ Boolean
-
#process_refund(auth_hash) ⇒ Hash{Symbol => Object}
Refunds the customer through gateway authorisations, splitting the total across one or more Payment ids per
auth_hash. -
#set_reps ⇒ void
Inherits sales-rep attribution from the original invoice when any rep slot is unset.
-
#to_s ⇒ String
Human-readable identifier used in audit logs and error messages.
Methods included from Models::TaxjarSubmittable
#customer_sync_instance, #delete_from_taxjar, #evaluate_taxjar_submission, #record_already_exists_on_taxjar?, #resubmit_to_taxjar, #should_be_submitted_to_taxjar?, #should_sync_customer_with_taxjar?, #submit_to_taxjar, #sync_customer_with_taxjar, #taxjar_customer_id, #taxjar_submission_instance
Methods included from Models::SupportCaseLinkable
#support_case_ref, #support_case_ref=
Methods included from Models::AccountingDocumentTransmittable
#can_be_transmitted?, #fallback_notification_channel_type, #notification_channel_sort_order, #notification_channel_types, #notification_channels, #own_notification_channel_type, #post_communication_exception_hook, #post_communication_sent_hook, #primary_transmission_contact, #primary_transmission_contact_point_id, #transmission_contact_points
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
Methods included from Models::TaxableResource
#apply_tax_rate_to_line_items, #build_tax_params, #calculate_tax_for_all_lines, #copy_tax_rate, #effective_date, #get_rates_for_line, #get_tax_rate, #manual_rate_goods, #manual_rate_services, #manual_rate_shipping, #origin_address, #refresh_tax_rate, #resource_not_taxable?, #set_initial_tax_rate, #should_refresh_tax_rate?, #state_code, #state_code_sym, #taxes_grouped_by_rate, #taxes_grouped_by_type
Methods included from Models::Itemizable
#add_line_item, #additional_items, #assign_sequence, #billing_entity, #breakdown_of_prices, #calculate_actual_insured_value, #calculate_discounts, #calculate_shipping_cost, #coupon_search, #customer_applied_coupons, #customer_can_apply_coupon?, #discounts_changed?, #discounts_grouped_by_coupon, #discounts_subtotal, #effective_discount, #effective_shipping_discount, #has_kits?, #has_kits_or_serial_numbers?, #has_serial_numbers?, #is_credit_order?, #line_items_requiring_serial_number, #line_items_with_counters, #line_total_plus_tax, #main_rep, #perform_db_total, #purge_empty_quoting_deliveries, #purge_shipping_when_no_other_lines, #remove_line_item, #require_total_reset?, #reset_discount, #set_for_recalc, #set_signature_confirmation_on_shipping_address_change, #set_totals, #shipping_conditions_changed?, #shipping_discounted, #shipping_method_changed?, #should_recalculate_shipping?, #smartinstall_data, #smartsupport_data, #store, #subtotal_cogs, #sync_shipping_line, #total_cogs
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#billing_address_id ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
#category ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
#company_id ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
#credit_order_id ⇒ Object (readonly)
144 |
# File 'app/models/credit_memo.rb', line 144 validates :credit_order_id, presence: { if: proc { |cm| cm.category == 'rma' } } |
#customer_company ⇒ Object
Returns the value of attribute customer_company.
171 172 173 |
# File 'app/models/credit_memo.rb', line 171 def customer_company @customer_company end |
#customer_id ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
#customer_name ⇒ Object
Returns the value of attribute customer_name.
171 172 173 |
# File 'app/models/credit_memo.rb', line 171 def customer_name @customer_name end |
#do_not_set_totals ⇒ Object
Returns the value of attribute do_not_set_totals.
171 172 173 |
# File 'app/models/credit_memo.rb', line 171 def do_not_set_totals @do_not_set_totals end |
#document_date ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
#gl_date ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
#order_type ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
#original_invoice_id ⇒ Object (readonly)
147 |
# File 'app/models/credit_memo.rb', line 147 validates :original_invoice_id, numericality: { allow_nil: true } |
#original_invoice_ref ⇒ Object
Returns the value of attribute original_invoice_ref.
171 172 173 |
# File 'app/models/credit_memo.rb', line 171 def original_invoice_ref @original_invoice_ref end |
#original_order_id ⇒ Object (readonly)
146 |
# File 'app/models/credit_memo.rb', line 146 validates :original_order_id, numericality: { allow_nil: true } |
#original_order_missing_reason ⇒ Object (readonly)
148 |
# File 'app/models/credit_memo.rb', line 148 validates :original_order_missing_reason, presence: { if: proc { |cm| cm.original_invoice_id.nil? && !cm.fully_offset? } } |
#original_order_ref ⇒ Object
Returns the value of attribute original_order_ref.
171 172 173 |
# File 'app/models/credit_memo.rb', line 171 def original_order_ref @original_order_ref end |
#request_date ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
#shipping_address_id ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
#tax_date ⇒ Object (readonly)
143 |
# File 'app/models/credit_memo.rb', line 143 validates :category, :customer_id, :company_id, :billing_address_id, :shipping_address_id, :request_date, :tax_date, :document_date, :gl_date, :order_type, presence: true |
Class Method Details
.available_to_apply ⇒ ActiveRecord::Relation<CreditMemo>
A relation of CreditMemos that are available to apply. Active Record Scope
167 |
# File 'app/models/credit_memo.rb', line 167 scope :available_to_apply, -> { where(state: %w[printed partially_offset]) } |
.credit_memo_count(company_id = nil, where_conditions = nil, where_not_conditions = nil) ⇒ Integer
Count credit memos optionally scoped to a company plus arbitrary
where/where.not clauses. Used by dashboard widgets that show
"open RMAs" / "fully offset this month" tallies without needing to
re-implement the filter chain.
248 249 250 251 252 253 254 |
# File 'app/models/credit_memo.rb', line 248 def self.credit_memo_count(company_id = nil, where_conditions = nil, where_not_conditions = nil) c = CreditMemo.order('id') c = c.where(company_id: company_id) unless company_id.nil? c = c.where(where_conditions) unless where_conditions.nil? c = c.where.not(where_not_conditions) unless where_not_conditions.nil? c.count end |
.for_company_id ⇒ ActiveRecord::Relation<CreditMemo>
A relation of CreditMemos that are for company id. Active Record Scope
169 |
# File 'app/models/credit_memo.rb', line 169 scope :for_company_id, ->(company_id) { where(company_id: company_id) } |
.new_credit_memo_from_order(order) ⇒ void
This method returns an undefined value.
Builds a complete RMA-category credit memo from a credit-Order —
copies the line items, applies the source order's tax (preserving
marketplace-set rates from CI invoices), copies discounts and
line-discount allocations, then transitions the memo through
approve! and printed! to post the ledger transactions. Wrapped
in a single transaction so a partial failure rolls back. Also
re-points refundable Payments to the new memo so refunds can
process against original authorisations.
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 |
# File 'app/models/credit_memo.rb', line 519 def self.new_credit_memo_from_order(order) CreditMemo.transaction do memo = CreditMemo.new memo.category = 'rma' memo.credit_order = order if order.rma.original_invoice.present? memo.original_invoice = order.rma.original_invoice memo.report_grouping = order.rma.original_invoice.report_grouping memo.original_order = order.rma.original_order if order.rma.original_order.present? memo.order_type = order.order_type else memo.original_order_missing_reason = 'Not provided on RMA' memo.order_type = Order::SALES_ORDER end invoice = memo.original_invoice.nil? ? nil : memo.original_invoice memo.rma = order.rma memo.billing_address = order.billing_address memo.shipping_address = order.shipping_address unless invoice.nil? memo.primary_sales_rep = invoice.primary_sales_rep memo.secondary_sales_rep = invoice.secondary_sales_rep memo.local_sales_rep = invoice.local_sales_rep end # memo.account_specialist = order.account_specialist memo.currency = order.currency memo.tax_exempt = order.tax_exempt # Build a map between source order line items and their credit memo copies # to later reassign line_discounts properly line_item_map = {} from_ci_invoice = order.ci_invoice_credit_order? order.line_items.each do |li| copy = li.deep_dup # Clear duplicated line_discounts - they reference order's discount records # We'll recreate them after creating credit memo's discount records copy.line_discounts.clear copy.resource = nil copy.credit_rma_item_id = nil copy.delivery_id = nil if from_ci_invoice # Inherit the exact per-line tax from the source credit order. The # line item's `before_create :set_initial_tax_rate` will still # reassign tax_rate / tax_type / resource_tax_rate_id to the credit # memo's own (matching) rate; `do_not_calculate_tax = true` prevents # tax_total from being recomputed from rate × discounted_price, which # would drop marketplace-set (CI invoice) amounts that do not align # with our rate. copy.tax_rate = li.tax_rate copy.tax_type = li.tax_type copy.resource_tax_rate_id = li.resource_tax_rate_id copy.tax_total = li.tax_total copy.do_not_calculate_tax = true else copy.tax_rate = nil copy.tax_type = nil copy.resource_tax_rate_id = nil copy.tax_total = 0 end copy.cm_category = (li.is_shipping? ? 'Freight' : 'Item') copy.credit_order_line_item_id = li.id memo.line_items << copy line_item_map[li.id] = copy end memo.tax_total = order.tax_total memo.line_total = order.line_total memo.total = order.total memo.discount = order.line_items.to_a.sum(&:coupon_amount) memo.shipping_cost = 0 memo.customer = order.customer memo.company = order.customer.store.company memo.request_date = order.rma.created_at.to_datetime.to_date memo.tax_date = order.tax_date || order.shipped_date || Date.current memo.gl_date = Date.current memo.document_date = Date.current memo.original_po_number = order.rma.original_invoice.po_number if order.rma.original_invoice.present? memo.save! # Create discount records for the credit memo and build a map # from original discount_id => new credit memo discount_id discount_map = {} order.discounts.each do |discount| copy_discount = discount.dup copy_discount.itemizable = memo copy_discount.save! discount_map[discount.id] = copy_discount.id end # Recreate line_discounts on the copied credit memo line items with valid # credit memo-scoped discount_ids. If a referenced discount is missing on the # order (data drift), fall back to finding/creating a credit memo-level # discount by coupon_id to preserve amounts and avoid FK violations. line_item_map.each do |original_li_id, memo_li| original_li = order.line_items.find { |li| li.id == original_li_id } next unless original_li original_li.line_discounts.each do |ld| new_discount_id = discount_map[ld.discount_id] if new_discount_id.blank? # Fallback: find or create discount on credit memo by coupon_id memo_discount = memo.discounts.find_by(coupon_id: ld.coupon_id) memo_discount ||= Discount.create!(itemizable: memo, coupon_id: ld.coupon_id, amount: 0) new_discount_id = memo_discount.id end memo_li.line_discounts.create!(amount: ld.amount, coupon_id: ld.coupon_id, discount_id: new_discount_id) end memo_li.save! end # Recalculate discount total from line discounts memo.discount = memo.line_items.to_a.sum(&:coupon_amount) memo.approve! memo.printed! order.line_items.collect do |li| li.credit_rma_item.returned_line_item.resource rescue StandardError nil end.compact.uniq.each do |oo| oo.payments.each do |pp| pp.update(credit_memo_id: memo.id) if pp. end end end end |
.printed ⇒ ActiveRecord::Relation<CreditMemo>
A relation of CreditMemos that are printed. Active Record Scope
168 |
# File 'app/models/credit_memo.rb', line 168 scope :printed, -> { where(state: %w[printed]) } |
.states_for_select ⇒ Array<Array(String, Symbol)>
State-machine states formatted for a Rails select helper —
[[human_name, machine_value], …], sorted alphabetically. Used by
the CRM credit-memo filter dropdown.
235 236 237 |
# File 'app/models/credit_memo.rb', line 235 def self.states_for_select CreditMemo.state_machines[:state].states.map { |s| [s.human_name.titleize, s.name] }.sort end |
Instance Method Details
#activities ⇒ ActiveRecord::Relation<Activity>
134 |
# File 'app/models/credit_memo.rb', line 134 has_many :activities, as: :resource, dependent: :nullify |
#available_to_refund ⇒ BigDecimal
Dollar amount that can still be refunded back to the original
payment instrument(s) — sum of available_to_refund across the
original invoice's refundable Payments. Returns 0 when the RMA
specifies a non-original refund method (e.g. check, store credit).
383 384 385 386 387 388 389 390 391 392 393 |
# File 'app/models/credit_memo.rb', line 383 def available_to_refund if original_invoice.nil? or (rma.present? and rma.payment_method != 'Original Payment Method') available = BigDecimal('0.00') else available = BigDecimal('0.00') original_invoice.payments.can_be_refunded.each do |payment| available += payment.available_to_refund end end available end |
#balance ⇒ BigDecimal
Outstanding balance — credit-memo total (a negative amount) minus
what's already been offset/refunded. Reaches zero once fully offset.
345 346 347 |
# File 'app/models/credit_memo.rb', line 345 def balance total - funds_offset end |
#billing_address ⇒ Address
126 |
# File 'app/models/credit_memo.rb', line 126 belongs_to :billing_address, class_name: 'Address', optional: true |
#billing_customer ⇒ Customer
124 |
# File 'app/models/credit_memo.rb', line 124 belongs_to :billing_customer, class_name: 'Customer', inverse_of: :billing_credit_memos, optional: true |
#build_activity ⇒ Activity
Builds (but does not save) a new Activity attached to this credit
memo with the customer party pre-populated.
353 354 355 |
# File 'app/models/credit_memo.rb', line 353 def build_activity activities.new(resource: self, party: customer) end |
#can_be_refunded? ⇒ Boolean
337 338 339 |
# File 'app/models/credit_memo.rb', line 337 def can_be_refunded? (printed? or partially_offset?) and available_to_refund > 0 end |
#communications ⇒ ActiveRecord::Relation<Communication>
141 |
# File 'app/models/credit_memo.rb', line 141 has_many :communications, -> { order(:id).reverse_order }, as: :resource, dependent: :nullify |
#credit_order ⇒ Order
119 |
# File 'app/models/credit_memo.rb', line 119 belongs_to :credit_order, class_name: 'Order', optional: true |
#crm_link ⇒ String
CRM URL for the credit-memo show page.
460 461 462 |
# File 'app/models/credit_memo.rb', line 460 def crm_link UrlHelper.instance.credit_memo_path(self) end |
#currency_symbol ⇒ String
Currency symbol (e.g. $, €) for this credit memo's #currency.
446 447 448 |
# File 'app/models/credit_memo.rb', line 446 def currency_symbol Money::Currency.new(currency).symbol end |
#customer ⇒ Customer
123 |
# File 'app/models/credit_memo.rb', line 123 belongs_to :customer, inverse_of: :credit_memos |
#disable_auto_coupon ⇒ true
Models::Itemizable hook — credit memos never auto-apply customer
coupons (a credit is the customer being made whole, not a new sale).
659 660 661 |
# File 'app/models/credit_memo.rb', line 659 def disable_auto_coupon true end |
#do_not_detect_shipping ⇒ true
Models::Itemizable hook — credit memos never re-detect shipping
automatically (the credit is for the original shipping cost, not a
newly-quoted rate).
651 652 653 |
# File 'app/models/credit_memo.rb', line 651 def do_not_detect_shipping true end |
#editing_locked? ⇒ Boolean
311 312 313 |
# File 'app/models/credit_memo.rb', line 311 def editing_locked? credit_order.present? or printed? or partially_offset? or fully_offset? or processing_refund? end |
#file_name ⇒ String
Filename used when attaching the credit-memo PDF to email or storing in S3.
467 468 469 |
# File 'app/models/credit_memo.rb', line 467 def file_name "CreditMemo_#{reference_number}.pdf" end |
#funds_fully_offset? ⇒ Boolean
315 316 317 |
# File 'app/models/credit_memo.rb', line 315 def funds_fully_offset? funds_offset == total end |
#funds_offset ⇒ BigDecimal
Net dollars already offset against this credit memo — sum of applied
ReceiptDetails (credit applied to invoices, write-offs) minus any
outgoing payments (refund checks). Drives the
funds_fully_offset? / funds_partially_offset? state-machine guards.
333 334 335 |
# File 'app/models/credit_memo.rb', line 333 def funds_offset receipt_details.non_voided.sum(:amount) - outgoing_payment_items.applied.sum(:amount) end |
#funds_partially_offset? ⇒ Boolean
319 320 321 |
# File 'app/models/credit_memo.rb', line 319 def funds_partially_offset? !funds_offset.zero? && !(total - funds_offset).zero? end |
#generate_pdf ⇒ Upload
Renders a fresh credit-memo PDF via PdfGenerator,
writes it to temp storage, uploads it to S3 under
credit_memo_pdf and attaches the Upload.
494 495 496 497 498 499 500 501 502 503 504 505 506 |
# File 'app/models/credit_memo.rb', line 494 def generate_pdf store = original_invoice.try(:store) || company.stores.first file_name = "#{reference_number}_credit_memo.pdf" path = Rails.application.config.x.temp_storage_path.join(file_name) pdf = CreditMemo::PdfGenerator.new(self, store: store, show_tax_info: true).generate File.binwrite(path, pdf) upload = Upload.uploadify(path, 'credit_memo_pdf', self, file_name) uploads << upload upload end |
#get_or_regen_pdf(logger = nil) ⇒ Upload
Returns the persisted credit-memo PDF Upload, regenerating it via
#generate_pdf if the upload row is missing or its file is gone
from S3.
477 478 479 480 481 482 483 484 485 486 487 |
# File 'app/models/credit_memo.rb', line 477 def get_or_regen_pdf(logger = nil) logger ||= Rails.logger pdf = uploads.in_category('credit_memo_pdf').first logger.info "Retrieving Credit Memo #{id} pdf, record exists: #{!pdf.nil?}" unless pdf&.file_exists? logger.error ' * Pdf nil or file does not exist, attempting regen' pdf = generate_pdf logger.info "Pdf regenerated with upload id #{pdf.id}" end pdf end |
#invoice ⇒ Invoice
129 |
# File 'app/models/credit_memo.rb', line 129 has_one :invoice, class_name: 'Invoice', foreign_key: :id, primary_key: :original_invoice_id |
#is_build_com? ⇒ Boolean
687 688 689 |
# File 'app/models/credit_memo.rb', line 687 def is_build_com? customer&.is_build_com? end |
#is_edi? ⇒ Boolean
683 684 685 |
# File 'app/models/credit_memo.rb', line 683 def is_edi? invoice&.order&.is_edi_order? end |
#ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
136 |
# File 'app/models/credit_memo.rb', line 136 has_many :ledger_transactions |
#line_discounts ⇒ ActiveRecord::Relation<LineDiscount>
131 |
# File 'app/models/credit_memo.rb', line 131 has_many :line_discounts, through: :line_items |
#line_items ⇒ ActiveRecord::Relation<LineItem>
130 |
# File 'app/models/credit_memo.rb', line 130 has_many :line_items, as: :resource, dependent: :destroy, extend: LineItemExtension, inverse_of: :resource |
#name ⇒ String
Display name used in lists and links.
453 454 455 |
# File 'app/models/credit_memo.rb', line 453 def name "Credit Memo: #{reference_number}" end |
#no_funds_offset? ⇒ Boolean
323 324 325 |
# File 'app/models/credit_memo.rb', line 323 def no_funds_offset? funds_offset == 0 end |
#original_invoice ⇒ Invoice
122 |
# File 'app/models/credit_memo.rb', line 122 belongs_to :original_invoice, class_name: 'Invoice', optional: true |
#original_order ⇒ Order
121 |
# File 'app/models/credit_memo.rb', line 121 belongs_to :original_order, class_name: 'Order', optional: true |
#outgoing_payment_items ⇒ ActiveRecord::Relation<OutgoingPaymentItem>
139 |
# File 'app/models/credit_memo.rb', line 139 has_many :outgoing_payment_items |
#outgoing_payments ⇒ ActiveRecord::Relation<OutgoingPayment>
140 |
# File 'app/models/credit_memo.rb', line 140 has_many :outgoing_payments, through: :outgoing_payment_items |
#payment_status ⇒ Hash
263 264 265 266 267 268 269 270 271 272 273 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 300 301 302 303 304 305 306 307 308 309 |
# File 'app/models/credit_memo.rb', line 263 def payment_status pm = outgoing_payments.applied.first ck = pm.try(:checks).try(:first) rec = receipts.fully_applied.first if printed? { status: 'Credit Memo Prepared and Awaiting Payment', color: 'orange', ref: reference_number } elsif pm if pm.category == 'check' if pm.queued? or pm.generated? { status: 'Check In Print Queue', color: 'orange', ref: reference_number } elsif pm.pending_review? { status: 'Check Pending Approval', color: 'orange', ref: reference_number } elsif pm.printed? or pm.reprinted? if ck mailed_out = 1.working.days.since(ck.created_at) if mailed_out < Time.current { status: "Check Mailed Out #{mailed_out.to_fs(:crm_date_only)}", color: 'green', preview: (ck.uploads.empty? ? nil : ck.uploads.first..encode('png').url), ref: reference_number } else { status: "Check Mailed Out #{mailed_out.to_fs(:crm_date_only)}", color: 'orange', preview: (ck.uploads.empty? ? nil : ck.uploads.first..encode('png').url), ref: reference_number } end else { status: 'Check Printed', color: 'green', ref: reference_number } end end else { status: "Refunded via #{pm.category} #{pm.payment_date.to_fs(:crm_default)}", color: 'green', ref: reference_number } end elsif rec if rec.category == 'Credit Card' { status: "Credit Card (#{rec.reference}) Refunded #{rec.receipt_date.to_fs(:crm_default)}", color: 'green', ref: reference_number } elsif rec.receipt_details.any? { |rd| rd.invoice_id.present? } { status: "Credit Redeemed #{rec.receipt_date.to_fs(:crm_default)}", color: 'green', ref: reference_number } else { status: "Refunded via #{rec.category} #{rec.receipt_date.to_fs(:crm_default)}", color: 'green', ref: reference_number } end elsif processing_refund? { status: 'Processing Refund', color: 'orange', ref: reference_number } elsif partially_offset? { status: 'Partially Refunded', color: 'orange', ref: reference_number } elsif fully_offset? { status: 'Fully Refunded', color: 'orange', ref: reference_number } else { status: state, color: 'red', ref: reference_number } end end |
#payments ⇒ ActiveRecord::Relation<Payment>
135 |
# File 'app/models/credit_memo.rb', line 135 has_many :payments |
#prevent_recalculate_shipping? ⇒ Boolean
373 374 375 |
# File 'app/models/credit_memo.rb', line 373 def prevent_recalculate_shipping? true end |
#process_refund(auth_hash) ⇒ Hash{Symbol => Object}
Refunds the customer through gateway authorisations, splitting the
total across one or more Payment ids per auth_hash. Validates
that no individual amount is negative, that the sum doesn't exceed
the credit-memo balance, and that each amount fits within its
authorisation's refundable amount, then issues gateway refunds.
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 |
# File 'app/models/credit_memo.rb', line 403 def process_refund(auth_hash) # check none are less than $0 if processing_refund? { success: false, error_message: 'A refund is already being processed for this credit memo' } elsif auth_hash.any? { |_auth_id, refund_amount| refund_amount < 0 } { success: false, error_message: 'All refund amounts must be greater than 0 or left blank' } else # sum up the total value of the refund refund_total = BigDecimal('0.00') auth_hash.each { |_auth_id, refund_amount| refund_total += refund_amount } # check refund total is not greater than the credit memo balance if refund_total > -balance { success: false, error_message: 'Total of refund is greater than balance of credit memo' } # check individual refund amounts are available on the selected authorization elsif auth_hash.any? { |auth_id, refund_amount| Payment.find(auth_id).available_to_refund < refund_amount } { success: false, error_message: 'Amount to refund is greater than amount available to refund on one or more payments' } else success = true auth_hash.each do |auth_id, refund_amount| payment = Payment.find(auth_id) res = payment.gateway_class.new(payment).refund(refund_amount, self) success = false unless res.success end if success == false { success: false, error_message: 'One or more refunds could not be processed successfully' } else { success: true, error_message: 'Refund processed successfully' } end end end end |
#receipt_details ⇒ ActiveRecord::Relation<ReceiptDetail>
137 |
# File 'app/models/credit_memo.rb', line 137 has_many :receipt_details, dependent: :nullify, inverse_of: :credit_memo |
#receipts ⇒ ActiveRecord::Relation<Receipt>
138 |
# File 'app/models/credit_memo.rb', line 138 has_many :receipts, through: :receipt_details |
#set_reps ⇒ void
This method returns an undefined value.
Inherits sales-rep attribution from the original invoice when any
rep slot is unset. Fired before save so commission credit follows
the source sale.
668 669 670 671 672 673 674 |
# File 'app/models/credit_memo.rb', line 668 def set_reps return unless (primary_sales_rep_id.nil? or secondary_sales_rep_id.nil? or local_sales_rep_id.nil?) and original_invoice.present? self.primary_sales_rep = original_invoice.primary_sales_rep if primary_sales_rep_id.nil? self.secondary_sales_rep = original_invoice.secondary_sales_rep if secondary_sales_rep_id.nil? self.local_sales_rep = original_invoice.local_sales_rep if local_sales_rep_id.nil? end |
#shipping_address ⇒ Address Also known as: destination_address
127 |
# File 'app/models/credit_memo.rb', line 127 belongs_to :shipping_address, class_name: 'Address', optional: true |
#to_s ⇒ String
Human-readable identifier used in audit logs and error messages.
679 680 681 |
# File 'app/models/credit_memo.rb', line 679 def to_s "Credit Memo # #{reference_number}" end |
#uploads ⇒ ActiveRecord::Relation<Upload>
has_many :shipping_costs, -> { order(:cost) }, :as => :resource, :dependent => :destroy
133 |
# File 'app/models/credit_memo.rb', line 133 has_many :uploads, -> { order(:updated_at).reverse_order }, as: :resource, dependent: :destroy |