Skip to content

Commit

Permalink
[FIX] point_of_sale: correctly reconcile reversed payments
Browse files Browse the repository at this point in the history
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

X-original-commit: 31aff38
  • Loading branch information
robinengels committed Apr 22, 2024
1 parent fbcfa5b commit 00fdfae
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 112 deletions.
18 changes: 11 additions & 7 deletions addons/point_of_sale/models/pos_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -848,11 +848,15 @@ def _create_misc_reversal_move(self, payment_moves):
})
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:
Expand Down Expand Up @@ -882,7 +886,7 @@ def _generate_pos_order_invoice(self):
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):
Expand Down Expand Up @@ -913,9 +917,9 @@ def _generate_pos_order_invoice(self):
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:
Expand Down
12 changes: 10 additions & 2 deletions addons/point_of_sale/models/pos_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _export_for_ui(self, payment):
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
Expand All @@ -87,9 +87,17 @@ def _create_payment_moves(self):
'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()
Expand Down
106 changes: 3 additions & 103 deletions addons/point_of_sale/tests/test_point_of_sale_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1473,9 +1473,9 @@ def test_sale_order_postponed_invoicing(self):

# 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)
Expand Down Expand Up @@ -1787,106 +1787,6 @@ def test_order_refund_with_invoice(self):
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',
Expand Down

0 comments on commit 00fdfae

Please sign in to comment.