Files
enterprise-addons/pos_settle_due/tests/test_total_due.py
T

404 lines
19 KiB
Python

from datetime import datetime
import odoo
from odoo.fields import Command
from odoo.addons.point_of_sale.tests.common import CommonPosTest
from odoo.tests import Form
@odoo.tests.tagged('post_install', '-at_install')
class TestPointOfSaleFlow(CommonPosTest):
def test_invoicing_after_closing_session(self):
self.partner_moda.parent_id = self.partner_adgu
self.pos_config_eur.payment_method_ids = [(4, self.credit_payment_method.id)]
order, _ = self.create_backend_pos_order({
'order_data': {
'partner_id': self.partner_moda.id,
},
'line_data': [
{'product_id': self.ten_dollars_no_tax.product_variant_id.id}
],
'payment_data': [
{'payment_method_id': self.bank_payment_method.id, 'amount': 5},
{'payment_method_id': self.credit_payment_method.id, 'amount': 5},
],
'pos_config': self.pos_config_eur
})
current_session = self.pos_config_eur.current_session_id
current_session.action_pos_session_closing_control()
accounting_partner = self.env['res.partner']._find_accounting_partner(self.partner_moda)
accounting_partner._invalidate_cache()
self.assertEqual(accounting_partner.total_due, 5.0)
order.action_pos_order_invoice()
self.assertEqual(accounting_partner.total_due, 5.0)
# 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.assertEqual(len(original_customer_payment_entry), 2)
self.assertTrue(order.account_move.line_ids.partner_id == self.partner_moda.commercial_partner_id)
self.assertEqual(reverser_customer_payment_entry[0].balance, -5.0)
self.assertEqual(reverser_customer_payment_entry[1].balance, -5.0)
self.assertEqual(reverser_customer_payment_entry[0].amount_currency, -5.0)
self.assertEqual(reverser_customer_payment_entry[1].amount_currency, -5.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_get_total_due_in_pos_currency(self):
""" get_total_due must return an amount expressed in the PoS currency.
The accounting due (res.partner.total_due) is in company currency and
must be converted, while the pay later payments of the still open
sessions are already in the PoS currency and must not be converted.
"""
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',
'is_storable': True,
'categ_id': self.env.ref('product.product_category_goods').id,
})
self.pos_config_usd.write({
'payment_method_ids': [(4, self.customer_account_payment_method.id, 0)],
})
# PoS runs in another currency than the company one: 1 company = 0.5 other
self.other_currency = self.setup_other_currency("EUR", rounding=0.01, rates=[(datetime.today().date(), 0.5)])
self.pos_config_usd.journal_id.write({
'currency_id': self.other_currency.id
})
other_pricelist = self.env['product.pricelist'].create({
'name': 'Public Pricelist Other',
'currency_id': self.other_currency.id,
})
self.pos_config_usd.write({
'pricelist_id': other_pricelist.id,
'available_pricelist_ids': [(6, 0, other_pricelist.ids)],
})
self.pos_config_usd.open_ui()
current_session = self.pos_config_usd.current_session_id
order = self.env['pos.order'].create({
'company_id': self.env.company.id,
'session_id': current_session.id,
'partner_id': self.partner_moda.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product1.id,
'price_unit': 100,
'discount': 0,
'qty': 1,
'tax_ids': [[6, False, []]],
'price_subtotal': 100,
'price_subtotal_incl': 100,
})],
'pricelist_id': self.pos_config_usd.pricelist_id.id,
'amount_paid': 100.0,
'amount_total': 100.0,
'amount_tax': 0.0,
'amount_return': 0.0,
})
self.assertEqual(order.currency_id, self.other_currency)
# pay the whole order with the customer account
payment_context = {"active_ids": order.ids, "active_id": order.id}
order_payment = self.env['pos.make.payment'].with_context(**payment_context).create({
'amount': 100.0,
'payment_method_id': self.customer_account_payment_method.id
})
order_payment.with_context(**payment_context).check()
# The session is still open: nothing is accounted yet, the due only comes
# from the pay later payment, which is already in the PoS currency.
self.assertEqual(self.partner_moda.total_due, 0.0)
self.assertEqual(self.partner_moda.get_total_due(self.pos_config_usd.id)['res.partner'][0]['total_due'], 100.0)
# Once the session is closed the due comes from accounting, in company
# currency, and must be converted back to the PoS currency.
current_session.action_pos_session_closing_control()
self.partner_moda.invalidate_recordset(['total_due'])
self.assertEqual(self.partner_moda.total_due, 200.0)
self.assertEqual(self.partner_moda.get_total_due(self.pos_config_usd.id)['res.partner'][0]['total_due'], 100.0)
# Both sources add up, each one in the PoS currency.
self.pos_config_usd.open_ui()
new_session = self.pos_config_usd.current_session_id
new_order = self.env['pos.order'].create({
'company_id': self.env.company.id,
'session_id': new_session.id,
'partner_id': self.partner_moda.id,
'lines': [(0, 0, {
'name': "OL/0002",
'product_id': self.product1.id,
'price_unit': 50,
'discount': 0,
'qty': 1,
'tax_ids': [[6, False, []]],
'price_subtotal': 50,
'price_subtotal_incl': 50,
})],
'pricelist_id': self.pos_config_usd.pricelist_id.id,
'amount_paid': 50.0,
'amount_total': 50.0,
'amount_tax': 0.0,
'amount_return': 0.0,
})
payment_context = {"active_ids": new_order.ids, "active_id": new_order.id}
order_payment = self.env['pos.make.payment'].with_context(**payment_context).create({
'amount': 50.0,
'payment_method_id': self.customer_account_payment_method.id
})
order_payment.with_context(**payment_context).check()
self.assertEqual(self.partner_moda.get_total_due(self.pos_config_usd.id)['res.partner'][0]['total_due'], 150.0)
def test_invoicing_after_closing_session_intermediary_account(self):
""" Test that an invoice can be created after the session is closed """
receivable_account = self.env.company.account_default_pos_receivable_account_id.copy()
self.cash_payment_method.receivable_account_id = receivable_account
self.partner_moda.parent_id = self.partner_adgu
order, _ = self.create_backend_pos_order({
'order_data': {
'partner_id': self.partner_moda.id,
},
'line_data': [
{'product_id': self.ten_dollars_no_tax.product_variant_id.id}
],
'payment_data': [
{'payment_method_id': self.cash_payment_method.id, 'amount': 10},
],
})
current_session = self.pos_config_usd.current_session_id
current_session.action_pos_session_closing_control()
accounting_partner = self.env['res.partner']._find_accounting_partner(self.partner_moda)
self.assertEqual(accounting_partner.total_due, 0.0)
# create invoice
order.action_pos_order_invoice()
self.assertEqual(accounting_partner.total_due, 0.0)
# 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(original_customer_payment_entry.account_id, receivable_account)
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)
aml_receivable = self.env['account.move.line'].formatted_read_group([('account_type', '=', 'asset_receivable')], groupby=['matching_number'], aggregates=['__count'])
self.assertEqual(len(aml_receivable), 3)
for aml_g in aml_receivable:
self.assertEqual(aml_g['__count'], 2)
def test_payment_order_does_not_set_negative_customer_due_total(self):
"""A payment order (negative pay_later amount) must not set customer_due_total < 0.
Before the fix, this caused pos_orders_amount_due to go negative, inflating
remainingDue in the frontend and showing a wrong settle-due amount."""
self.pos_config_eur.payment_method_ids = [(4, self.credit_payment_method.id)]
charge_order, _ = self.create_backend_pos_order({
'order_data': {'partner_id': self.partner_moda.id},
'line_data': [{'product_id': self.ten_dollars_no_tax.product_variant_id.id}],
'payment_data': [{'payment_method_id': self.credit_payment_method.id, 'amount': 10}],
'pos_config': self.pos_config_eur,
})
self.assertEqual(charge_order.customer_due_total, 10.0)
session = self.pos_config_eur.current_session_id
payment_order = self.env['pos.order'].create({
'company_id': self.env.company.id,
'session_id': session.id,
'partner_id': self.partner_moda.id,
'amount_paid': -10.0,
'amount_total': -10.0,
'amount_tax': 0.0,
'amount_return': 0.0,
'payment_ids': [Command.create({
'amount': -10.0,
'payment_method_id': self.credit_payment_method.id,
})],
})
payment_order.write({'state': 'paid'})
self.assertEqual(payment_order.customer_due_total, 0.0)
accounting_partner = self.env['res.partner']._find_accounting_partner(self.partner_moda)
accounting_partner.invalidate_recordset(['pos_orders_amount_due'])
self.assertEqual(accounting_partner.pos_orders_amount_due, 10.0)
def test_has_moves_after_full_settle(self):
""" has_moves should stay True for a fully-settled pay-later customer (no invoice). """
self.pos_config_usd.payment_method_ids = [(4, self.credit_payment_method.id)]
order, _ = self.create_backend_pos_order({
'order_data': {'partner_id': self.partner_moda.id},
'line_data': [{'product_id': self.ten_dollars_no_tax.product_variant_id.id}],
'payment_data': [{'payment_method_id': self.credit_payment_method.id, 'amount': 10}],
})
current_session = self.pos_config_usd.current_session_id
settle_order = self.env['pos.order'].create({
'company_id': self.env.company.id,
'session_id': current_session.id,
'partner_id': self.partner_moda.id,
'lines': [(0, 0, {
'name': "SL/0001",
'product_id': self.pos_config_usd.settle_due_product_id.id,
'price_unit': 10,
'qty': 0,
'price_subtotal': 0,
'price_subtotal_incl': 0,
'settled_order_id': order.id,
})],
'pricelist_id': self.pos_config_usd.pricelist_id.id,
'amount_paid': 0.0,
'amount_total': 0.0,
'amount_tax': 0.0,
'amount_return': 0.0,
})
settle_order.add_payment({'pos_order_id': settle_order.id, 'amount': 10, 'payment_method_id': self.bank_payment_method.id})
settle_order.add_payment({'pos_order_id': settle_order.id, 'amount': -10, 'payment_method_id': self.credit_payment_method.id})
settle_order.state = 'paid'
current_session.action_pos_session_closing_control()
self.assertEqual(self.partner_moda.total_due, 0)
self.assertEqual(self.partner_moda.has_moves, True)
def test_deleted_partner_get_all_total_due(self):
""" Test that get_all_total_due works when some partners have been deleted """
partner_a = self.env["res.partner"].create({"name": "A Partner"})
partner_b = self.env["res.partner"].create({"name": "B Partner"})
partner_c = self.env["res.partner"].create({"name": "C Partner"})
partners = self.env['res.partner'].browse([partner_a.id, partner_b.id, partner_c.id])
partner_b.unlink()
self.assertEqual(len(partners.get_all_total_due(self.pos_config_usd.id)), 2)
def test_matching_orders_and_refunds_using_pay_later_pm_full_match(self):
""" Testing get_matching_paylater_orders.
It needs to return the list of orders to hide. In this case both the
order and the full refund needs to be returned as the amounts paid using
the customer account amounts to 0 in total.
"""
partner = self.env["res.partner"].create({"name": "Partner"})
self.pos_config_usd.payment_method_ids = [(4, self.credit_payment_method.id)]
order, refund = self.create_backend_pos_order({
'order_data': {
'partner_id': partner.id,
},
'line_data': [
{'product_id': self.ten_dollars_with_10_incl.product_variant_id.id},
{'product_id': self.twenty_dollars_with_10_incl.product_variant_id.id},
],
'payment_data': [
{'payment_method_id': self.credit_payment_method.id, 'amount': 30},
],
'refund_data': [
{'payment_method_id': self.credit_payment_method.id, 'amount': -30},
]
})
matching_orders = partner.get_matching_paylater_orders()
self.assertIn(order.id, matching_orders)
self.assertIn(refund.id, matching_orders)
def test_matching_orders_and_refunds_using_pay_later_pm_no_match(self):
""" Testing get_matching_paylater_orders.
It needs to return the list of orders to hide. In this case nothing needs
to be returned as the amount paid using the customer account amounts to 10 in total.
"""
partner = self.env["res.partner"].create({"name": "Partner"})
self.pos_config_usd.payment_method_ids = [(4, self.credit_payment_method.id)]
self.create_backend_pos_order({
'order_data': {
'partner_id': partner.id,
},
'line_data': [
{'product_id': self.ten_dollars_with_10_incl.product_variant_id.id},
{'product_id': self.twenty_dollars_with_10_incl.product_variant_id.id},
],
'payment_data': [
{'payment_method_id': self.credit_payment_method.id, 'amount': 30},
],
'refund_data': [
{'payment_method_id': self.cash_payment_method.id, 'amount': -10},
{'payment_method_id': self.credit_payment_method.id, 'amount': -20},
]
})
matching_orders = partner.get_matching_paylater_orders()
self.assertEqual(len(matching_orders), 0)
def test_matching_orders_and_refunds_using_pay_later_pm_partial_refunds_match(self):
""" Testing get_matching_paylater_orders.
It needs to return the list of orders to hide.
First we have a partial refund. The total using the customer account is not 0.
Therefore the function should return an empty string.
Later the rest is refunded on the customer account. This time the function
should return the order and both refund since the total on the customer account
is 0.
"""
partner = self.env["res.partner"].create({"name": "Partner"})
self.pos_config_usd.payment_method_ids = [(4, self.credit_payment_method.id)]
order, _ = self.create_backend_pos_order({
'order_data': {
'partner_id': partner.id,
},
'line_data': [
{'product_id': self.ten_dollars_with_10_incl.product_variant_id.id},
{'product_id': self.twenty_dollars_with_15_incl.product_variant_id.id},
],
'payment_data': [
{'payment_method_id': self.credit_payment_method.id, 'amount': 30},
],
})
refund_action = order.refund()
refund = self.env['pos.order'].browse(refund_action['res_id'])
with Form(refund) as refund_form:
with refund_form.lines.edit(0) as line:
line.qty = 0
refund = refund_form.save()
self.assertEqual(refund.amount_total, -20.0)
payment_context = {"active_ids": refund.ids, "active_id": refund.id}
refund_payment = self.env['pos.make.payment'].with_context(**payment_context).create({
'amount': refund.amount_total,
'payment_method_id': self.credit_payment_method.id,
})
refund_payment.with_context(**payment_context).check()
matching_orders = partner.get_matching_paylater_orders()
self.assertEqual(len(matching_orders), 0)
refund_action = order.refund()
remaining_refund = self.env['pos.order'].browse(refund_action['res_id'])
self.assertEqual(remaining_refund.amount_total, -10.0)
payment_context = {"active_ids": remaining_refund.ids, "active_id": remaining_refund.id}
refund_payment = self.env['pos.make.payment'].with_context(**payment_context).create({
'amount': remaining_refund.amount_total,
'payment_method_id': self.credit_payment_method.id,
})
refund_payment.with_context(**payment_context).check()
matching_orders = partner.get_matching_paylater_orders()
self.assertIn(order.id, matching_orders)
self.assertIn(refund.id, matching_orders)
self.assertIn(remaining_refund.id, matching_orders)