diff --git a/product_contract/README.rst b/product_contract/README.rst index 64d4740680..d88c8bcb1a 100644 --- a/product_contract/README.rst +++ b/product_contract/README.rst @@ -52,6 +52,12 @@ To use this module, you need to: product 3. Define default recurrence rules +Known issues / Roadmap +====================== + +- There's no support right now for computing the start date for the + following recurrent types: daily, weekly and monthlylastday. + Bug Tracker =========== @@ -80,6 +86,7 @@ Contributors - Ernesto Tejeda - Pedro M. Baeza + - Carlos Roca - David Jaen diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index 606ac6ef96..9c899adfbe 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -61,6 +61,73 @@ class ProductTemplate(models.Model): string="Renewal type", help="Specify Interval for automatic renewal.", ) + contract_start_date_method = fields.Selection( + [ + ("manual", "Manual"), + ("start_this", "Start of current period"), + ("end_this", "End of current period"), + ("start_next", "Start of next period"), + ("end_next", "End of next period"), + ], + "Start Date Method", + default="manual", + help="""This field allows to define how the start date of the contract will + be calculated: + + - Manual: The start date will be selected by the user, by default will be the + date of sale confirmation. + - Start of current period: The start date will be the first day of the actual + period selected on 'Invoicing Every' field. Example: If we are on 2024/08/27 + and the period selected is 'Year(s)' the start date will be 2024/01/01. + - End of current period: The start date will be the last day of the actual + period selected on 'Invoicing Every' field. Example: If we are on 2024/08/27 + and the period selected is 'Year(s)' the start date will be 2024/12/31. + - Start of next period: The start date will be the first day of the next + period selected on 'Invoicing Every' field. Example: If we are on 2024/08/27 + and the period selected is 'Year(s)' the start date will be 2025/01/01. + - End of next period: The start date will be the last day of the actual + period selected on 'Invoicing Every' field. Example: If we are on 2024/08/27 + and the period selected is 'Year(s)' the start date will be 2025/12/31. + """, + ) + force_month_yearly = fields.Selection( + [ + ("1", "January"), + ("2", "February"), + ("3", "March"), + ("4", "April"), + ("5", "May"), + ("6", "June"), + ("7", "July"), + ("8", "August"), + ("9", "September"), + ("10", "October"), + ("11", "November"), + ("12", "December"), + ], + "Force Month", + ) + force_month_quarterly = fields.Selection( + [ + ("1", "First month"), + ("2", "Second month"), + ("3", "Third month"), + ], + "Force Month", + help="Force the month to be used inside the quarter", + ) + force_month_semesterly = fields.Selection( + [ + ("1", "First month"), + ("2", "Second month"), + ("3", "Third month"), + ("4", "Fourth month"), + ("5", "Fifth month"), + ("6", "Sixth month"), + ], + "Force Month", + help="Force the month to be used inside the semester", + ) def write(self, vals): if "is_contract" in vals and vals["is_contract"] is False: diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 9832c51dfc..6ab44cdb24 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -67,6 +67,7 @@ def action_create_contract(self): line_to_create_contract = rec.order_line.filtered( lambda r: not r.contract_id and r.product_id.is_contract ) + line_to_create_contract._set_contract_line_start_date() line_to_update_contract = rec.order_line.filtered( lambda r: r.contract_id and r.product_id.is_contract diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 04a8ce20be..5743129378 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -7,6 +7,13 @@ from odoo import _, api, fields, models from odoo.exceptions import ValidationError +MONTH_NB_MAPPING = { + "monthly": 1, + "quarterly": 3, + "semesterly": 6, + "yearly": 12, +} + class SaleOrderLine(models.Model): _inherit = "sale.order.line" @@ -81,6 +88,9 @@ class SaleOrderLine(models.Model): string="Renewal type", help="Specify Interval for automatic renewal.", ) + contract_start_date_method = fields.Selection( + related="product_id.contract_start_date_method" + ) @api.constrains("contract_id") def _check_contact_is_not_terminated(self): @@ -109,15 +119,18 @@ def _get_auto_renew_rule_type(self): def _get_date_end(self): self.ensure_one() - contract_line_model = self.env["contract.line"] - date_end = ( - self.date_start - + contract_line_model.get_relative_delta( - self._get_auto_renew_rule_type(), - int(self.product_uom_qty), + contract_start_date_method = self.product_id.contract_start_date_method + date_end = False + if contract_start_date_method == "manual": + contract_line_model = self.env["contract.line"] + date_end = ( + self.date_start + + contract_line_model.get_relative_delta( + self._get_auto_renew_rule_type(), + int(self.product_uom_qty), + ) + - relativedelta(days=1) ) - - relativedelta(days=1) - ) return date_end @api.depends("product_id") @@ -127,8 +140,9 @@ def _compute_auto_renew(self): rec.product_uom_qty = rec.product_id.default_qty rec.recurring_rule_type = rec.product_id.recurring_rule_type rec.recurring_invoicing_type = rec.product_id.recurring_invoicing_type - rec.date_start = rec.date_start or fields.Date.today() - + contract_start_date_method = rec.product_id.contract_start_date_method + if contract_start_date_method == "manual": + rec.date_start = rec.date_start or fields.Date.today() rec.date_end = rec._get_date_end() rec.is_auto_renew = rec.product_id.is_auto_renew if rec.is_auto_renew: @@ -269,3 +283,45 @@ def _compute_qty_to_invoice(self): res = super()._compute_qty_to_invoice() self.filtered("product_id.is_contract").update({"qty_to_invoice": 0.0}) return res + + def _set_contract_line_start_date(self): + """Set date start of lines using it's method and the confirmation date.""" + for line in self: + if ( + line.contract_start_date_method == "manual" + or line.recurring_rule_type in ["daily", "weekly", "monthlylastday"] + ): + continue + is_end = "end_" in line.contract_start_date_method + today = fields.Date.today() + month_period = month = today.month + month_nb = MONTH_NB_MAPPING[line.recurring_rule_type] + # The period number is started by 0 to be able to calculate the month + period_number = (month - 1) // month_nb + if line.recurring_rule_type == "yearly": + month_period = 1 + elif line.recurring_rule_type != "monthly": + # Checking quarterly and semesterly + month_period = period_number * month_nb + 1 + forced_month = 0 + if line.recurring_rule_type != "monthly": + forced_value = int( + line.product_id["force_month_%s" % line.recurring_rule_type] + ) + if forced_value: + # When the selected period is yearly, the period_number field is + # 0, so forced_month will take the value of the forced month set + # on product. + forced_month = month_nb * period_number + forced_value + # If forced_month is set, use it, but if it isn't use the month_period + start_date = today + relativedelta( + day=1, month=forced_month or month_period + ) + if is_end: + increment = month_nb - 1 if not forced_month else 0 + start_date = start_date + relativedelta(months=1 + increment, days=-1) + if "_next" in line.contract_start_date_method and start_date <= today: + start_date = start_date + relativedelta(months=month_nb) + if is_end: + start_date = start_date + relativedelta(day=1, months=1, days=-1) + line.date_start = start_date diff --git a/product_contract/readme/CONTRIBUTORS.md b/product_contract/readme/CONTRIBUTORS.md index 0205873524..c80ad3d118 100644 --- a/product_contract/readme/CONTRIBUTORS.md +++ b/product_contract/readme/CONTRIBUTORS.md @@ -3,4 +3,5 @@ - [Tecnativa](https://www.tecnativa.com): - Ernesto Tejeda - Pedro M. Baeza + - Carlos Roca - David Jaen \<\> diff --git a/product_contract/readme/ROADMAP.md b/product_contract/readme/ROADMAP.md new file mode 100644 index 0000000000..31d21127a4 --- /dev/null +++ b/product_contract/readme/ROADMAP.md @@ -0,0 +1,2 @@ +- There's no support right now for computing the start date for the + following recurrent types: daily, weekly and monthlylastday. \ No newline at end of file diff --git a/product_contract/static/description/index.html b/product_contract/static/description/index.html index 49185289e4..04db3ee2a6 100644 --- a/product_contract/static/description/index.html +++ b/product_contract/static/description/index.html @@ -380,11 +380,12 @@

Recurring - Product Contract

+
+

Known issues / Roadmap

+
    +
  • There’s no support right now for computing the start date for the +following recurrent types: daily, weekly and monthlylastday.
  • +
+
-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -408,29 +416,30 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • LasLabs
  • ACSONE SA/NV
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association diff --git a/product_contract/views/product_template.xml b/product_contract/views/product_template.xml index 8191d880bc..0de57a86d4 100644 --- a/product_contract/views/product_template.xml +++ b/product_contract/views/product_template.xml @@ -36,6 +36,19 @@ + + + + diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml index 8c8f7178dd..61fce5b15a 100644 --- a/product_contract/views/sale_order.xml +++ b/product_contract/views/sale_order.xml @@ -71,7 +71,7 @@ - + @@ -114,8 +114,13 @@ /> - - + + + - diff --git a/product_contract/wizards/product_contract_configurator.py b/product_contract/wizards/product_contract_configurator.py index 4916d33d0b..8c10eb82d5 100644 --- a/product_contract/wizards/product_contract_configurator.py +++ b/product_contract/wizards/product_contract_configurator.py @@ -75,6 +75,9 @@ class ProductContractConfigurator(models.TransientModel): string="Renewal type", help="Specify Interval for automatic renewal.", ) + contract_start_date_method = fields.Selection( + related="product_id.contract_start_date_method" + ) @api.depends("product_id", "company_id") def _compute_contract_template_id(self): diff --git a/product_contract/wizards/product_contract_configurator_views.xml b/product_contract/wizards/product_contract_configurator_views.xml index d08f524997..a0c2d33479 100644 --- a/product_contract/wizards/product_contract_configurator_views.xml +++ b/product_contract/wizards/product_contract_configurator_views.xml @@ -12,14 +12,25 @@ - - - - + + + + + - +