Module: ReceiptSpreadsheetIo

Extended by:
ActiveSupport::Concern
Included in:
Receipt
Defined in:
app/models/concerns/receipt_spreadsheet_io.rb

Overview

Bulk receipt creation and editing from uploaded XLSX spreadsheets:
row parsing (via Roo), pre-save validation reports, and the create/
edit importers driven by the receipts importer UI and the
CreateReceiptsWorker / EditReceiptsWorker background jobs.

See Also:

Class Method Summary collapse

Class Method Details

.create_receipts_from_xlsx(xlsx_file) {|1, total_steps, 'Reading Excel file'| ... } ⇒ Object

Yields:

  • (1, total_steps, 'Reading Excel file')


141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'app/models/concerns/receipt_spreadsheet_io.rb', line 141

def create_receipts_from_xlsx(xlsx_file)
  report = { errors: [], successes: [], new_receipt_ids: [] }
  new_receipt = nil
  new_receipts = []
  total_steps = 5

  require 'roo'
  yield(1, total_steps, 'Reading Excel file') if block_given?
  sleep 2
  receipts_xlsx = Roo::Spreadsheet.open(xlsx_file.to_file.to_s)

  yield(2, total_steps, 'Building receipts') if block_given?
  sleep 4
  yield(3, total_steps, 'Adding receipt details') if block_given?
  sleep 4
  receipts_xlsx.parse(headers: true, clean: true).each_with_index do |row, index|
    next unless index >= 1

    if row.map { |_k, v| v }.uniq.compact.present?
      # get the components of receipt
      if row['company_id'].present?
        company = Company.find(row['company_id'])
         = company. || LedgerAccount.find_by(number: row['bank_account'].to_i).ledger_company_accounts.first.
        customer = Party.find_by(id: row['payor_id'].to_i)
        receipt_date = row['receipt_date']
        new_receipt = Receipt.new(company: company, bank_account: , customer: customer, category: row['receipt_category'], reference: row['reference'],
                                  currency: row['currency'], amount: row['receipt_amount'], gl_date: row['gl_date'], receipt_date: receipt_date,
                                  card_type: row['card_type'], auth_code: row['authorization_code'])
      end

      # Add all the necesary receipt details to link to its receipt
      if new_receipt.present? && row['receipt_detail_category'].present?
        case row['receipt_detail_category']
        when 'Invoice'
          invoice = Invoice.find_by(reference_number: row['invoice_credit_memo_number'])
          write_off = row['write_off_amount'].present? ? row['write_off_amount'].to_f : 0
          new_receipt.receipt_details << ReceiptDetail.new(category: row['receipt_detail_category'], invoice: invoice, gl_date: receipt_date, amount: row['receipt_detail_amount'].to_f,
                                                           write_off: write_off, write_off_code: row['write_off_code'], remark: row['receipt_detail_remark'])
        when 'Credit Memo'
          credit_memo = CreditMemo.find_by(reference_number: row['invoice_credit_memo_number'])
          new_receipt.receipt_details << ReceiptDetail.new(category: row['receipt_detail_category'], credit_memo: credit_memo, gl_date: receipt_date,
                                                           amount: row['receipt_detail_amount'], remark: row['receipt_detail_remark'])
        when 'GL Account'
          new_receipt.receipt_details << ReceiptDetail.new(category: row['receipt_detail_category'], amount: row['receipt_detail_amount'],
                                                           ledger_company_account: LedgerCompanyAccount.joins(:company, :ledger_detail_account).where(companies: { number: row['gl_company'] }, ledger_accounts: { number: row['gl_account'] }).first,
                                                           business_unit: BusinessUnit.where(number: row['business_unit']).first,
                                                           ledger_detail_project: LedgerDetailProject.where(project_number: row['project']).first, remark: row['receipt_detail_remark'])
        end
      end

    end

    if new_receipt.present? && (row.map { |_k, v| v }.uniq.compact.empty? || receipts_xlsx.last_row == (index + 1))
      new_receipts << new_receipt unless new_receipt.nil?
      new_receipt = nil
    end
  end

  yield(4, total_steps, 'Recording receipts in the database') if block_given?
  begin
    Receipt.transaction do
      new_receipts.each do |new_receipt|
        new_receipt.save!
        report[:new_receipt_ids] << new_receipt.id
        msg = "#{Time.current}: Created new receipt id: #{new_receipt.id}"
        logger.info msg
        report[:successes] << msg
      end
      msg = 'Receipts have been created successfully.'
      report[:successes] << msg
    end
  rescue StandardError => e
    msg = "Unable to create receipt, message : #{e}."
    logger.error msg
    report[:errors] << msg
  end
  yield(5, total_steps, 'Finishing...') if block_given?
  sleep 3
  report
