Skip to content

Commit

Permalink
[IMP] product_contract: Add posibility to compute date_start of line …
Browse files Browse the repository at this point in the history
…using confirmation date_start

With these changes, we allow the contract line start date to be computed
using the order confirmation date. When the product is configured with
any of the options set in contract_start_date_method other than manual,
the start date will be calculated based on the established date and the
selected period.

Additionally, we can force the month in which we will work in case the
frequency is yearly, quarterly, or semesterly.

Is not added support for daily, weekly or monthlylastday in this commit.
  • Loading branch information
CarlosRoca13 committed Sep 2, 2024
1 parent 88d2cb9 commit 9e81292
Show file tree
Hide file tree
Showing 16 changed files with 1,131 additions and 107 deletions.
7 changes: 7 additions & 0 deletions product_contract/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
===========

Expand Down Expand Up @@ -80,6 +86,7 @@ Contributors

- Ernesto Tejeda
- Pedro M. Baeza
- Carlos Roca

- David Jaen <[email protected]>

Expand Down
350 changes: 333 additions & 17 deletions product_contract/i18n/es.po

Large diffs are not rendered by default.

284 changes: 275 additions & 9 deletions product_contract/i18n/product_contract.pot

Large diffs are not rendered by default.

67 changes: 67 additions & 0 deletions product_contract/models/product_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions product_contract/models/sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
165 changes: 132 additions & 33 deletions product_contract/models/sale_order_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -22,26 +29,9 @@ class SaleOrderLine(models.Model):
string="Contract Template",
compute="_compute_contract_template_id",
)
recurring_rule_type = fields.Selection(
[
("daily", "Day(s)"),
("weekly", "Week(s)"),
("monthly", "Month(s)"),
("monthlylastday", "Month(s) last day"),
("quarterly", "Quarter(s)"),
("semesterly", "Semester(s)"),
("yearly", "Year(s)"),
],
default="monthly",
string="Invoice Every",
copy=False,
)
recurring_rule_type = fields.Selection(related="product_id.recurring_rule_type")
recurring_invoicing_type = fields.Selection(
[("pre-paid", "Pre-paid"), ("post-paid", "Post-paid")],
default="pre-paid",
string="Invoicing type",
help="Specify if process date is 'from' or 'to' invoicing date",
copy=False,
related="product_id.recurring_invoicing_type"
)
date_start = fields.Date()
date_end = fields.Date()
Expand Down Expand Up @@ -81,6 +71,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):
Expand Down Expand Up @@ -109,33 +102,35 @@ 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")
def _compute_auto_renew(self):
for rec in self:
if rec.product_id.is_contract:
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:
rec.auto_renew_interval = rec.product_id.auto_renew_interval
rec.auto_renew_rule_type = rec.product_id.auto_renew_rule_type

@api.onchange("date_start", "product_uom_qty", "recurring_rule_type")
@api.onchange("date_start", "product_uom_qty")
def onchange_date_start(self):
for rec in self.filtered("product_id.is_contract"):
rec.date_end = rec._get_date_end() if rec.date_start else False
Expand Down Expand Up @@ -171,7 +166,7 @@ def _prepare_contract_line_values(
return {
"sequence": self.sequence,
"product_id": self.product_id.id,
"name": self.name,
"name": self.name.split(":\n")[0],
"quantity": self._get_contract_line_qty(),
"uom_id": self.product_uom.id,
"price_unit": self.price_unit,
Expand Down Expand Up @@ -269,3 +264,107 @@ 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=increment, day=31)
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=31)
line.date_start = start_date

@api.depends("product_id")
def _compute_name(self):
res = super()._compute_name()
for line in self:
if line.is_contract:
date_text = ""
if line.contract_start_date_method == "manual":
date_text = "%s" % line.date_start

Check warning on line 317 in product_contract/models/sale_order_line.py

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L317

Added line #L317 was not covered by tests
if line.date_end:
date_text += " -> %s" % line.date_end

Check warning on line 319 in product_contract/models/sale_order_line.py

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L319

Added line #L319 was not covered by tests
else:
field_info = dict(
line._fields["contract_start_date_method"].get_description(
self.env
)
)
field_selection = dict(field_info.get("selection"))
start_method_label = field_selection.get(
line.contract_start_date_method
)
date_text = "%s" % start_method_label
if (
line.recurring_rule_type != "monthly"
and line.product_id["force_month_%s" % line.recurring_rule_type]
):
field_info = dict(
self.env["product.template"]
._fields["force_month_%s" % line.recurring_rule_type]
.get_description(self.env)
)
field_selection = dict(field_info.get("selection"))
force_month_label = field_selection.get(
line.product_id["force_month_%s" % line.recurring_rule_type]
)
date_text += " (%s)" % force_month_label
field_info = dict(
self._fields["recurring_rule_type"].get_description(self.env)
)
field_selection = dict(field_info.get("selection"))
recurring_rule_label = field_selection.get(line.recurring_rule_type)
field_info = dict(
self._fields["recurring_invoicing_type"].get_description(self.env)
)
field_selection = dict(field_info.get("selection"))
invoicing_type_label = field_selection.get(
line.recurring_invoicing_type
)
line.name = _(
"""{product}:
- Recurrency: {recurring_rule}
- Invoicing Type: {invoicing_type}
- Date: {date_text}
"""
).format(
product=line.product_id.display_name,
recurring_rule=recurring_rule_label,
invoicing_type=invoicing_type_label,
date_text=date_text,
)

return res
1 change: 1 addition & 0 deletions product_contract/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
- [Tecnativa](https://www.tecnativa.com):
- Ernesto Tejeda
- Pedro M. Baeza
- Carlos Roca
- David Jaen \<<[email protected]>\>
2 changes: 2 additions & 0 deletions product_contract/readme/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- There's no support right now for computing the start date for the
following recurrent types: daily, weekly and monthlylastday.
Loading

0 comments on commit 9e81292

Please sign in to comment.