[FIX] point_of_sale: correctly reconcile reversed payments
Current behavior: When an order is invoided after the session has been closed, a reversed payment is created. This payment is not reconciled correctly with the invoice. This is creating an aged receivable for the partner. Steps to reproduce: - Change the bank payment method to "Identify customer" - Create an order in the PoS and pay with bank and specify a partner - Close the session - Open the session again, and create an invoice for the order - Go to the accounting module and look for the aged receivable report you should see some entries under the partner you selected. - You can also go to the partner form and see that he has some due invoices. opw-3678298 correct partner closes odoo/odoo#162653 X-original-commit: 31aff389c4c9d01be2f960a0ae134ff658e802c9 Related: odoo/enterprise#61158 Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com> Signed-off-by: Robin Engels (roen) <roen@odoo.com>
This commit is contained in:
@@ -848,11 +848,15 @@ class PosOrder(models.Model):
|
||||
})
|
||||
reversal_entry.action_post()
|
||||
|
||||
# Reconcile the new receivable line with the lines from the payment move.
|
||||
pos_account_receivable = self.company_id.account_default_pos_receivable_account_id
|
||||
reversal_entry_receivable = reversal_entry.line_ids.filtered(lambda l: l.account_id == pos_account_receivable)
|
||||
payment_receivable = payment_moves.line_ids.filtered(lambda l: l.account_id == pos_account_receivable)
|
||||
(reversal_entry_receivable | payment_receivable).reconcile()
|
||||
account_receivable = self.payment_ids.payment_method_id.receivable_account_id
|
||||
reversal_entry_receivable = reversal_entry.line_ids.filtered(lambda l: l.account_id in (pos_account_receivable + account_receivable))
|
||||
payment_receivable = payment_moves.line_ids.filtered(lambda l: l.account_id in (pos_account_receivable + account_receivable))
|
||||
lines_to_reconcile = defaultdict(lambda: self.env['account.move.line'])
|
||||
for line in (reversal_entry_receivable | payment_receivable):
|
||||
lines_to_reconcile[line.account_id] |= line
|
||||
for line in lines_to_reconcile.values():
|
||||
line.filtered(lambda l: not l.reconciled).reconcile()
|
||||
|
||||
def action_pos_order_invoice(self):
|
||||
if len(self.company_id) > 1:
|
||||
@@ -882,7 +886,7 @@ class PosOrder(models.Model):
|
||||
new_move.sudo().with_company(order.company_id).with_context(skip_invoice_sync=True)._post()
|
||||
|
||||
moves += new_move
|
||||
payment_moves = order._apply_invoice_payments()
|
||||
payment_moves = order._apply_invoice_payments(order.session_id.state == 'closed')
|
||||
|
||||
# Send and Print
|
||||
if self.env.context.get('generate_pdf', True):
|
||||
@@ -913,9 +917,9 @@ class PosOrder(models.Model):
|
||||
def action_pos_order_cancel(self):
|
||||
return self.write({'state': 'cancel'})
|
||||
|
||||
def _apply_invoice_payments(self):
|
||||
def _apply_invoice_payments(self, is_reverse=False):
|
||||
receivable_account = self.env["res.partner"]._find_accounting_partner(self.partner_id).with_company(self.company_id).property_account_receivable_id
|
||||
payment_moves = self.payment_ids.sudo().with_company(self.company_id)._create_payment_moves()
|
||||
payment_moves = self.payment_ids.sudo().with_company(self.company_id)._create_payment_moves(is_reverse)
|
||||
if receivable_account.reconcile:
|
||||
invoice_receivables = self.account_move.line_ids.filtered(lambda line: line.account_id == receivable_account and not line.reconciled)
|
||||
if invoice_receivables:
|
||||
|
||||
@@ -63,7 +63,7 @@ class PosPayment(models.Model):
|
||||
def export_for_ui(self):
|
||||
return self.mapped(self._export_for_ui) if self else []
|
||||
|
||||
def _create_payment_moves(self):
|
||||
def _create_payment_moves(self, is_reverse=False):
|
||||
result = self.env['account.move']
|
||||
for payment in self:
|
||||
order = payment.pos_order_id
|
||||
@@ -87,9 +87,17 @@ class PosPayment(models.Model):
|
||||
'partner_id': accounting_partner.id,
|
||||
'move_id': payment_move.id,
|
||||
}, amounts['amount'], amounts['amount_converted'])
|
||||
is_split_transaction = payment.payment_method_id.split_transactions
|
||||
if is_split_transaction and is_reverse:
|
||||
reversed_move_receivable_account_id = accounting_partner.with_company(order.company_id).property_account_receivable_id.id
|
||||
elif is_reverse:
|
||||
reversed_move_receivable_account_id = payment.payment_method_id.receivable_account_id.id or self.company_id.account_default_pos_receivable_account_id.id
|
||||
else:
|
||||
reversed_move_receivable_account_id = self.company_id.account_default_pos_receivable_account_id.id
|
||||
debit_line_vals = pos_session._debit_amounts({
|
||||
'account_id': pos_session.company_id.account_default_pos_receivable_account_id.id,
|
||||
'account_id': reversed_move_receivable_account_id,
|
||||
'move_id': payment_move.id,
|
||||
'partner_id': accounting_partner.id if is_split_transaction and is_reverse else False,
|
||||
}, amounts['amount'], amounts['amount_converted'])
|
||||
self.env['account.move.line'].create([credit_line_vals, debit_line_vals])
|
||||
payment_move._post()
|
||||
|
||||
@@ -1473,9 +1473,9 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
|
||||
# Check 2: Reconciliation
|
||||
# The invoice receivable should be reconciled with the payment receivable of the same account.
|
||||
invoice_receivable_line = invoice.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'])
|
||||
payment_receivable_line = payment.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'])
|
||||
self.assertEqual(invoice_receivable_line.matching_number, payment_receivable_line.matching_number)
|
||||
invoice_receivable_line = invoice.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'] and line.reconciled)
|
||||
payment_receivable_line = payment.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'] and line.reconciled and line.matching_number == invoice_receivable_line.matching_number)
|
||||
self.assertTrue(payment_receivable_line)
|
||||
# The payment receivable (POS) is reconciled with the closing entry receivable (POS)
|
||||
payment_receivable_pos_line = payment.line_ids.filtered(lambda line: line.account_id == self.company_data['company'].account_default_pos_receivable_account_id)
|
||||
misc_receivable_pos_line = misc_reversal_entry.line_ids.filtered(lambda line: line.account_id == self.company_data['company'].account_default_pos_receivable_account_id)
|
||||
@@ -1787,106 +1787,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
self.assertEqual(credit_notes.ref, "Reversal of: "+invoices.name)
|
||||
self.assertEqual(credit_notes.reversed_entry_id.id, invoices.id)
|
||||
|
||||
def test_invoicing_after_closing_session(self):
|
||||
""" Test that an invoice can be created after the session is closed """
|
||||
#create customer account payment method
|
||||
self.customer_account_payment_method = self.env['pos.payment.method'].create({
|
||||
'name': 'Customer Account',
|
||||
'split_transactions': True,
|
||||
})
|
||||
|
||||
self.product1 = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'type': 'product',
|
||||
'categ_id': self.env.ref('product.product_category_all').id,
|
||||
})
|
||||
self.partner1.write({'parent_id': self.env['res.partner'].create({'name': 'Parent'}).id})
|
||||
|
||||
#add customer account payment method to pos config
|
||||
self.pos_config.write({
|
||||
'payment_method_ids': [(4, self.customer_account_payment_method.id, 0)],
|
||||
})
|
||||
# change the currency of PoS config
|
||||
(self.currency_data['currency'].rate_ids | self.company.currency_id.rate_ids).unlink()
|
||||
self.env['res.currency.rate'].create({
|
||||
'rate': 0.5,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'name': datetime.today().date(),
|
||||
})
|
||||
self.pos_config.journal_id.write({
|
||||
'currency_id': self.currency_data['currency'].id
|
||||
})
|
||||
other_pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'Public Pricelist Other',
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
})
|
||||
self.pos_config.write({
|
||||
'pricelist_id': other_pricelist.id,
|
||||
'available_pricelist_ids': [(6, 0, other_pricelist.ids)],
|
||||
})
|
||||
self.pos_config.open_ui()
|
||||
current_session = self.pos_config.current_session_id
|
||||
|
||||
# create pos order
|
||||
order = self.PosOrder.create({
|
||||
'company_id': self.env.company.id,
|
||||
'session_id': current_session.id,
|
||||
'partner_id': self.partner1.id,
|
||||
'lines': [(0, 0, {
|
||||
'name': "OL/0001",
|
||||
'product_id': self.product1.id,
|
||||
'price_unit': 6,
|
||||
'discount': 0,
|
||||
'qty': 1,
|
||||
'tax_ids': [[6, False, []]],
|
||||
'price_subtotal': 6,
|
||||
'price_subtotal_incl': 6,
|
||||
})],
|
||||
'pricelist_id': self.pos_config.pricelist_id.id,
|
||||
'amount_paid': 6.0,
|
||||
'amount_total': 6.0,
|
||||
'amount_tax': 0.0,
|
||||
'amount_return': 0.0,
|
||||
'to_invoice': False,
|
||||
'last_order_preparation_change': '{}'
|
||||
})
|
||||
|
||||
#pay for the order with customer account
|
||||
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
||||
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
||||
'amount': 2.0,
|
||||
'payment_method_id': self.cash_payment_method.id
|
||||
})
|
||||
order_payment.with_context(**payment_context).check()
|
||||
|
||||
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
||||
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
||||
'amount': 4.0,
|
||||
'payment_method_id': self.customer_account_payment_method.id
|
||||
})
|
||||
order_payment.with_context(**payment_context).check()
|
||||
|
||||
# close session
|
||||
current_session.action_pos_session_closing_control()
|
||||
|
||||
# create invoice
|
||||
order.action_pos_order_invoice()
|
||||
#get journal entry that does the reverse payment, it the ref must contains Reversal
|
||||
reverse_payment = self.env['account.move'].search([('ref', 'ilike', "Reversal")])
|
||||
original_payment = self.env['account.move'].search([('ref', '=', current_session.display_name)])
|
||||
original_customer_payment_entry = original_payment.line_ids.filtered(lambda l: l.account_id.account_type == 'asset_receivable')
|
||||
reverser_customer_payment_entry = reverse_payment.line_ids.filtered(lambda l: l.account_id.account_type == 'asset_receivable')
|
||||
|
||||
#check that both use the same account
|
||||
self.assertEqual(len(reverser_customer_payment_entry), 2)
|
||||
self.assertTrue(order.account_move.line_ids.partner_id == self.partner1.commercial_partner_id)
|
||||
self.assertEqual(reverser_customer_payment_entry[0].balance, -4.0)
|
||||
self.assertEqual(reverser_customer_payment_entry[1].balance, -8.0)
|
||||
self.assertEqual(reverser_customer_payment_entry[0].amount_currency, -2.0)
|
||||
self.assertEqual(reverser_customer_payment_entry[1].amount_currency, -4.0)
|
||||
self.assertEqual(original_customer_payment_entry.account_id.id, reverser_customer_payment_entry.account_id.id)
|
||||
self.assertEqual(reverser_customer_payment_entry.partner_id, original_customer_payment_entry.partner_id)
|
||||
|
||||
def test_order_total_subtotal_account_line_values(self):
|
||||
self.tax1 = self.env['account.tax'].create({
|
||||
'name': 'Tax 1',
|
||||
|
||||
Reference in New Issue
Block a user