# -*- coding: utf-8 -*- from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.exceptions import ValidationError from odoo.tests import tagged from odoo import fields, Command from odoo.tests.common import Form @tagged('post_install', '-at_install') class TestAccountPaymentTerms(AccountTestInvoicingCommon): @classmethod def setUpClass(cls, chart_template_ref=None): super().setUpClass(chart_template_ref=chart_template_ref) cls.pay_term_today = cls.env['account.payment.term'].create({ 'name': 'Today', 'line_ids': [ (0, 0, { 'value': 'balance', 'days': 0, }), ], }) cls.pay_term_next_month_on_the_15 = cls.env['account.payment.term'].create({ 'name': 'Next month on the 15th', 'line_ids': [ (0, 0, { 'value': 'balance', 'days': 0, 'end_month': True, 'days_after': 15, }), ], }) cls.pay_term_last_day_of_month = cls.env['account.payment.term'].create({ 'name': 'Last Day of month', 'line_ids': [ (0, 0, { 'value': 'balance', 'days': 0, 'end_month': True }), ], }) cls.pay_term_first_day_next_month = cls.env['account.payment.term'].create({ 'name': 'First day next month', 'line_ids': [ (0, 0, { 'value': 'balance', 'days': 0, 'end_month': True, 'days_after': 1, }), ], }) cls.pay_term_net_30_days = cls.env['account.payment.term'].create({ 'name': 'Net 30 days', 'line_ids': [ (0, 0, { 'value': 'balance', 'days': 30, }), ], }) cls.pay_term_30_days_end_of_month = cls.env['account.payment.term'].create({ 'name': '30 days end of month', 'line_ids': [ (0, 0, { 'value': 'balance', 'days': 30, 'end_month': True }), ], }) cls.pay_term_1_month_end_of_month = cls.env['account.payment.term'].create({ 'name': '1 month, end of month', 'line_ids': [ (0, 0, { 'value': 'balance', 'months': 1, 'days': 0, 'end_month': True }), ], }) cls.pay_term_30_days_end_of_month_the_10 = cls.env['account.payment.term'].create({ 'name': '30 days end of month the 10th', 'line_ids': [ (0, 0, { 'value': 'balance', 'days': 30, 'end_month': True, 'days_after': 10, }), ], }) cls.pay_term_90_days_end_of_month_the_10 = cls.env['account.payment.term'].create({ 'name': '90 days end of month the 10', 'line_ids': [ (0, 0, { 'value': 'balance', 'days': 90, 'end_month': True, 'days_after': 10, }), ], }) cls.pay_term_3_months_end_of_month_the_10 = cls.env['account.payment.term'].create({ 'name': '3 months end of month the 10', 'line_ids': [ (0, 0, { 'value': 'balance', 'months': 3, 'end_month': True, 'days_after': 10, }), ], }) cls.pay_term_end_month_on_the_30th = cls.env['account.payment.term'].create({ 'name': 'End of month, the 30th', 'line_ids': [ (0, 0, { 'value': 'balance', 'end_month': True, 'days_after': 30, }), ], }) cls.pay_term_1_month_15_days_end_month_45_days = cls.env['account.payment.term'].create({ 'name': '1 month, 15 days, end month, 45 days', 'line_ids': [ (0, 0, { 'value': 'balance', 'months': 1, 'days': 15, 'end_month': True, 'days_after': 45, }), ], }) cls.pay_term_next_10_of_the_month = cls.env['account.payment.term'].create({ 'name': 'Next 10th of the month', 'line_ids': [ (0, 0, { 'value': 'balance', 'months': 0, 'days': -10, 'end_month': True, 'days_after': 10, }), ], }) cls.invoice = cls.init_invoice('out_refund', products=cls.product_a+cls.product_b) def assertPaymentTerm(self, pay_term, invoice_date, dates): with Form(self.invoice) as move_form: move_form.invoice_payment_term_id = pay_term move_form.invoice_date = invoice_date self.assertEqual( self.invoice.line_ids.filtered( lambda l: l.account_id == self.company_data['default_account_receivable'] ).mapped('date_maturity'), [fields.Date.from_string(date) for date in dates], ) def test_payment_term(self): self.assertPaymentTerm(self.pay_term_today, '2019-01-01', ['2019-01-01']) self.assertPaymentTerm(self.pay_term_today, '2019-01-15', ['2019-01-15']) self.assertPaymentTerm(self.pay_term_today, '2019-01-31', ['2019-01-31']) self.assertPaymentTerm(self.pay_term_next_month_on_the_15, '2019-01-01', ['2019-02-15']) self.assertPaymentTerm(self.pay_term_next_month_on_the_15, '2019-01-15', ['2019-02-15']) self.assertPaymentTerm(self.pay_term_next_month_on_the_15, '2019-01-31', ['2019-02-15']) self.assertPaymentTerm(self.pay_term_last_day_of_month, '2019-01-01', ['2019-01-31']) self.assertPaymentTerm(self.pay_term_last_day_of_month, '2019-01-15', ['2019-01-31']) self.assertPaymentTerm(self.pay_term_last_day_of_month, '2019-01-31', ['2019-01-31']) self.assertPaymentTerm(self.pay_term_first_day_next_month, '2019-01-01', ['2019-02-01']) self.assertPaymentTerm(self.pay_term_first_day_next_month, '2019-01-15', ['2019-02-01']) self.assertPaymentTerm(self.pay_term_first_day_next_month, '2019-01-31', ['2019-02-01']) self.assertPaymentTerm(self.pay_term_net_30_days, '2022-01-01', ['2022-01-31']) self.assertPaymentTerm(self.pay_term_net_30_days, '2022-01-15', ['2022-02-14']) self.assertPaymentTerm(self.pay_term_net_30_days, '2022-01-31', ['2022-03-02']) self.assertPaymentTerm(self.pay_term_30_days_end_of_month, '2022-01-01', ['2022-01-31']) self.assertPaymentTerm(self.pay_term_30_days_end_of_month, '2022-01-15', ['2022-02-28']) self.assertPaymentTerm(self.pay_term_30_days_end_of_month, '2022-01-31', ['2022-03-31']) self.assertPaymentTerm(self.pay_term_1_month_end_of_month, '2022-01-01', ['2022-02-28']) self.assertPaymentTerm(self.pay_term_1_month_end_of_month, '2022-01-15', ['2022-02-28']) self.assertPaymentTerm(self.pay_term_1_month_end_of_month, '2022-01-31', ['2022-02-28']) self.assertPaymentTerm(self.pay_term_30_days_end_of_month_the_10, '2022-01-01', ['2022-02-10']) self.assertPaymentTerm(self.pay_term_30_days_end_of_month_the_10, '2022-01-15', ['2022-03-10']) self.assertPaymentTerm(self.pay_term_30_days_end_of_month_the_10, '2022-01-31', ['2022-04-10']) self.assertPaymentTerm(self.pay_term_90_days_end_of_month_the_10, '2022-01-01', ['2022-05-10']) self.assertPaymentTerm(self.pay_term_90_days_end_of_month_the_10, '2022-01-15', ['2022-05-10']) self.assertPaymentTerm(self.pay_term_90_days_end_of_month_the_10, '2022-01-31', ['2022-06-10']) self.assertPaymentTerm(self.pay_term_3_months_end_of_month_the_10, '2022-01-01', ['2022-05-10']) self.assertPaymentTerm(self.pay_term_3_months_end_of_month_the_10, '2022-01-15', ['2022-05-10']) self.assertPaymentTerm(self.pay_term_3_months_end_of_month_the_10, '2022-01-31', ['2022-05-10']) self.assertPaymentTerm(self.pay_term_1_month_15_days_end_month_45_days, '2022-01-01', ['2022-04-14']) self.assertPaymentTerm(self.pay_term_1_month_15_days_end_month_45_days, '2022-01-15', ['2022-05-15']) self.assertPaymentTerm(self.pay_term_1_month_15_days_end_month_45_days, '2022-01-31', ['2022-05-15']) self.assertPaymentTerm(self.pay_term_next_10_of_the_month, '2022-01-01', ['2022-01-10']) self.assertPaymentTerm(self.pay_term_next_10_of_the_month, '2022-01-09', ['2022-01-10']) self.assertPaymentTerm(self.pay_term_next_10_of_the_month, '2022-01-10', ['2022-01-10']) self.assertPaymentTerm(self.pay_term_next_10_of_the_month, '2022-01-15', ['2022-02-10']) self.assertPaymentTerm(self.pay_term_next_10_of_the_month, '2022-01-31', ['2022-02-10']) def test_payment_term_compute_method(self): def assert_payment_term_values(expected_values_list): res = pay_term._compute_terms( fields.Date.from_string('2016-01-01'), self.env.company.currency_id, self.env.company, 150, 150, 1, 1000, 1000, ) self.assertEqual(len(res), len(expected_values_list)) for values, (company_amount, discount_balance) in zip(res, expected_values_list): self.assertDictEqual( { 'company_amount': values['company_amount'], 'discount_balance': values['discount_balance'], }, { 'company_amount': company_amount, 'discount_balance': discount_balance, }, ) pay_term = self.env['account.payment.term'].create({ 'name': "turlututu", 'line_ids': [ Command.create({ 'value': 'percent', 'value_amount': 10, 'days': 2, 'discount_percentage': 10, 'discount_days': 1, }), Command.create({ 'value': 'percent', 'value_amount': 20, 'days': 4, 'discount_percentage': 20, 'discount_days': 3, }), Command.create({ 'value': 'percent', 'value_amount': 20, 'days': 6, }), Command.create({ 'value': 'balance', 'days': 8, 'discount_percentage': 20, 'discount_days': 7, }), ], }) self.env.company.early_pay_discount_computation = 'included' assert_payment_term_values([ (115.0, 103.5), (230.0, 184.0), (230.0, 0.0), (575.0, 460.0), ]) self.env.company.early_pay_discount_computation = 'excluded' assert_payment_term_values([ (115.0, 105.0), (230.0, 190.0), (230.0, 0.0), (575.0, 475.0), ]) def test_payment_term_compute_method_cash_rounding(self): """Test that the payment terms are computed correctly in case we apply cash rounding. We check the amounts in document and company currency. We check that the cash rounding does not change the totals in document or company curreny. """ def assert_payment_term_values(expected_values_list): foreign_currency = self.currency_data['currency'] rate = self.env['res.currency']._get_conversion_rate(foreign_currency, self.env.company.currency_id, self.env.company, '2017-01-01') self.assertEqual(rate, 0.5) res = pay_term._compute_terms( fields.Date.from_string('2017-01-01'), foreign_currency, self.env.company, 75, 150, 1, 359.18, 718.35, cash_rounding=self.cash_rounding_a ) self.assertEqual(len(res), len(expected_values_list)) keys = ['company_amount', 'discount_balance', 'foreign_amount', 'discount_amount_currency'] for index, (values, expected_values) in enumerate(zip(res, expected_values_list)): for key in keys: with self.subTest(index=index, key=key): self.assertAlmostEqual(values[key], expected_values[key]) total_company_amount = sum(value['company_amount'] for value in res) total_foreign_amount = sum(value['foreign_amount'] for value in res) self.assertAlmostEqual(total_company_amount, 434.18) self.assertAlmostEqual(total_foreign_amount, 868.35) pay_term = self.env['account.payment.term'].create({ 'name': "turlututu", 'line_ids': [ Command.create({ 'value': 'percent', 'value_amount': 10, 'days': 2, 'discount_percentage': 10, 'discount_days': 1, }), Command.create({ 'value': 'percent', 'value_amount': 20, 'days': 4, 'discount_percentage': 20, 'discount_days': 3, }), Command.create({ 'value': 'percent', 'value_amount': 20, 'days': 6, }), Command.create({ 'value': 'balance', 'days': 8, 'discount_percentage': 20, 'discount_days': 7, }), ], }) with self.subTest(test='included'): self.env.company.early_pay_discount_computation = 'included' assert_payment_term_values([ { 'company_amount': 43.43, 'discount_balance': 39.11, 'foreign_amount': 86.85, 'discount_amount_currency': 78.20, }, { 'company_amount': 86.86, 'discount_balance': 69.51, 'foreign_amount': 173.70, 'discount_amount_currency': 139.00, }, { 'company_amount': 86.86, 'discount_balance': 0, 'foreign_amount': 173.70, 'discount_amount_currency': 0.00, }, { 'company_amount': 217.03, 'discount_balance': 173.63, 'foreign_amount': 434.10, 'discount_amount_currency': 347.30, }, ]) with self.subTest(test='excluded'): self.env.company.early_pay_discount_computation = 'excluded' assert_payment_term_values([ { 'company_amount': 43.43, 'discount_balance': 39.86, 'foreign_amount': 86.85, 'discount_amount_currency': 79.70, }, { 'company_amount': 86.86, 'discount_balance': 72.51, 'foreign_amount': 173.70, 'discount_amount_currency': 145.00, }, { 'company_amount': 86.86, 'discount_balance': 0, 'foreign_amount': 173.70, 'discount_amount_currency': 0.00, }, { 'company_amount': 217.03, 'discount_balance': 181.13, 'foreign_amount': 434.10, 'discount_amount_currency': 362.30, }, ]) def test_payment_term_multi_company(self): """ Ensure that the payment term is determined by `move.company_id` rather than `user.company_id`. OdooBot has `res.company(1)` set as the default company. The test checks that the payment term correctly reflects the company associated with the move, independent of the user's default company. """ user_company, other_company = self.company_data_2.get('company'), self.company_data.get('company') self.env.user.write({ 'company_ids': [user_company.id, other_company.id], 'company_id': user_company.id, }) self.pay_terms_a.company_id = user_company self.partner_a.with_company(user_company).property_payment_term_id = self.pay_terms_a self.partner_a.with_company(other_company).property_payment_term_id = False invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'company_id': other_company.id }) self.assertFalse(invoice.invoice_payment_term_id)