[FIX] account: reconcile payments against min date_maturity journal item

1. Create a payment term with:
   - 50% due now
   - balance due in 14 days
2. Validate an invoice with this payment term and partner Agrolait
3. Create a payment from Agrolait with the 50% due
4. Add this outstanding credit to the invoice

It is reconciled with the balance due in 14 days instead of what is
due now. Because of this e.g. the Aged Receivable report will show the
customer being overdue with a payment.

This issue was first fixed in 9.0 at
adad192cc0. It was then reintroduced in
saas-11.4 at d3d2612061. This commit
adds a test to avoid regressions like this.

To fix the issue this sorts journal items by their Due Date if there
is one. Otherwise we use the date from the journal entry as before.

Note that before this commit journal items were not sorted at all by
the two 'sorted' statements. sorted returns a sorted recordset, it
doesn't modify the recordset it's called on.

opw-1906665
This commit is contained in:
Joren Van Onder
2018-11-07 09:25:29 +01:00
committed by De Paoli Quentin
parent dc35de9d30
commit b9e6adcaf7
2 changed files with 62 additions and 2 deletions
+2 -2
View File
@@ -862,8 +862,8 @@ class AccountMoveLine(models.Model):
# Create list of debit and list of credit move ordered by date-currency
debit_moves = self.filtered(lambda r: r.debit != 0 or r.amount_currency > 0)
credit_moves = self.filtered(lambda r: r.credit != 0 or r.amount_currency < 0)
debit_moves.sorted(key=lambda a: (a.date, a.currency_id))
credit_moves.sorted(key=lambda a: (a.date, a.currency_id))
debit_moves = debit_moves.sorted(key=lambda a: (a.date_maturity or a.date, a.currency_id))
credit_moves = credit_moves.sorted(key=lambda a: (a.date_maturity or a.date, a.currency_id))
# Compute on which field reconciliation should be based upon:
field = self[0].account_id.currency_id and 'amount_residual_currency' or 'amount_residual'
#if all lines share the same currency, use amount_residual_currency to avoid currency rounding error
@@ -974,3 +974,63 @@ class TestReconciliation(AccountingTestCase):
# because they owe us still 50 CC.
self.assertEqual(invoice_cust_1.state, 'open',
'Invoice is in status %s' % invoice_cust_1.state)
def test_multiple_term_reconciliation_opw_1906665(self):
'''Test that when registering a payment to an invoice with multiple
payment term lines the reconciliation happens against the line
with the earliest date_maturity
'''
payment_term = self.env['account.payment.term'].create({
'name': 'Pay in 2 installments',
'line_ids': [
# Pay 50% immediately
(0, 0, {
'value': 'percent',
'value_amount': 50,
}),
# Pay the rest after 14 days
(0, 0, {
'value': 'balance',
'days': 14,
})
],
})
# can't use self.create_invoice because it validates and we need to set payment_term_id
invoice = self.account_invoice_model.create({
'partner_id': self.partner_agrolait_id,
'payment_term_id': payment_term.id,
'reference_type': 'none',
'currency_id': self.currency_usd_id,
'name': 'Multiple payment terms',
'account_id': self.account_rcv.id,
'type': 'out_invoice',
'date_invoice': time.strftime('%Y') + '-07-01',
})
self.account_invoice_line_model.create({
'product_id': self.product.id,
'quantity': 1,
'price_unit': 50,
'invoice_id': invoice.id,
'name': self.product.display_name,
'account_id': self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1).id,
})
invoice.action_invoice_open()
payment = self.env['account.payment'].create({
'payment_type': 'inbound',
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
'partner_type': 'customer',
'partner_id': self.partner_agrolait_id,
'amount': 25,
'currency_id': self.currency_usd_id,
'journal_id': self.bank_journal_usd.id,
})
payment.post()
invoice.assign_outstanding_credit(payment.move_line_ids.filtered('credit').id)
receivable_lines = invoice.move_id.line_ids.filtered(lambda line: line.account_id == self.account_rcv).sorted('date_maturity')[0]
self.assertTrue(receivable_lines.matched_credit_ids)