end

.edit_receipts_from_xlsx(xlsx_file) {|1, total_steps, 'Reading Excel file'| ... } ⇒ Object

Yields:

  • (1, total_steps, 'Reading Excel file')


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
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'app/models/concerns/receipt_spreadsheet_io.rb', line 322

def edit_receipts_from_xlsx(xlsx_file)
  report = { errors: [], successes: [], edited_receipt_ids: [] }
  receipt = nil
  receipts = []
  total_steps = 4

  require 'roo'
  yield(1, total_steps, 'Reading Excel file') if block_given?
  sleep 2
  receipts_xlsx = Roo::Spreadsheet.open(xlsx_file.to_file.to_s)

  yield(2, total_steps, 'Adding receipt details') if block_given?
  sleep 4
  receipts_xlsx.parse(headers: true, clean: true).each_with_index do |row, index|
    next unless index >= 1

    if row.map { |_k, v| v }.uniq.compact.present?
      # update data of existing receipt
      if row['receipt_number'].present?
        receipt = Receipt.find_by(id: row['receipt_number'])
        next if receipt.nil?

        remark = row['receipt_remark'] || receipt.remark
        receipt.remark = remark
        receipt.new_gl_date = row['new_gl_date']
      end

      # add new receipt details to existing receipt
      if receipt.present? && row['receipt_detail_category'].present?
        if row['receipt_detail_category'] == 'Invoice' && row['invoice_credit_memo_number'].present?
          invoice = Invoice.find_by(reference_number: row['invoice_credit_memo_number'])
          write_off = row['write_off_amount'].present? ? row['write_off_amount'].to_f : 0
          receipt.receipt_details << ReceiptDetail.new(category: row['receipt_detail_category'], invoice: invoice, gl_date: receipt.new_gl_date, amount: row['receipt_detail_amount'].to_f,
                                                       write_off: write_off, write_off_code: row['write_off_code'], remark: row['receipt_detail_remark'])
        elsif row['receipt_detail_category'] == 'Credit Memo' && row['invoice_credit_memo_number'].present?
          credit_memo = CreditMemo.find_by(reference_number: row['invoice_credit_memo_number'])
          receipt.receipt_details << ReceiptDetail.new(category: row['receipt_detail_category'], credit_memo: credit_memo, gl_date: receipt.new_gl_date,
                                                       amount: row['receipt_detail_amount'], remark: row['receipt_detail_remark'])
        elsif row['receipt_detail_category'] == 'GL Account'
          receipt.receipt_details << ReceiptDetail.new(category: row['receipt_detail_category'], amount: row['receipt_detail_amount'],
                                                       ledger_company_account: LedgerCompanyAccount.joins(:company, :ledger_detail_account).where(companies: { number: row['gl_company'] }, ledger_accounts: { number: row['gl_account'] }).first,
                                                       business_unit: BusinessUnit.where(number: row['business_unit']).first,
                                                       ledger_detail_project: LedgerDetailProject.where(project_number: row['project']).first, remark: row['receipt_detail_remark'])
        end
      end
    end

    if receipt.present? && (row.map { |_k, v| v }.uniq.compact.empty? || receipts_xlsx.last_row == (index + 1))
      receipts << receipt unless receipt.nil?
      receipt = nil
    end
  end

  yield(3, total_steps, 'Updating receipts in the database') if block_given?
  begin
    Receipt.transaction do
      receipts.each do |receipt|
        receipt.save!
        report[:edited_receipt_ids] << receipt.id
        msg = "#{Time.current}: Updated receipt id: #{receipt.id}"
        logger.info msg
        report[:successes] << msg
      end
      # redirect_to edited_receipts_path(edited_receipt_ids: receipt_ids)
    end
  rescue StandardError => e
    msg = "Unable to edit receipt, message : #{e}."
    logger.error msg
    report[:errors] << msg
  end
  yield(4, total_steps, 'Finishing...') if block_given?
  sleep 3
  report
end

.validate_data_create_receipts_from_xlsx(xlsx_file) ⇒ Array<String>

