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
1 change: 1 addition & 0 deletions subscription_oca/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from . import account_move
from . import account_move_line
from . import product_template
from . import res_partner
from . import sale_order
Expand Down
17 changes: 17 additions & 0 deletions subscription_oca/models/account_move_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2026 Domatix - Alvaro Domatix
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class AccountMoveLine(models.Model):
_inherit = "account.move.line"

subscription_id = fields.Many2one(
comodel_name="sale.subscription",
string="Subscription",
index=True,
ondelete="set null",
)
subscription_period_start = fields.Date(string="Subscription period start")
subscription_period_end = fields.Date(string="Subscription period end")
17 changes: 16 additions & 1 deletion subscription_oca/models/sale_subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,15 +302,30 @@ def _prepare_account_move(self, line_ids):
values["journal_id"] = self.journal_id.id
return values

def _get_invoice_period(self):
self.ensure_one()
period_start = self.recurring_next_date or fields.Date.today()
type_interval = self.template_id.recurring_rule_type
interval = int(self.template_id.recurring_interval or 0) or 1
period_end = (
period_start
+ relativedelta(**{type_interval: interval})
- relativedelta(days=1)
)
return period_start, period_end

def create_invoice(self):
if not self.env["account.move"].has_access("create"):
try:
self.check_access("write")
except AccessError:
return self.env["account.move"]
period_start, period_end = self._get_invoice_period()
line_ids = []
for line in self.sale_subscription_line_ids:
line_values = line._prepare_account_move_line()
line_values = line._prepare_account_move_line(
period_start=period_start, period_end=period_end
)
line_ids.append(Command.create(line_values))
invoice_values = self._prepare_account_move(line_ids)
invoice_id = (
Expand Down
17 changes: 14 additions & 3 deletions subscription_oca/models/sale_subscription_line.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2023 Domatix - Carlos Martínez
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import Command, api, fields, models
from odoo.tools.misc import get_lang
from odoo.tools.misc import format_date, get_lang


class SaleSubscriptionLine(models.Model):
Expand Down Expand Up @@ -298,15 +298,23 @@ def _prepare_sale_order_line(self):
"analytic_distribution": self.analytic_distribution,
}

def _prepare_account_move_line(self):
def _prepare_account_move_line(self, period_start=False, period_end=False):
self.ensure_one()
account = (
self.product_id.property_account_income_id
or self.product_id.categ_id.property_account_income_categ_id
)
name = self.name
if period_start and period_end:
lang_code = get_lang(
self.env, self.sale_subscription_id.partner_id.lang
).code
start_str = format_date(self.env, period_start, lang_code=lang_code)
end_str = format_date(self.env, period_end, lang_code=lang_code)
name = f"{name} ({start_str} - {end_str})"
return {
"product_id": self.product_id.id,
"name": self.name,
"name": name,
"quantity": self.product_uom_qty,
"price_unit": self.price_unit,
"discount": self.discount,
Expand All @@ -315,4 +323,7 @@ def _prepare_account_move_line(self):
"product_uom_id": self.product_id.uom_id.id,
"account_id": account.id,
"analytic_distribution": self.analytic_distribution,
"subscription_id": self.sale_subscription_id.id,
"subscription_period_start": period_start or False,
"subscription_period_end": period_end or False,
}
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_invoice_period
73 changes: 73 additions & 0 deletions subscription_oca/tests/test_subscription_invoice_period.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 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
from odoo.addons.product.tests.common import ProductCommon


class TestSubscriptionInvoicePeriod(ProductCommon, 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": "Invoice period partner"})
cls.pricelist = cls.env["product.pricelist"].create(
{"name": "Invoice period pricelist"}
)
cls.template_monthly = cls.env["sale.subscription.template"].create(
{
"name": "Monthly template",
"code": "PER-MTH",
"recurring_rule_type": "months",
"recurring_rule_boundary": "unlimited",
}
)
cls.product = cls._create_product(
name="Period product",
lst_price=100.0,
subscribable=True,
uom_id=cls.uom_unit.id,
)
cls.subscription = cls.env["sale.subscription"].create(
{
"partner_id": cls.partner.id,
"pricelist_id": cls.pricelist.id,
"template_id": cls.template_monthly.id,
"date_start": fields.Date.today(),
"recurring_next_date": fields.Date.today(),
}
)
cls.env["sale.subscription.line"].create(
{
"sale_subscription_id": cls.subscription.id,
"product_id": cls.product.id,
}
)

def test_get_invoice_period_returns_start_and_end(self):
start, end = self.subscription._get_invoice_period()
self.assertEqual(start, self.subscription.recurring_next_date)
self.assertEqual(
end,
start + relativedelta(months=1) - relativedelta(days=1),
)

def test_manual_invoice_tracks_subscription_period(self):
period_start, period_end = self.subscription._get_invoice_period()
invoice = self.subscription.create_invoice()
for line in invoice.invoice_line_ids:
self.assertEqual(line.subscription_id, self.subscription)
self.assertEqual(line.subscription_period_start, period_start)
self.assertEqual(line.subscription_period_end, period_end)

def test_invoice_line_description_contains_period_dates(self):
period_start, period_end = self.subscription._get_invoice_period()
invoice = self.subscription.create_invoice()
line = invoice.invoice_line_ids[:1]
self.assertIn(fields.Date.to_string(period_start)[:4], line.name)
self.assertIn(fields.Date.to_string(period_end)[:4], line.name)
self.assertIn(" - ", line.name)
Loading