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 Aug 30, 2024
1 parent 88d2cb9 commit 914dbd2
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 29 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 <david.jaen.revert@gmail.com>

Expand Down
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
76 changes: 66 additions & 10 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 Down Expand Up @@ -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):
Expand Down Expand Up @@ -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")
Expand All @@ -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:
Expand Down Expand Up @@ -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]

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

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L295-L298

Added lines #L295 - L298 were not covered by tests
# The period number is started by 0 to be able to calculate the month
period_number = (month - 1) // month_nb

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

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L300

Added line #L300 was not covered by tests
if line.recurring_rule_type == "yearly":
month_period = 1

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

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L302

Added line #L302 was not covered by tests
elif line.recurring_rule_type != "monthly":
# Checking quarterly and semesterly
month_period = period_number * month_nb + 1
forced_month = 0

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

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L305-L306

Added lines #L305 - L306 were not covered by tests
if line.recurring_rule_type != "monthly":
forced_value = int(

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

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L308

Added line #L308 was not covered by tests
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

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

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L315

Added line #L315 was not covered by tests
# If forced_month is set, use it, but if it isn't use the month_period
start_date = today + relativedelta(

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

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

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L321-L322

Added lines #L321 - L322 were not covered by tests
if "_next" in line.contract_start_date_method and start_date <= today:
start_date = start_date + relativedelta(months=month_nb)

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

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L324

Added line #L324 was not covered by tests
if is_end:
start_date = start_date + relativedelta(day=1, months=1, days=-1)
line.date_start = start_date

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

View check run for this annotation

Codecov / codecov/patch

product_contract/models/sale_order_line.py#L326-L327

Added lines #L326 - L327 were not covered by tests
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 \<<david.jaen.revert@gmail.com>\>
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.
29 changes: 19 additions & 10 deletions product_contract/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,12 @@ <h1 class="title">Recurring - Product Contract</h1>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
Expand All @@ -399,38 +400,46 @@ <h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<li>Define default recurrence rules</li>
</ol>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>There’s no support right now for computing the start date for the
following recurrent types: daily, weekly and monthlylastday.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/contract/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/contract/issues/new?body=module:%20product_contract%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>LasLabs</li>
<li>ACSONE SA/NV</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<ul class="simple">
<li>Ted Salmon &lt;<a class="reference external" href="mailto:tsalmon&#64;laslabs.com">tsalmon&#64;laslabs.com</a>&gt;</li>
<li>Souheil Bejaoui &lt;<a class="reference external" href="mailto:souheil.bejaoui&#64;acsone.eu">souheil.bejaoui&#64;acsone.eu</a>&gt;</li>
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>Ernesto Tejeda</li>
<li>Pedro M. Baeza</li>
<li>Carlos Roca</li>
</ul>
</li>
<li>David Jaen &lt;<a class="reference external" href="mailto:david.jaen.revert&#64;gmail.com">david.jaen.revert&#64;gmail.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand Down
13 changes: 13 additions & 0 deletions product_contract/views/product_template.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@
</group>
<group>
<field name="is_auto_renew" />
<field name="contract_start_date_method" required="True" />
<field
name="force_month_yearly"
invisible="contract_start_date_method == 'manual' or recurring_rule_type != 'yearly'"
/>
<field
name="force_month_quarterly"
invisible="contract_start_date_method == 'manual' or recurring_rule_type != 'quarterly'"
/>
<field
name="force_month_semesterly"
invisible="contract_start_date_method == 'manual' or recurring_rule_type != 'semesterly'"
/>
</group>
<group>
<group invisible="is_auto_renew == False">
Expand Down
12 changes: 8 additions & 4 deletions product_contract/views/sale_order.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
<field name="date_start" required="is_contract" />
</group>
<group invisible="not is_contract">
<field name="date_end" required="is_contract" />
<field name="date_end" />
</group>
<group invisible="not is_contract">
<field name="is_auto_renew" />
Expand Down Expand Up @@ -114,8 +114,13 @@
/>
<field name="recurring_rule_type" optional="hide" />
<field name="recurring_invoicing_type" optional="hide" />
<field name="date_start" optional="hide" required="is_contract" />
<field name="date_end" required="is_contract" optional="hide" />
<field name="contract_start_date_method" column_invisible="1" />
<field
name="date_start"
optional="hide"
required="is_contract and contract_start_date_method == 'manual'"
/>
<field name="date_end" optional="hide" />
<field name="is_auto_renew" optional="hide" />
<field
name="auto_renew_interval"
Expand All @@ -131,7 +136,6 @@
required="is_auto_renew"
optional="hide"
/>
<field name="date_end" column_invisible="parent.is_contract == False" />
</xpath>
</field>
</record>
Expand Down
3 changes: 3 additions & 0 deletions product_contract/wizards/product_contract_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
21 changes: 16 additions & 5 deletions product_contract/wizards/product_contract_configurator_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,25 @@
<field name="product_uom_qty" />
</group>
<separator colspan="4" string="Recurrence Invoicing" />
<group>
<field name="recurring_rule_type" />
<field name="date_start" required="1" />
<field name="is_auto_renew" />
<group invisible="contract_start_date_method != 'manual'">
<field name="contract_start_date_method" invisible="1" />
<field
name="recurring_rule_type"
invisible="contract_start_date_method != 'manual'"
/>
<field
name="date_start"
required="contract_start_date_method == 'manual'"
invisible="contract_start_date_method != 'manual'"
/>
<field name="is_auto_renew" invisible="not date_end" />
</group>
<group>
<field name="recurring_invoicing_type" />
<field name="date_end" required="1" />
<field
name="date_end"
invisible="contract_start_date_method != 'manual'"
/>
<label
for="auto_renew_interval"
invisible="not is_auto_renew"
Expand Down

0 comments on commit 914dbd2

Please sign in to comment.