Pre-flight a "create receipts" XLSX upload: parse each row, resolve
company/bank account/payor/invoice/credit-memo/GL-account, and
collect a list of human-readable error strings without persisting
anything. Used by the importer UI to render a validation report.

Parameters:

Returns:

  • (Array<String>)

    HTML-formatted error messages (empty if valid)



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'app/models/concerns/receipt_spreadsheet_io.rb', line 20

def validate_data_create_receipts_from_xlsx(xlsx_file)
  errors = []
  new_receipt = nil
  require 'roo'
  receipts_xlsx = Roo::Spreadsheet.open(xlsx_file.to_file.to_s)
  receipt_ref = 0
  receipts_xlsx.parse(headers: true, clean: true).each_with_index do |row, index|
    next unless index >= 1

    if row.map { |_k, v| v }.uniq.compact.present?
      # get the data to validate the components of receipt
      if row['company_id'].present?
        receipt_ref = (index + 1)
        company = Company.find_by(id: row['company_id'])
        if company.nil?
          errors << "-<b> Receipt located on row #{receipt_ref}:</b> Company with ID #{row['company_id']} not found. <br>"
          new_receipt = nil # Clear stale receipt to prevent detail rows from using wrong receipt
          next
        end
         = nil
        if row['bank_account'].nil?
          errors << "-<b> Receipt located on row #{receipt_ref}:</b> Column bank_account can't be blank. <br>"
        else
           = company.
          if .nil?
             = LedgerAccount.find_by(number: row['bank_account'].to_i)
            if .nil? || .ledger_company_accounts.first.nil?
              errors << "-<b> Receipt located on row #{receipt_ref}:</b> Bank account #{row['bank_account']} not found. <br>"
            else
               = .ledger_company_accounts.first.
            end
          end
        end
        customer = nil
        if row['payor_type'].nil? || row['payor_id'].nil?
          errors << "-<b> Receipt located on row #{receipt_ref}:</b> Columns payor_type and payor_id can't be blank. <br>"
        else
          customer = Party.find_by(id: row['payor_id'].to_i)
          errors << "-<b> Receipt located on row #{receipt_ref}:</b> Customer with ID #{row['payor_id']} not found. <br>" if customer.nil?
        end
        if row['receipt_category'].nil? || row['reference'].nil? || row['currency'].nil? || row['receipt_amount'].nil? || row['receipt_date'].nil? || row['gl_date'].nil?
          errors << "-<b> Receipt located on row #{receipt_ref} (#{row['payor_type']}: #{row['payor_id']}):</b> Columns receipt_category, reference, currency, receipt_amount, receipt_date and gl_date can't be blank. <br>"
        end

        new_receipt = Receipt.new(company: company, bank_account: , customer: customer, category: row['receipt_category'], reference: row['reference'],
                                  currency: row['currency'], amount: row['receipt_amount'], gl_date: row['gl_date'], receipt_date: row['receipt_date'],
                                  card_type: row['card_type'], auth_code: row['authorization_code'])
      end

      # validate all the necesary receipt details to link to its receipt
      if new_receipt.present? && row['receipt_detail_category'].present?
        receipt_ref = (index + 1)
        valid_categories = ['Invoice', 'Credit Memo', 'GL Account']
        errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> Invalid category '#{row['receipt_detail_category']}'. Must be one of: #{valid_categories.join(', ')}. <br>" unless valid_categories.include?(row['receipt_detail_category'])
        if ['Invoice', 'Credit Memo'].include?(row['receipt_detail_category']) && row['invoice_credit_memo_number'].nil?
          errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> Column invoice_credit_memo_number can't be blank when an invoice or credit is present. <br>"
        end
        if row['receipt_detail_category'].present? && row['receipt_detail_amount'].nil?
          errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Column amount can't be blank when receipt_detail_category is present. <br>"
        end
        if row['write_off_amount'].present? && (row['write_off_amount'].to_f != 0) && row['write_off_code'].nil?
          errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Column write_off_code can't be blank when write_off_amount is present. <br>"
        end
        if row['receipt_detail_category'] == 'GL Account' && (row['gl_company'].nil? || row['gl_account'].nil? || row['business_unit'].nil? || row['receipt_detail_remark'].nil?)
          errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> Columns gl_company, gl_account, business_unit and receipt_detail_remark can't be blank when an G/L is present. <br>"
        elsif row['receipt_detail_category'] == 'GL Account'
           = LedgerCompanyAccount.joins(:company, :ledger_detail_account).where(companies: { number: row['gl_company'] }, ledger_accounts: { number: row['gl_account'] }).first
          if .nil?
            errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> No GL account found for company #{row['gl_company']} and account #{row['gl_account']}. Please verify these values exist. <br>"
          elsif new_receipt.company.present? && new_receipt.company != .company
            errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['gl_company']}.#{row['gl_account']}):</b> Receipt and GL account must belong to the same company. <br>"
          end
          business_unit = BusinessUnit.find_by(number: row['business_unit'])
          errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> Business unit #{row['business_unit']} not found. <br>" if business_unit.nil?
        end

        if row['receipt_detail_category'] == 'Invoice' && row['invoice_credit_memo_number'].present?
          invoice = Invoice.find_by(reference_number: row['invoice_credit_memo_number'])
          if invoice.nil?
            errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Invoice not found, or reference number is not an invoice. <br>"
          else
            if new_receipt.company.present? && new_receipt.company != invoice.company
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Receipt and invoice must belong to the same company. <br>"
            end
            if new_receipt.receipt_date.present? && invoice.gl_date.present? && invoice.gl_date > new_receipt.receipt_date
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Invoice date can't be greater than receipt date. <br>"
            end
            errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Invoice must be under an approved status to apply the receipt. <br>" if invoice.draft?
            if new_receipt.customer.is_a?(Customer) && !new_receipt.customer.can_apply_document_customer?(invoice.customer_id, invoice.billing_customer_id)
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Invoice customer is not the receipt payor or in its parent/child account family. <br>"
            end
          end

        elsif row['receipt_detail_category'] == 'Credit Memo' && row['invoice_credit_memo_number'].present?
          credit_memo = CreditMemo.find_by(reference_number: row['invoice_credit_memo_number'])
          if credit_memo.nil?
            errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Credit memo not found, or reference number is not a credit memo. <br>"
          else
            if new_receipt.company.present? && new_receipt.company != credit_memo.company
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Receipt and credit memo must belong to the same company. <br>"
            end
            if new_receipt.receipt_date.present? && credit_memo.gl_date.present? && credit_memo.gl_date > new_receipt.receipt_date
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Credit memo date can't be greater than receipt date. <br>"
            end
            if credit_memo.requested? || credit_memo.approved? || credit_memo.fully_offset?
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Credit memo must be under printed, processing refund or partially offset state to apply the receipt. <br>"
            end
            if new_receipt.customer.is_a?(Customer) && !new_receipt.customer.can_apply_document_customer?(credit_memo.customer_id, credit_memo.billing_customer_id)
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Credit memo customer is not the receipt payor or in its parent/child account family. <br>"
            end
          end
        end
      end

    end

    new_receipt = nil if errors.empty? && new_receipt.present? && (row.map { |_k, v| v }.uniq.compact.empty? || receipts_xlsx.last_row == (index + 1))
  end
  errors
