[IMP] account: payment term - Days end of month

This commit add a new delay_type that will add to the date the nb_days then go
the end of the month and finally add a new field called days_next_month.

This field is a Char because we want the field to be of size 2. Also, we added a
constraint that this field must be numeric and between 0 and 31.

Ex of use with Invoice date the 25/11/2023, if we have a payment term with 90
for the nb_days and 10 for the days_next_month:

+90 days = 23/02/2024
End of month = 29/02/2024
+10 days = 10/03/2024

(Also, there is a 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)

closes odoo/odoo#143758

Task: 3609320
Signed-off-by: Florian Gilbert (flg) <flg@odoo.com>
This commit is contained in:
Maximilien (malb)
2023-12-15 11:40:43 +00:00
parent 924006f33c
commit e5097a183a
9 changed files with 235 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
@@ -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',
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="account_payment_term_advance" model="account.payment.term">
<field name="name">90 days, on the 10th</field>
<field name="note">Payment terms: 90 days, on the 10th</field>
<field name="line_ids" eval="[
Command.clear(),
Command.create({'value': 'percent', 'value_amount': 100.0, 'delay_type': 'days_end_of_month_on_the','nb_days': 90, 'days_next_month': 10})]"/>
</record>
</data>
</odoo>
@@ -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 ""
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_payment_term
@@ -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
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_payment_term
@@ -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')])
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="account_payment_term_form" model="ir.ui.view">
<field name="model">account.payment.term</field>
<field name="name">account.payment.term.form</field>
<field name="inherit_id" ref="account.view_payment_term_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='line_ids']/tree['delay_type']" position="inside">
<field name="display_days_next_month" column_invisible="1"/>
<field name="days_next_month" width="30px" nolabel="1" invisible="not display_days_next_month"/>
</xpath>
</field>
</record>
</data>
</odoo>