Skip to content

Commit

Permalink
[IMP] product_contract: add recurrence interval
Browse files Browse the repository at this point in the history
  • Loading branch information
sbejaoui committed Nov 29, 2024
1 parent 7dd87ac commit 04a57be
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 39 deletions.
10 changes: 10 additions & 0 deletions product_contract/models/product_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ class ProductTemplate(models.Model):
company_dependent=True,
)
default_qty = fields.Integer(string="Recurrence Number", default=1)
recurrence_interval = fields.Selection(
[
("monthly", "Month(s)"),
("quarterly", "Quarter(s)"),
("semesterly", "Semester(s)"),
("yearly", "Year(s)"),
],
default="monthly",
help="Specify Interval for contract duration.",
)
recurring_rule_type = fields.Selection(
[
("daily", "Day(s)"),
Expand Down
26 changes: 12 additions & 14 deletions product_contract/models/sale_order_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,26 +163,23 @@ def _compute_qty_to_invoice(self):
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"]
):
if line.contract_start_date_method == "manual":
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]
month_nb = MONTH_NB_MAPPING[line.recurrence_interval]
# 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":
if line.recurrence_interval == "yearly":
month_period = 1
elif line.recurring_rule_type != "monthly":
elif line.recurrence_interval != "monthly":
# Checking quarterly and semesterly
month_period = period_number * month_nb + 1
forced_month = 0
if line.recurring_rule_type != "monthly":
if line.recurrence_interval != "monthly":
forced_value = int(
line.product_id[f"force_month_{line.recurring_rule_type}"]
line.product_id[f"force_month_{line.recurrence_interval}"]
)
if forced_value:
# When the selected period is yearly, the period_number field is
Expand All @@ -208,6 +205,7 @@ def _set_contract_line_start_date(self):
"date_start",
"date_end",
"recurring_rule_type",
"recurrence_interval",
"recurring_invoicing_type",
)
def _compute_name(self):
Expand All @@ -230,21 +228,21 @@ def _compute_name(self):
)
date_text = f"{start_method_label}"
if (
line.recurring_rule_type != "monthly"
and line.product_id[f"force_month_{line.recurring_rule_type}"]
line.recurrence_interval != "monthly"
and line.product_id[f"force_month_{line.recurrence_interval}"]
):
field_info = dict(
self.env["product.template"]
._fields[f"force_month_{line.recurring_rule_type}"]
._fields[f"force_month_{line.recurrence_interval}"]
.get_description(self.env)
)
field_selection = dict(field_info.get("selection"))
force_month_label = field_selection.get(
line.product_id[f"force_month_{line.recurring_rule_type}"]
line.product_id[f"force_month_{line.recurrence_interval}"]
)
date_text += f" ({force_month_label})"
field_info = dict(
self._fields["recurring_rule_type"].get_description(self.env)
self._fields["recurrence_interval"].get_description(self.env)
)
field_selection = dict(field_info.get("selection"))
recurring_rule_label = field_selection.get(line.recurring_rule_type)
Expand Down
29 changes: 19 additions & 10 deletions product_contract/models/sale_order_line_contract_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ class SaleOrderLineContractMixin(models.AbstractModel):
store=True,
readonly=False,
)
recurrence_interval = fields.Selection(
[
("monthly", "Month(s)"),
("quarterly", "Quarter(s)"),
("semesterly", "Semester(s)"),
("yearly", "Year(s)"),
],
default="monthly",
help="Specify Interval for contract duration.",
compute="_compute_product_contract_data",
precompute=True,
store=True,
readonly=False,
)
recurring_rule_type = fields.Selection(
[
("daily", "Day(s)"),
Expand All @@ -40,7 +54,7 @@ class SaleOrderLineContractMixin(models.AbstractModel):
("yearly", "Year(s)"),
],
default="monthly",
string="Recurrence",
string="Invoice Every",
help="Specify Interval for automatic invoice generation.",
compute="_compute_product_contract_data",
precompute=True,
Expand Down Expand Up @@ -157,6 +171,7 @@ def _compute_product_contract_data(self):
"recurrence_number": 0,
"recurring_rule_type": False,
"recurring_invoicing_type": False,
"recurrence_interval": False,
"is_auto_renew": False,
"auto_renew_interval": False,
"auto_renew_rule_type": False,
Expand All @@ -168,6 +183,7 @@ def _compute_product_contract_data(self):
"recurrence_number": p.default_qty,
"recurring_rule_type": p.recurring_rule_type,
"recurring_invoicing_type": p.recurring_invoicing_type,
"recurrence_interval": p.recurrence_interval,
"is_auto_renew": p.is_auto_renew,
"auto_renew_interval": p.auto_renew_interval,
"auto_renew_rule_type": p.auto_renew_rule_type,
Expand All @@ -181,25 +197,18 @@ def _compute_contract_line_date_start(self):
if rec.contract_start_date_method == "manual":
rec.date_start = rec.date_start or fields.Date.today()

@api.depends("date_start", "recurring_rule_type", "recurrence_number")
@api.depends("date_start", "recurrence_interval", "recurrence_number")
def _compute_contract_line_date_end(self):
for rec in self:
rec.date_end = rec._get_date_end() if rec.date_start else False

def _get_auto_renew_rule_type(self):
"""monthly last day don't make sense for auto_renew_rule_type"""
self.ensure_one()
if self.recurring_rule_type == "monthlylastday":
return "monthly"
return self.recurring_rule_type

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(), self.recurrence_number
self.recurrence_interval, self.recurrence_number
)
- relativedelta(days=1)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class ProductContractConfiguratorController extends formView.Controller {
product_uom_qty,
recurrence_number,
recurring_rule_type,
recurrence_interval,
contract_id,
date_start,
date_end,
Expand All @@ -31,6 +32,7 @@ export class ProductContractConfiguratorController extends formView.Controller {
product_uom_qty,
recurrence_number,
recurring_rule_type,
recurrence_interval,
contract_id,
date_start,
date_end,
Expand Down
1 change: 1 addition & 0 deletions product_contract/static/src/js/sale_product_field.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ patch(SaleOrderLineProductField.prototype, {
default_company_id: this.props.record.model.root.data.company_id[0],
default_recurrence_number: this.props.record.data.recurrence_number,
default_recurring_rule_type: this.props.record.data.recurring_rule_type,
default_recurrence_interval: this.props.record.data.recurrence_interval,
default_product_uom_qty: this.props.record.data.product_uom_qty,
default_contract_id: this.props.record.data.contract_id[0],
default_date_start: this.props.record.data.date_start,
Expand Down
26 changes: 11 additions & 15 deletions product_contract/tests/test_sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def setUpClass(cls):
}
)
cls.contract_line = cls.contract.contract_line_ids[0]
cls.sale.order_line._compute_product_contract_data()

def test_compute_is_contract(self):
"""Sale Order should have is_contract true if one of its lines is
Expand All @@ -109,7 +110,6 @@ def test_compute_is_contract(self):
def test_action_confirm(self):
"""It should create a contract for each contract template used in
order_line"""
self.order_line1._compute_auto_renew()
self.sale.action_confirm()
contracts = self.sale.order_line.mapped("contract_id")
self.assertEqual(len(contracts), 2)
Expand Down Expand Up @@ -155,7 +155,6 @@ def test_action_confirm_without_contract_creation(self):
"""It should create a contract for each contract template used in
order_line"""
self.sale.company_id.create_contract_at_sale_order_confirmation = False
self.order_line1._compute_auto_renew()
self.sale.action_confirm()
self.assertEqual(len(self.sale.order_line.mapped("contract_id")), 0)
self.assertTrue(self.sale.need_contract_creation)
Expand All @@ -174,14 +173,12 @@ def test_action_confirm_without_contract_creation(self):
def test_sale_contract_count(self):
"""It should count contracts as many different contract template used
in order_line"""
self.order_line1._compute_auto_renew()
self.sale.action_confirm()
self.assertEqual(self.sale.contract_count, 2)

def test_onchange_product(self):
"""It should get recurrence invoicing info to the sale line from
its product"""
self.order_line1._compute_auto_renew()
self.assertEqual(
self.order_line1.recurring_rule_type,
self.product1.recurring_rule_type,
Expand Down Expand Up @@ -229,7 +226,6 @@ def test_no_contract_proudct(self):
def test_sale_order_line_invoice_status(self):
"""Sale order line for contract product should have nothing to
invoice as status"""
self.order_line1._compute_auto_renew()
self.sale.action_confirm()
self.assertEqual(self.order_line1.invoice_status, "no")

Expand All @@ -239,15 +235,15 @@ def test_sale_order_invoice_status_2(self):
self.sale.order_line.filtered(
lambda line: not line.product_id.is_contract
).unlink()
self.order_line1._compute_auto_renew()

self.sale.action_confirm()
self.assertEqual(self.sale.invoice_status, "no")

def test_sale_order_create_invoice(self):
"""Should not invoice contract product on sale order create invoice"""
self.product2.is_contract = False
self.product2.invoice_policy = "order"
self.order_line1._compute_auto_renew()

self.sale.action_confirm()
self.sale._create_invoices()
self.assertEqual(len(self.sale.invoice_ids), 1)
Expand All @@ -258,7 +254,7 @@ def test_sale_order_create_invoice(self):

def test_link_contract_invoice_to_sale_order(self):
"""It should link contract invoice to sale order"""
self.order_line1._compute_auto_renew()

self.sale.action_confirm()
invoice = self.order_line1.contract_id.recurring_create_invoice()
self.assertTrue(invoice in self.sale.invoice_ids)
Expand All @@ -270,7 +266,7 @@ def test_contract_upsell(self):
self.contract_line.date_end = Date.today() + relativedelta(months=4)
self.contract_line.is_auto_renew = True
self.order_line1.date_start = "2018-06-01"
self.order_line1._compute_auto_renew()

self.sale.action_confirm()
self.assertEqual(self.contract_line.date_end, Date.to_date("2018-05-31"))
self.assertFalse(self.contract_line.is_auto_renew)
Expand All @@ -296,7 +292,7 @@ def test_contract_upsell_2(self):
}
)
self.order_line1.date_start = "2018-06-01"
self.order_line1._compute_auto_renew()

self.sale.action_confirm()
self.assertFalse(self.contract_line.date_end)
self.assertTrue(self.contract_line.is_canceled)
Expand Down Expand Up @@ -362,7 +358,6 @@ def test_order_lines_with_the_same_contract_template(self):
"property_contract_template_id": self.contract_template1.id,
}
)
self.sale.order_line._compute_auto_renew()
self.sale.action_confirm()
contracts = self.sale.order_line.mapped("contract_id")
self.assertEqual(len(contracts), 1)
Expand All @@ -375,20 +370,20 @@ def test_order_lines_with_the_same_contract_template(self):
self.assertEqual(len(contracts), 1)

def _create_contract_product(
self, recurring_rule_type, contract_start_date_method, force_month=False
self, recurrence_interval, contract_start_date_method, force_month=False
):
product = self.env["product.product"].create(
{
"name": "Contract Test",
"type": "service",
"is_contract": True,
"recurring_rule_type": recurring_rule_type,
"recurrence_interval": recurrence_interval,
"contract_start_date_method": contract_start_date_method,
"property_contract_template_id": self.contract_template1.id,
}
)
if recurring_rule_type != "monthly":
product[f"force_month_{recurring_rule_type}"] = force_month
if recurrence_interval != "monthly":
product[f"force_month_{recurrence_interval}"] = force_month
return product

def _create_and_confirm_sale(self, product):
Expand All @@ -406,6 +401,7 @@ def _create_and_confirm_sale(self, product):
],
}
)
sale.order_line._compute_product_contract_data()
sale.action_confirm()
return sale

Expand Down
1 change: 1 addition & 0 deletions product_contract/views/product_template.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
</group>
<group name="recurrence_info">
<group>
<field name="recurrence_interval" />
<field name="recurring_rule_type" />
</group>
<group>
Expand Down
2 changes: 2 additions & 0 deletions product_contract/views/sale_order.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
/>
<group invisible="not is_contract">
<field name="recurrence_number" />
<field name="recurrence_interval" />
<field name="recurring_rule_type" />
</group>
<group invisible="not is_contract">
Expand Down Expand Up @@ -114,6 +115,7 @@
optional="hide"
/>
<field name="recurrence_number" optional="hide" />
<field name="recurrence_interval" optional="hide" />
<field name="recurring_rule_type" optional="hide" />
<field name="recurring_invoicing_type" optional="hide" />
<field name="contract_start_date_method" column_invisible="1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<field name="is_auto_renew" invisible="not date_end" />
</group>
<group>
<field name="recurrence_interval" />
<field name="recurring_rule_type" />
<field name="recurring_invoicing_type" />
<field
Expand Down

0 comments on commit 04a57be

Please sign in to comment.