end

.validate_data_edit_receipts_from_xlsx(xlsx_file) ⇒ Array<String>

Pre-flight an "edit receipts" XLSX upload: each row references an
existing receipt by id and adds new ReceiptDetails under
new_gl_date. Returns a list of HTML error strings; nothing is
persisted.

Parameters:

Returns:

  • (Array<String>)


229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'app/models/concerns/receipt_spreadsheet_io.rb', line 229

def validate_data_edit_receipts_from_xlsx(xlsx_file)
  errors = []
  receipt = nil
  require 'roo'
  receipts_xlsx = Roo::Spreadsheet.open(xlsx_file.to_file.to_s)
  receipt_ref = 0
  receipts_xlsx.parse(headers: true, clean: true).each_with_index do |row, index|
    next unless index >= 1

    if row.map { |_k, v| v }.uniq.compact.present?
      # update data of existing receipt
      if row['receipt_number'].present?
        receipt_ref = (index + 1)
        receipt = Receipt.find_by(id: row['receipt_number'])
        if receipt.present?
          errors << "-<b> Receipt located on row #{receipt_ref} (#{row['receipt_number']}):</b> Column new_gl_date can't be blank. <br>" if row['new_gl_date'].nil?
          remark = row['receipt_remark'] || receipt.remark
          receipt.remark = remark
          receipt.new_gl_date = row['new_gl_date']
        else
          errors << "-<b> Receipt located on row #{receipt_ref} (#{row['receipt_number']}):</b> Receipt not found. <br>"
          receipt = nil # Clear stale receipt to prevent detail rows from using wrong receipt
        end
      end

      # add new receipt details to existing receipt
      if receipt.present? && row['receipt_detail_category'].present?
        receipt_ref = (index + 1)
        valid_categories = ['Invoice', 'Credit Memo', 'GL Account']
        errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> Invalid category '#{row['receipt_detail_category']}'. Must be one of: #{valid_categories.join(', ')}. <br>" unless valid_categories.include?(row['receipt_detail_category'])
        if ['Invoice', 'Credit Memo'].include?(row['receipt_detail_category']) && row['invoice_credit_memo_number'].nil?
          errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> Column invoice_credit_memo_number can't be blank when an invoice or credit is present. <br>"
        end
        if row['receipt_detail_category'].present? && row['receipt_detail_amount'].nil?
          errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Column amount can't be blank when receipt_detail_category is present. <br>"
        end
        if row['write_off_amount'].present? && (row['write_off_amount'].to_f != 0) && row['write_off_code'].nil?
          errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Column write_off_code can't be blank when write_off_amount is present. <br>"
        end
        if row['receipt_detail_category'] == 'GL Account' && (row['gl_company'].nil? || row['gl_account'].nil? || row['business_unit'].nil? || row['receipt_detail_remark'].nil?)
          errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> Columns gl_company, gl_account, business_unit and receipt_detail_remark can't be blank when an G/L is present. <br>"
        elsif row['receipt_detail_category'] == 'GL Account'
           = LedgerCompanyAccount.joins(:company, :ledger_detail_account).where(companies: { number: row['gl_company'] }, ledger_accounts: { number: row['gl_account'] }).first
          if .nil?
            errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> No GL account found for company #{row['gl_company']} and account #{row['gl_account']}. Please verify these values exist. <br>"
          elsif receipt.company.present? && receipt.company != .company
            errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['gl_company']}.#{row['gl_account']}):</b> Receipt and GL account must belong to the same company. <br>"
          end
          business_unit = BusinessUnit.find_by(number: row['business_unit'])
          errors << "-<b> Receipt detail located on row #{receipt_ref}:</b> Business unit #{row['business_unit']} not found. <br>" if business_unit.nil?
        end

        if row['receipt_detail_category'] == 'Invoice' && row['invoice_credit_memo_number'].present?
          invoice = Invoice.find_by(reference_number: row['invoice_credit_memo_number'])
          if invoice.nil?
            errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Invoice not found, or reference number is not an invoice. <br>"
          else
            errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Receipt and invoice must belong to the same company. <br>" if receipt.company.present? && receipt.company != invoice.company
            if receipt.new_gl_date.present? && invoice.gl_date.present? && invoice.gl_date > receipt.new_gl_date
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Invoice date can't be greater than receipt date. <br>"
            end
            errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Invoice must be under an approved status to apply the receipt. <br>" if invoice.draft?
            if receipt.customer.is_a?(Customer) && !receipt.customer.can_apply_document_customer?(invoice.customer_id, invoice.billing_customer_id)
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Invoice customer is not the receipt payor or in its parent/child account family. <br>"
            end
          end
        elsif row['receipt_detail_category'] == 'Credit Memo' && row['invoice_credit_memo_number'].present?
          credit_memo = CreditMemo.find_by(reference_number: row['invoice_credit_memo_number'])
          if credit_memo.nil?
            errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Credit memo not found, or reference number is not a credit memo. <br>"
          else
            if receipt.company.present? && receipt.company != credit_memo.company
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Receipt and credit memo must belong to the same company. <br>"
            end
            if receipt.new_gl_date.present? && credit_memo.gl_date.present? && credit_memo.gl_date > receipt.new_gl_date
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Credit memo date can't be greater than receipt date. <br>"
            end
            if credit_memo.requested? || credit_memo.approved? || credit_memo.fully_offset?
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Credit memo must be under printed, processing refund or partially offset state to apply the receipt. <br>"
            end
            if receipt.customer.is_a?(Customer) && !receipt.customer.can_apply_document_customer?(credit_memo.customer_id, credit_memo.billing_customer_id)
              errors << "-<b> Receipt detail located on row #{receipt_ref} (#{row['invoice_credit_memo_number']}):</b> Credit memo customer is not the receipt payor or in its parent/child account family. <br>"
            end
          end
        end
      end
    end

    receipt = nil if errors.empty? && receipt.present? && (row.map { |_k, v| v }.uniq.compact.empty? || receipts_xlsx.last_row == (index + 1))
  end
  errors
end