Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions subscription_oca/models/sale_subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,21 +215,46 @@ def _onchange_template_id(self):
else:
self.calculate_recurring_next_date(today)

def _get_recurrence_delta(self):
self.ensure_one()
type_interval = self.template_id.recurring_rule_type
interval = int(self.template_id.recurring_interval or 0) or 1
return relativedelta(**{type_interval: interval})

def _get_first_invoice_date(self):
self.ensure_one()
return self.date_start or fields.Date.today()

def _get_next_invoice_date(self, previous_date):
self.ensure_one()
if isinstance(previous_date, datetime):
previous_date = previous_date.date()
elif not isinstance(previous_date, date):
previous_date = fields.Date.to_date(previous_date)
return previous_date + self._get_recurrence_delta()

def _set_next_invoice_date_after_invoice(self, invoice_date=None):
self.ensure_one()
previous_date = invoice_date or self.recurring_next_date
self.recurring_next_date = self._get_next_invoice_date(previous_date)

def _get_contract_end_date(self):
self.ensure_one()
if self.template_id.recurring_rule_boundary == "unlimited":
return False
return self.template_id._get_date(self._get_first_invoice_date())

def calculate_recurring_next_date(self, start_date):
if self.account_invoice_ids_count == 0:
if not start_date:
start_date = self.date_start or date.today()
start_date = self._get_first_invoice_date()
if isinstance(start_date, datetime):
start_date = start_date.date()
elif not isinstance(start_date, date):
start_date = fields.Date.to_date(start_date)
self.recurring_next_date = start_date
else:
type_interval = self.template_id.recurring_rule_type
interval = int(self.template_id.recurring_interval)
self.recurring_next_date = start_date + relativedelta(
**{type_interval: interval}
)
self.recurring_next_date = self._get_next_invoice_date(start_date)

@api.onchange("partner_id")
def onchange_partner_id(self):
Expand Down Expand Up @@ -371,12 +396,12 @@ def generate_invoice(self):
if not invoice_number:
invoice_number = self.env._("To validate")
message_body = f"<b>{msg_static}</b> {invoice_number}"
self.calculate_recurring_next_date(self.recurring_next_date)
self._set_next_invoice_date_after_invoice()
self.message_post(body=Markup(message_body))

def manual_invoice(self):
invoice_id = self.create_invoice()
self.calculate_recurring_next_date(self.recurring_next_date)
self._set_next_invoice_date_after_invoice()
context = dict(self.env.context)
context["form_view_initial_mode"] = "edit"
return {
Expand Down
1 change: 1 addition & 0 deletions subscription_oca/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import test_subscription_oca
from . import test_subscription_recurrence_dates
94 changes: 94 additions & 0 deletions subscription_oca/tests/test_subscription_recurrence_dates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright 2026 Domatix - Alvaro Domatix
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from dateutil.relativedelta import relativedelta

from odoo import fields

from odoo.addons.base.tests.common import BaseCommon


class TestSubscriptionRecurrenceDates(BaseCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.partner = cls.env["res.partner"].create({"name": "Recurrence partner"})
cls.pricelist = cls.env["product.pricelist"].create(
{"name": "Recurrence pricelist"}
)

def _make_template(self, rule_type, boundary="unlimited", count=1, interval=1):
return self.env["sale.subscription.template"].create(
{
"name": f"Tmpl {rule_type} {boundary}",
"code": f"REC-{rule_type}-{boundary}",
"recurring_rule_type": rule_type,
"recurring_rule_boundary": boundary,
"recurring_rule_count": count,
"recurring_interval": interval,
}
)

def _make_subscription(self, template, date_start=None):
return self.env["sale.subscription"].create(
{
"partner_id": self.partner.id,
"pricelist_id": self.pricelist.id,
"template_id": template.id,
"date_start": date_start or fields.Date.today(),
}
)

def test_recurrence_delta_daily(self):
sub = self._make_subscription(self._make_template("days"))
self.assertEqual(sub._get_recurrence_delta(), relativedelta(days=1))

def test_recurrence_delta_weekly(self):
sub = self._make_subscription(self._make_template("weeks"))
self.assertEqual(sub._get_recurrence_delta(), relativedelta(weeks=1))

def test_recurrence_delta_monthly(self):
sub = self._make_subscription(self._make_template("months"))
self.assertEqual(sub._get_recurrence_delta(), relativedelta(months=1))

def test_recurrence_delta_yearly(self):
sub = self._make_subscription(self._make_template("years"))
self.assertEqual(sub._get_recurrence_delta(), relativedelta(years=1))

def test_recurrence_delta_respects_interval(self):
sub = self._make_subscription(self._make_template("months", interval=3))
self.assertEqual(sub._get_recurrence_delta(), relativedelta(months=3))

def test_first_invoice_date_uses_date_start(self):
start = fields.Date.today() + relativedelta(days=10)
sub = self._make_subscription(self._make_template("months"), date_start=start)
self.assertEqual(sub._get_first_invoice_date(), start)

def test_next_invoice_date_advances_by_recurrence(self):
sub = self._make_subscription(self._make_template("months"))
base = fields.Date.today()
self.assertEqual(
sub._get_next_invoice_date(base),
base + relativedelta(months=1),
)

def test_set_next_invoice_date_after_invoice_advances(self):
sub = self._make_subscription(self._make_template("weeks"))
sub.recurring_next_date = fields.Date.today()
previous = sub.recurring_next_date
sub._set_next_invoice_date_after_invoice()
self.assertEqual(sub.recurring_next_date, previous + relativedelta(weeks=1))

def test_contract_end_date_unlimited_is_false(self):
sub = self._make_subscription(self._make_template("months", "unlimited"))
self.assertFalse(sub._get_contract_end_date())

def test_contract_end_date_limited_uses_template(self):
template = self._make_template("months", "limited", count=6)
start = fields.Date.today()
sub = self._make_subscription(template, date_start=start)
self.assertEqual(
sub._get_contract_end_date(),
start + relativedelta(months=6),
)
Loading