diff --git a/addons/account_payment_term/__init__.py b/addons/account_payment_term/__init__.py new file mode 100644 index 00000000000..d6210b1285d --- /dev/null +++ b/addons/account_payment_term/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/account_payment_term/__manifest__.py b/addons/account_payment_term/__manifest__.py new file mode 100644 index 00000000000..db7f6dd4638 --- /dev/null +++ b/addons/account_payment_term/__manifest__.py @@ -0,0 +1,21 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Payment Term - Days end of month on the', + 'version': '1.0', + 'category': 'Accounting/Accounting', + 'description': """ +Bridge module to add a new payment term - Days end of month on the +""", + 'website': 'https://www.odoo.com/app/invoicing', + 'depends': ['account'], + 'data': [ + 'views/account_payment_term_views.xml', + ], + 'demo': [ + 'demo/account_payment_term_demo.xml', + ], + 'installable': True, + 'auto_install': True, + 'license': 'OEEL-1', +} diff --git a/addons/account_payment_term/demo/account_payment_term_demo.xml b/addons/account_payment_term/demo/account_payment_term_demo.xml new file mode 100644 index 00000000000..eb28e9fd4d7 --- /dev/null +++ b/addons/account_payment_term/demo/account_payment_term_demo.xml @@ -0,0 +1,12 @@ + + + + + 90 days, on the 10th + Payment terms: 90 days, on the 10th + + + + diff --git a/addons/account_payment_term/i18n/account_payment_term.pot b/addons/account_payment_term/i18n/account_payment_term.pot new file mode 100644 index 00000000000..c8dd4768641 --- /dev/null +++ b/addons/account_payment_term/i18n/account_payment_term.pot @@ -0,0 +1,65 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_payment_term +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-12-13 08:17+0000\n" +"PO-Revision-Date: 2023-12-13 08:17+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_payment_term +#: model:account.payment.term,name:account_payment_term.account_payment_term_advance +msgid "90 days, on the 10th" +msgstr "" + +#. module: account_payment_term +#: model:ir.model.fields.selection,name:account_payment_term.selection__account_payment_term_line__delay_type__days_end_of_month_on_the +msgid "Days end of month on the" +msgstr "" + +#. module: account_payment_term +#: model:ir.model.fields,field_description:account_payment_term.field_account_payment_term_line__days_next_month +msgid "Days on the next month" +msgstr "" + +#. module: account_payment_term +#: model:ir.model.fields,field_description:account_payment_term.field_account_payment_term_line__delay_type +msgid "Delay Type" +msgstr "" + +#. module: account_payment_term +#: model:ir.model.fields,field_description:account_payment_term.field_account_payment_term_line__display_days_next_month +msgid "Display Days Next Month" +msgstr "" + +#. module: account_payment_term +#: model:ir.model,name:account_payment_term.model_account_payment_term_line +msgid "Payment Terms Line" +msgstr "" + +#. module: account_payment_term +#: model_terms:account.payment.term,note:account_payment_term.account_payment_term_advance +msgid "Payment terms: 90 days, on the 10th" +msgstr "" + +#. module: account_payment_term +#. odoo-python +#: code:addons/account_payment_term/models/account_payment_term.py:0 +#, python-format +msgid "The days added must be a number and has to be between 0 and 31." +msgstr "" + +#. module: account_payment_term +#. odoo-python +#: code:addons/account_payment_term/models/account_payment_term.py:0 +#, python-format +msgid "The days added must be between 0 and 31." +msgstr "" diff --git a/addons/account_payment_term/models/__init__.py b/addons/account_payment_term/models/__init__.py new file mode 100644 index 00000000000..56a24fcf5f0 --- /dev/null +++ b/addons/account_payment_term/models/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import account_payment_term diff --git a/addons/account_payment_term/models/account_payment_term.py b/addons/account_payment_term/models/account_payment_term.py new file mode 100644 index 00000000000..6fae63f40fb --- /dev/null +++ b/addons/account_payment_term/models/account_payment_term.py @@ -0,0 +1,54 @@ +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError +from odoo.tools import date_utils + + +class AccountPaymentTermLine(models.Model): + _inherit = "account.payment.term.line" + + delay_type = fields.Selection( + selection_add=[('days_end_of_month_on_the', 'Days end of month on the')], + ondelete={'days_end_of_month_on_the': 'set default'} + ) + display_days_next_month = fields.Boolean(compute='_compute_display_days_next_month') + days_next_month = fields.Char( + string='Days on the next month', + readonly=False, + store=True, + default='10', + size=2, + ) + + @api.constrains('days_next_month') + def _check_valid_char_value(self): + for record in self: + if record.days_next_month.isnumeric(): + if not (0 <= int(record.days_next_month) <= 31): + raise ValidationError(_('The days added must be between 0 and 31.')) + else: + raise ValidationError(_('The days added must be a number and has to be between 0 and 31.')) + + @api.depends('delay_type') + def _compute_display_days_next_month(self): + for record in self: + record.display_days_next_month = record.delay_type == 'days_end_of_month_on_the' + + def _get_due_date(self, date_ref): + res = super()._get_due_date(date_ref) + + due_date = fields.Date.from_string(date_ref) or fields.Date.today() + if self.delay_type == 'days_end_of_month_on_the': + date_end_of_month = date_utils.end_of(due_date + relativedelta(days=self.nb_days), 'month') + + # Special case handling when the day of the month is 29, 30 or 31 to avoid exceeding the next month's end + # For instance, with a payment term of 30 days end of month and using the 31st of a month, + # prevent calculation from moving beyond the end of the next month (e.g., early March instead of end February) + if self.days_next_month in {'29', '30', '31'}: + # We get the min of the days the user enter in the payment term and the last day of the next month + days_next_month = relativedelta(days=min(int(self.days_next_month), (date_end_of_month + relativedelta(month=2)).day)) + else: + days_next_month = relativedelta(days=int(self.days_next_month)) + return date_end_of_month + days_next_month + return res diff --git a/addons/account_payment_term/tests/__init__.py b/addons/account_payment_term/tests/__init__.py new file mode 100644 index 00000000000..62598fc0173 --- /dev/null +++ b/addons/account_payment_term/tests/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_payment_term diff --git a/addons/account_payment_term/tests/test_payment_term.py b/addons/account_payment_term/tests/test_payment_term.py new file mode 100644 index 00000000000..a438f1ec43a --- /dev/null +++ b/addons/account_payment_term/tests/test_payment_term.py @@ -0,0 +1,58 @@ +from odoo import Command, fields +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +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.invoice = cls.init_invoice('out_refund', products=cls.product_a + cls.product_b) + + cls.pay_term_days_end_of_month_10 = cls.env['account.payment.term'].create({ + 'name': "basic case", + 'line_ids': [ + Command.create({ + 'value': 'percent', + 'value_amount': 100, + 'nb_days': 30, + 'delay_type': 'days_end_of_month_on_the', + 'days_next_month': 10, + }), + ], + }) + cls.pay_term_days_end_of_month_31 = cls.env['account.payment.term'].create({ + 'name': "special case", + 'line_ids': [ + Command.create({ + 'value': 'percent', + 'value_amount': 100, + 'nb_days': 30, + 'delay_type': 'days_end_of_month_on_the', + 'days_next_month': 31, + }), + ], + }) + + def test_payment_term_days_end_of_month_on_the(self): + """ + This test will check that payment terms with a delay_type 'days_end_of_month_on_the' works as expected. + It will check if the date of the date maturity is correctly calculated depending on the invoice date and payment + term selected. + """ + with Form(self.invoice) as basic_case: + basic_case.invoice_payment_term_id = self.pay_term_days_end_of_month_10 + basic_case.invoice_date = '2023-12-12' + + expected_date_basic_case = self.invoice.line_ids.filtered(lambda l: l.account_id == self.company_data['default_account_receivable']).mapped('date_maturity'), + self.assertEqual(expected_date_basic_case[0], [fields.Date.from_string('2024-02-10')]) + + with Form(self.invoice) as special_case: + special_case.invoice_payment_term_id = self.pay_term_days_end_of_month_31 + special_case.invoice_date = '2023-12-12' + + expected_date_special_case = self.invoice.line_ids.filtered(lambda l: l.account_id == self.company_data['default_account_receivable']).mapped('date_maturity'), + self.assertEqual(expected_date_special_case[0], [fields.Date.from_string('2024-02-29')]) diff --git a/addons/account_payment_term/views/account_payment_term_views.xml b/addons/account_payment_term/views/account_payment_term_views.xml new file mode 100644 index 00000000000..25654b808d8 --- /dev/null +++ b/addons/account_payment_term/views/account_payment_term_views.xml @@ -0,0 +1,16 @@ + + + + + account.payment.term + account.payment.term.form + + + + + + + + + +