mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
244 lines
8.9 KiB
Python
244 lines
8.9 KiB
Python
# Copyright 2018 ACSONE SA/NV.
|
|
# Copyright 2020 Tecnativa - Pedro M. Baeza
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import api, fields, models
|
|
|
|
|
|
class ContractRecurrencyBasicMixin(models.AbstractModel):
|
|
_name = "contract.recurrency.basic.mixin"
|
|
_description = "Basic recurrency mixin for abstract contract models"
|
|
|
|
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="Recurrence",
|
|
help="Specify Interval for automatic invoice generation.",
|
|
)
|
|
recurring_invoicing_type = fields.Selection(
|
|
[("pre-paid", "Pre-paid"), ("post-paid", "Post-paid")],
|
|
default="pre-paid",
|
|
string="Invoicing type",
|
|
help=(
|
|
"Specify if the invoice must be generated at the beginning "
|
|
"(pre-paid) or end (post-paid) of the period."
|
|
),
|
|
)
|
|
recurring_invoicing_offset = fields.Integer(
|
|
compute="_compute_recurring_invoicing_offset",
|
|
string="Invoicing offset",
|
|
help=(
|
|
"Number of days to offset the invoice from the period end "
|
|
"date (in post-paid mode) or start date (in pre-paid mode)."
|
|
),
|
|
)
|
|
recurring_interval = fields.Integer(
|
|
default=1,
|
|
string="Invoice Every",
|
|
help="Invoice every (Days/Week/Month/Year)",
|
|
)
|
|
date_start = fields.Date(string="Date Start")
|
|
recurring_next_date = fields.Date(string="Date of Next Invoice")
|
|
|
|
@api.depends("recurring_invoicing_type", "recurring_rule_type")
|
|
def _compute_recurring_invoicing_offset(self):
|
|
for rec in self:
|
|
method = self._get_default_recurring_invoicing_offset
|
|
rec.recurring_invoicing_offset = method(
|
|
rec.recurring_invoicing_type, rec.recurring_rule_type
|
|
)
|
|
|
|
@api.model
|
|
def _get_default_recurring_invoicing_offset(
|
|
self, recurring_invoicing_type, recurring_rule_type
|
|
):
|
|
if (
|
|
recurring_invoicing_type == "pre-paid"
|
|
or recurring_rule_type == "monthlylastday"
|
|
):
|
|
return 0
|
|
else:
|
|
return 1
|
|
|
|
|
|
class ContractRecurrencyMixin(models.AbstractModel):
|
|
_inherit = "contract.recurrency.basic.mixin"
|
|
_name = "contract.recurrency.mixin"
|
|
_description = "Recurrency mixin for contract models"
|
|
|
|
date_start = fields.Date(default=lambda self: fields.Date.context_today(self))
|
|
recurring_next_date = fields.Date(
|
|
compute="_compute_recurring_next_date", store=True, readonly=False, copy=True
|
|
)
|
|
date_end = fields.Date(string="Date End", index=True)
|
|
next_period_date_start = fields.Date(
|
|
string="Next Period Start",
|
|
compute="_compute_next_period_date_start",
|
|
)
|
|
next_period_date_end = fields.Date(
|
|
string="Next Period End",
|
|
compute="_compute_next_period_date_end",
|
|
)
|
|
last_date_invoiced = fields.Date(
|
|
string="Last Date Invoiced", readonly=True, copy=False
|
|
)
|
|
|
|
@api.depends("next_period_date_start")
|
|
def _compute_recurring_next_date(self):
|
|
records_with_date_start = self.filtered("next_period_date_start")
|
|
for rec in records_with_date_start:
|
|
rec.recurring_next_date = self.get_next_invoice_date(
|
|
rec.next_period_date_start,
|
|
rec.recurring_invoicing_type,
|
|
rec.recurring_invoicing_offset,
|
|
rec.recurring_rule_type,
|
|
rec.recurring_interval,
|
|
max_date_end=rec.date_end,
|
|
)
|
|
(self - records_with_date_start).update(
|
|
{
|
|
"recurring_next_date": False,
|
|
}
|
|
)
|
|
|
|
@api.depends("last_date_invoiced", "date_start", "date_end")
|
|
def _compute_next_period_date_start(self):
|
|
for rec in self:
|
|
if rec.last_date_invoiced:
|
|
next_period_date_start = rec.last_date_invoiced + relativedelta(days=1)
|
|
else:
|
|
next_period_date_start = rec.date_start
|
|
if rec.date_end and next_period_date_start > rec.date_end:
|
|
next_period_date_start = False
|
|
rec.next_period_date_start = next_period_date_start
|
|
|
|
@api.depends(
|
|
"next_period_date_start",
|
|
"recurring_invoicing_type",
|
|
"recurring_invoicing_offset",
|
|
"recurring_rule_type",
|
|
"recurring_interval",
|
|
"date_end",
|
|
"recurring_next_date",
|
|
)
|
|
def _compute_next_period_date_end(self):
|
|
for rec in self:
|
|
rec.next_period_date_end = self.get_next_period_date_end(
|
|
rec.next_period_date_start,
|
|
rec.recurring_rule_type,
|
|
rec.recurring_interval,
|
|
max_date_end=rec.date_end,
|
|
next_invoice_date=rec.recurring_next_date,
|
|
recurring_invoicing_type=rec.recurring_invoicing_type,
|
|
recurring_invoicing_offset=rec.recurring_invoicing_offset,
|
|
)
|
|
|
|
@api.model
|
|
def get_relative_delta(self, recurring_rule_type, interval):
|
|
"""Return a relativedelta for one period.
|
|
|
|
When added to the first day of the period,
|
|
it gives the first day of the next period.
|
|
"""
|
|
if recurring_rule_type == "daily":
|
|
return relativedelta(days=interval)
|
|
elif recurring_rule_type == "weekly":
|
|
return relativedelta(weeks=interval)
|
|
elif recurring_rule_type == "monthly":
|
|
return relativedelta(months=interval)
|
|
elif recurring_rule_type == "monthlylastday":
|
|
return relativedelta(months=interval, day=1)
|
|
elif recurring_rule_type == "quarterly":
|
|
return relativedelta(months=3 * interval)
|
|
elif recurring_rule_type == "semesterly":
|
|
return relativedelta(months=6 * interval)
|
|
else:
|
|
return relativedelta(years=interval)
|
|
|
|
@api.model
|
|
def get_next_period_date_end(
|
|
self,
|
|
next_period_date_start,
|
|
recurring_rule_type,
|
|
recurring_interval,
|
|
max_date_end,
|
|
next_invoice_date=False,
|
|
recurring_invoicing_type=False,
|
|
recurring_invoicing_offset=False,
|
|
):
|
|
"""Compute the end date for the next period.
|
|
|
|
The next period normally depends on recurrence options only.
|
|
It is however possible to provide it a next invoice date, in
|
|
which case this method can adjust the next period based on that
|
|
too. In that scenario it required the invoicing type and offset
|
|
arguments.
|
|
"""
|
|
if not next_period_date_start:
|
|
return False
|
|
if max_date_end and next_period_date_start > max_date_end:
|
|
# start is past max date end: there is no next period
|
|
return False
|
|
if not next_invoice_date:
|
|
# regular algorithm
|
|
next_period_date_end = (
|
|
next_period_date_start
|
|
+ self.get_relative_delta(recurring_rule_type, recurring_interval)
|
|
- relativedelta(days=1)
|
|
)
|
|
else:
|
|
# special algorithm when the next invoice date is forced
|
|
if recurring_invoicing_type == "pre-paid":
|
|
next_period_date_end = (
|
|
next_invoice_date
|
|
- relativedelta(days=recurring_invoicing_offset)
|
|
+ self.get_relative_delta(recurring_rule_type, recurring_interval)
|
|
- relativedelta(days=1)
|
|
)
|
|
else: # post-paid
|
|
next_period_date_end = next_invoice_date - relativedelta(
|
|
days=recurring_invoicing_offset
|
|
)
|
|
if max_date_end and next_period_date_end > max_date_end:
|
|
# end date is past max_date_end: trim it
|
|
next_period_date_end = max_date_end
|
|
return next_period_date_end
|
|
|
|
@api.model
|
|
def get_next_invoice_date(
|
|
self,
|
|
next_period_date_start,
|
|
recurring_invoicing_type,
|
|
recurring_invoicing_offset,
|
|
recurring_rule_type,
|
|
recurring_interval,
|
|
max_date_end,
|
|
):
|
|
next_period_date_end = self.get_next_period_date_end(
|
|
next_period_date_start,
|
|
recurring_rule_type,
|
|
recurring_interval,
|
|
max_date_end=max_date_end,
|
|
)
|
|
if not next_period_date_end:
|
|
return False
|
|
if recurring_invoicing_type == "pre-paid":
|
|
recurring_next_date = next_period_date_start + relativedelta(
|
|
days=recurring_invoicing_offset
|
|
)
|
|
else: # post-paid
|
|
recurring_next_date = next_period_date_end + relativedelta(
|
|
days=recurring_invoicing_offset
|
|
)
|
|
return recurring_next_date
|