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
+
+
+
+
+
+
+
+
+
+