mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[12.0][IMP] - Add strat/stop wizard to contract line
[12.0][IMP] - Add pause button to contract line [IMP] - Add state filed in contract line form [FIX] - stop don't change date_end for finished contract line [IMP] - Change contract line buttons visibility Add renewal process with termination notice [FIX] - don't consider stop_date If it is after the contract line end_date [IMP] - consider more cases in stop_plan_successor [IMP] - cancel upcoming line on stop [IMP] - Chnage next invoice date on un-cancel [IMP] - Post message in contract on contract line actions [IMP] - check contract line overlap
This commit is contained in:
@@ -86,8 +86,28 @@ class AccountAbstractAnalyticContractLine(models.AbstractModel):
|
||||
pricelist_id = fields.Many2one(
|
||||
comodel_name='product.pricelist', string='Pricelist'
|
||||
)
|
||||
recurring_next_date = fields.Date(
|
||||
copy=False, string='Date of Next Invoice'
|
||||
recurring_next_date = fields.Date(string='Date of Next Invoice')
|
||||
|
||||
is_canceled = fields.Boolean(string="Canceled", default=False)
|
||||
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
|
||||
auto_renew_interval = fields.Integer(
|
||||
default=1,
|
||||
string='Renew Every',
|
||||
help="Renew every (Days/Week/Month/Year)",
|
||||
)
|
||||
auto_renew_rule_type = fields.Selection(
|
||||
[('monthly', 'Month(s)'), ('yearly', 'Year(s)')],
|
||||
default='yearly',
|
||||
string='Renewal type',
|
||||
help="Specify Interval for automatic renewal.",
|
||||
)
|
||||
termination_notice_interval = fields.Integer(
|
||||
default=1, string='Termination Notice Before'
|
||||
)
|
||||
termination_notice_rule_type = fields.Selection(
|
||||
[('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)')],
|
||||
default='monthly',
|
||||
string='Termination Notice type',
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
|
||||
@@ -6,6 +6,8 @@ from dateutil.relativedelta import relativedelta
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from ..data.contract_line_constraints import get_allowed
|
||||
|
||||
|
||||
class AccountAnalyticInvoiceLine(models.Model):
|
||||
_name = 'account.analytic.invoice.line'
|
||||
@@ -20,9 +22,7 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
)
|
||||
date_start = fields.Date(string='Date Start', default=fields.Date.today())
|
||||
date_end = fields.Date(string='Date End', index=True)
|
||||
recurring_next_date = fields.Date(
|
||||
copy=False, string='Date of Next Invoice'
|
||||
)
|
||||
recurring_next_date = fields.Date(string='Date of Next Invoice')
|
||||
create_invoice_visibility = fields.Boolean(
|
||||
compute='_compute_create_invoice_visibility'
|
||||
)
|
||||
@@ -40,6 +40,139 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
successor_contract_line_id = fields.Many2one(
|
||||
comodel_name='account.analytic.invoice.line',
|
||||
string="Successor Contract Line",
|
||||
required=False,
|
||||
readonly=True,
|
||||
copy=False,
|
||||
help="Contract Line created by this one.",
|
||||
)
|
||||
predecessor_contract_line_id = fields.Many2one(
|
||||
comodel_name='account.analytic.invoice.line',
|
||||
string="Predecessor Contract Line",
|
||||
required=False,
|
||||
readonly=True,
|
||||
copy=False,
|
||||
help="Contract Line origin of this one.",
|
||||
)
|
||||
is_plan_successor_allowed = fields.Boolean(
|
||||
string="Plan successor allowed?", compute='_compute_allowed'
|
||||
)
|
||||
is_stop_plan_successor_allowed = fields.Boolean(
|
||||
string="Stop/Plan successor allowed?", compute='_compute_allowed'
|
||||
)
|
||||
is_stop_allowed = fields.Boolean(
|
||||
string="Stop allowed?", compute='_compute_allowed'
|
||||
)
|
||||
is_cancel_allowed = fields.Boolean(
|
||||
string="Cancel allowed?", compute='_compute_allowed'
|
||||
)
|
||||
is_un_cancel_allowed = fields.Boolean(
|
||||
string="Un-Cancel allowed?", compute='_compute_allowed'
|
||||
)
|
||||
state = fields.Selection(
|
||||
string="State",
|
||||
selection=[
|
||||
('upcoming', 'Upcoming'),
|
||||
('in-progress', 'In-progress'),
|
||||
('upcoming-close', 'Upcoming Close'),
|
||||
('closed', 'Closed'),
|
||||
('canceled', 'Canceled'),
|
||||
],
|
||||
compute="_compute_state",
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def _compute_state(self):
|
||||
today = fields.Date.today()
|
||||
for rec in self:
|
||||
if rec.is_canceled:
|
||||
rec.state = 'canceled'
|
||||
elif today < rec.date_start:
|
||||
rec.state = 'upcoming'
|
||||
elif not rec.date_end or (
|
||||
today <= rec.date_end and rec.is_auto_renew
|
||||
):
|
||||
rec.state = 'in-progress'
|
||||
elif today <= rec.date_end and not rec.is_auto_renew:
|
||||
rec.state = 'upcoming-close'
|
||||
else:
|
||||
rec.state = 'closed'
|
||||
|
||||
@api.depends(
|
||||
'date_start',
|
||||
'date_end',
|
||||
'is_auto_renew',
|
||||
'successor_contract_line_id',
|
||||
'is_canceled',
|
||||
)
|
||||
def _compute_allowed(self):
|
||||
for rec in self:
|
||||
allowed = get_allowed(
|
||||
rec.date_start,
|
||||
rec.date_end,
|
||||
rec.is_auto_renew,
|
||||
rec.successor_contract_line_id,
|
||||
rec.is_canceled,
|
||||
)
|
||||
if allowed:
|
||||
rec.is_plan_successor_allowed = allowed.PLAN_SUCCESSOR
|
||||
rec.is_stop_plan_successor_allowed = (
|
||||
allowed.STOP_PLAN_SUCCESSOR
|
||||
)
|
||||
rec.is_stop_allowed = allowed.STOP
|
||||
rec.is_cancel_allowed = allowed.CANCEL
|
||||
rec.is_un_cancel_allowed = allowed.UN_CANCEL
|
||||
|
||||
@api.constrains('is_auto_renew', 'successor_contract_line_id', 'date_end')
|
||||
def _check_allowed(self):
|
||||
"""
|
||||
logical impossible combination:
|
||||
* a line with is_auto_renew True should have date_end and
|
||||
couldn't have successor_contract_line_id
|
||||
* a line without date_end can't have successor_contract_line_id
|
||||
|
||||
"""
|
||||
for rec in self:
|
||||
if rec.is_auto_renew:
|
||||
if rec.successor_contract_line_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"A contract line with a successor "
|
||||
"can't be set to auto-renew"
|
||||
)
|
||||
)
|
||||
if not rec.date_end:
|
||||
raise ValidationError(
|
||||
_("An auto-renew line should have a " "date end ")
|
||||
)
|
||||
else:
|
||||
if not rec.date_end and rec.successor_contract_line_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"A contract line with a successor "
|
||||
"should have date end"
|
||||
)
|
||||
)
|
||||
|
||||
@api.constrains('successor_contract_line_id', 'date_end')
|
||||
def _check_overlap_successor(self):
|
||||
for rec in self:
|
||||
if rec.date_end and rec.successor_contract_line_id:
|
||||
if rec.date_end > rec.successor_contract_line_id.date_start:
|
||||
raise ValidationError(
|
||||
_("Contract line and its successor overlapped")
|
||||
)
|
||||
|
||||
@api.constrains('predecessor_contract_line_id', 'date_start')
|
||||
def _check_overlap_predecessor(self):
|
||||
for rec in self:
|
||||
if rec.predecessor_contract_line_id:
|
||||
if rec.date_start < rec.predecessor_contract_line_id.date_end:
|
||||
raise ValidationError(
|
||||
_("Contract line and its predecessor overlapped")
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _compute_first_recurring_next_date(
|
||||
@@ -59,6 +192,17 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
recurring_rule_type, recurring_interval
|
||||
)
|
||||
|
||||
@api.onchange(
|
||||
'is_auto_renew', 'auto_renew_rule_type', 'auto_renew_interval'
|
||||
)
|
||||
def _onchange_is_auto_renew(self):
|
||||
"""Date end should be auto-computed if a contract line is set to
|
||||
auto_renew"""
|
||||
for rec in self.filtered('is_auto_renew'):
|
||||
rec.date_end = self.date_start + self.get_relative_delta(
|
||||
rec.auto_renew_rule_type, rec.auto_renew_interval
|
||||
)
|
||||
|
||||
@api.onchange(
|
||||
'date_start',
|
||||
'recurring_invoicing_type',
|
||||
@@ -143,6 +287,7 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
[
|
||||
('contract_id.recurring_invoices', '=', True),
|
||||
('recurring_next_date', '<=', date_ref),
|
||||
('is_canceled', '=', False),
|
||||
'|',
|
||||
('date_end', '=', False),
|
||||
('date_end', '>=', date_ref),
|
||||
@@ -248,3 +393,386 @@ class AccountAnalyticInvoiceLine(models.Model):
|
||||
return relativedelta(months=interval, day=31)
|
||||
else:
|
||||
return relativedelta(years=interval)
|
||||
|
||||
@api.multi
|
||||
def delay(self, delay_delta):
|
||||
"""
|
||||
Delay a contract line
|
||||
:param delay_delta: delay relative delta
|
||||
:return: delayed contract line
|
||||
"""
|
||||
for rec in self:
|
||||
old_date_start = rec.date_start
|
||||
old_date_end = rec.date_end
|
||||
new_date_start = rec.date_start + delay_delta
|
||||
rec.recurring_next_date = self._compute_first_recurring_next_date(
|
||||
new_date_start,
|
||||
rec.recurring_invoicing_type,
|
||||
rec.recurring_rule_type,
|
||||
rec.recurring_interval,
|
||||
)
|
||||
rec.date_end = (
|
||||
rec.date_end
|
||||
if not rec.date_end
|
||||
else rec.date_end + delay_delta
|
||||
)
|
||||
rec.date_start = new_date_start
|
||||
msg = _(
|
||||
"""Contract line for <strong>{product}</strong>
|
||||
delayed: <br/>
|
||||
- <strong>Start</strong>: {old_date_start} -- {new_date_start}
|
||||
<br/>
|
||||
- <strong>End</strong>: {old_date_end} -- {new_date_end}
|
||||
""".format(
|
||||
product=rec.name,
|
||||
old_date_start=old_date_start,
|
||||
new_date_start=rec.date_start,
|
||||
old_date_end=old_date_end,
|
||||
new_date_end=rec.date_end,
|
||||
)
|
||||
)
|
||||
rec.contract_id.message_post(body=msg)
|
||||
|
||||
@api.multi
|
||||
def stop(self, date_end):
|
||||
"""
|
||||
Put date_end on contract line
|
||||
We don't consider contract lines that end's before the new end date
|
||||
:param date_end: new date end for contract line
|
||||
:return: True
|
||||
"""
|
||||
if not all(self.mapped('is_stop_allowed')):
|
||||
raise ValidationError(_('Stop not allowed for this line'))
|
||||
for rec in self:
|
||||
if date_end < rec.date_start:
|
||||
rec.cancel()
|
||||
else:
|
||||
old_date_end = rec.date_end
|
||||
date_end = (
|
||||
rec.date_end
|
||||
if rec.date_end and rec.date_end < date_end
|
||||
else date_end
|
||||
)
|
||||
rec.write({'date_end': date_end, 'is_auto_renew': False})
|
||||
|
||||
msg = _(
|
||||
"""Contract line for <strong>{product}</strong>
|
||||
stopped: <br/>
|
||||
- <strong>End</strong>: {old_date_end} -- {new_date_end}
|
||||
""".format(
|
||||
product=rec.name,
|
||||
old_date_end=old_date_end,
|
||||
new_date_end=rec.date_end,
|
||||
)
|
||||
)
|
||||
rec.contract_id.message_post(body=msg)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def _prepare_value_for_plan_successor(
|
||||
self, date_start, date_end, is_auto_renew, recurring_next_date=False
|
||||
):
|
||||
self.ensure_one()
|
||||
if not recurring_next_date:
|
||||
recurring_next_date = self._compute_first_recurring_next_date(
|
||||
date_start,
|
||||
self.recurring_invoicing_type,
|
||||
self.recurring_rule_type,
|
||||
self.recurring_interval,
|
||||
)
|
||||
new_vals = self.read()[0]
|
||||
new_vals.pop("id", None)
|
||||
values = self._convert_to_write(new_vals)
|
||||
values['date_start'] = date_start
|
||||
values['date_end'] = date_end
|
||||
values['recurring_next_date'] = recurring_next_date
|
||||
values['is_auto_renew'] = is_auto_renew
|
||||
values['predecessor_contract_line_id'] = self.id
|
||||
return values
|
||||
|
||||
@api.multi
|
||||
def plan_successor(
|
||||
self, date_start, date_end, is_auto_renew, recurring_next_date=False
|
||||
):
|
||||
"""
|
||||
Create a copy of a contract line in a new interval
|
||||
:param date_start: date_start for the successor_contract_line
|
||||
:param date_end: date_end for the successor_contract_line
|
||||
:param is_auto_renew: is_auto_renew option for successor_contract_line
|
||||
:param recurring_next_date: recurring_next_date for the
|
||||
successor_contract_line
|
||||
:return: successor_contract_line
|
||||
"""
|
||||
contract_line = self.env['account.analytic.invoice.line']
|
||||
for rec in self:
|
||||
if not rec.is_plan_successor_allowed:
|
||||
raise ValidationError(
|
||||
_('Plan successor not allowed for this line')
|
||||
)
|
||||
rec.is_auto_renew = False
|
||||
new_line = self.create(
|
||||
rec._prepare_value_for_plan_successor(
|
||||
date_start, date_end, is_auto_renew, recurring_next_date
|
||||
)
|
||||
)
|
||||
rec.successor_contract_line_id = new_line
|
||||
contract_line |= new_line
|
||||
|
||||
msg = _(
|
||||
"""Contract line for <strong>{product}</strong>
|
||||
planned a successor: <br/>
|
||||
- <strong>Start</strong>: {new_date_start}
|
||||
<br/>
|
||||
- <strong>End</strong>: {new_date_end}
|
||||
""".format(
|
||||
product=rec.name,
|
||||
new_date_start=new_line.date_start,
|
||||
new_date_end=new_line.date_end,
|
||||
)
|
||||
)
|
||||
rec.contract_id.message_post(body=msg)
|
||||
return contract_line
|
||||
|
||||
@api.multi
|
||||
def stop_plan_successor(self, date_start, date_end, is_auto_renew):
|
||||
"""
|
||||
Stop a contract line for a defined period and start it later
|
||||
Cases to consider:
|
||||
* contract line end's before the suspension period:
|
||||
-> apply stop
|
||||
* contract line start before the suspension period and end in it
|
||||
-> apply stop at suspension start date
|
||||
-> apply plan successor:
|
||||
- date_start: suspension.date_end
|
||||
- date_end: date_end + (contract_line.date_end
|
||||
- suspension.date_start)
|
||||
* contract line start before the suspension period and end after it
|
||||
-> apply stop at suspension start date
|
||||
-> apply plan successor:
|
||||
- date_start: suspension.date_end
|
||||
- date_end: date_end + (suspension.date_end
|
||||
- suspension.date_start)
|
||||
* contract line start and end's in the suspension period
|
||||
-> apply delay
|
||||
- delay: suspension.date_end - contract_line.end_date
|
||||
* contract line start in the suspension period and end after it
|
||||
-> apply delay
|
||||
- delay: suspension.date_end - contract_line.date_start
|
||||
* contract line start and end after the suspension period
|
||||
-> apply delay
|
||||
- delay: suspension.date_end - suspension.start_date
|
||||
:param date_start: suspension start date
|
||||
:param date_end: suspension end date
|
||||
:param is_auto_renew: is the new line is set to auto_renew
|
||||
:return: created contract line
|
||||
"""
|
||||
if not all(self.mapped('is_stop_plan_successor_allowed')):
|
||||
raise ValidationError(
|
||||
_('Stop/Plan successor not allowed for this line')
|
||||
)
|
||||
contract_line = self.env['account.analytic.invoice.line']
|
||||
for rec in self:
|
||||
if rec.date_start >= date_start:
|
||||
if rec.date_end and rec.date_end <= date_end:
|
||||
delay = date_end - rec.date_end
|
||||
elif (
|
||||
rec.date_end
|
||||
and rec.date_end > date_end
|
||||
or not rec.date_end
|
||||
) and rec.date_start <= date_end:
|
||||
delay = date_end - rec.date_start
|
||||
else:
|
||||
delay = date_end - date_start
|
||||
rec.delay(delay)
|
||||
contract_line |= rec
|
||||
else:
|
||||
if rec.date_end and rec.date_end < date_start:
|
||||
rec.stop(date_start)
|
||||
elif (
|
||||
rec.date_end
|
||||
and rec.date_end > date_start
|
||||
and rec.date_end < date_end
|
||||
):
|
||||
new_date_start = date_end
|
||||
new_date_end = date_end + (rec.date_end - date_start)
|
||||
rec.stop(date_start)
|
||||
contract_line |= rec.plan_successor(
|
||||
new_date_start, new_date_end, is_auto_renew
|
||||
)
|
||||
else:
|
||||
new_date_start = date_end
|
||||
new_date_end = (
|
||||
rec.date_end
|
||||
if not rec.date_end
|
||||
else rec.date_end + (date_end - date_start)
|
||||
)
|
||||
rec.stop(date_start)
|
||||
contract_line |= rec.plan_successor(
|
||||
new_date_start, new_date_end, is_auto_renew
|
||||
)
|
||||
|
||||
return contract_line
|
||||
|
||||
@api.multi
|
||||
def cancel(self):
|
||||
if not all(self.mapped('is_cancel_allowed')):
|
||||
raise ValidationError(_('Cancel not allowed for this line'))
|
||||
for contract in self.mapped('contract_id'):
|
||||
lines = self.filtered(lambda l, c=contract: l.contract_id == c)
|
||||
msg = _(
|
||||
"""Contract line canceled: %s"""
|
||||
% "<br/>- ".join(
|
||||
[
|
||||
"<strong>%s</strong>" % name
|
||||
for name in lines.mapped('name')
|
||||
]
|
||||
)
|
||||
)
|
||||
contract.message_post(body=msg)
|
||||
return self.write({'is_canceled': True})
|
||||
|
||||
@api.multi
|
||||
def uncancel(self, recurring_next_date):
|
||||
if not all(self.mapped('is_un_cancel_allowed')):
|
||||
raise ValidationError(_('Un-cancel not allowed for this line'))
|
||||
for contract in self.mapped('contract_id'):
|
||||
lines = self.filtered(lambda l, c=contract: l.contract_id == c)
|
||||
msg = _(
|
||||
"""Contract line Un-canceled: %s"""
|
||||
% "<br/>- ".join(
|
||||
[
|
||||
"<strong>%s</strong>" % name
|
||||
for name in lines.mapped('name')
|
||||
]
|
||||
)
|
||||
)
|
||||
contract.message_post(body=msg)
|
||||
return self.write(
|
||||
{'is_canceled': False, 'recurring_next_date': recurring_next_date}
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def action_uncancel(self):
|
||||
self.ensure_one()
|
||||
context = {
|
||||
'default_contract_line_id': self.id,
|
||||
'default_recurring_next_date': fields.Date.today(),
|
||||
}
|
||||
context.update(self.env.context)
|
||||
view_id = self.env.ref(
|
||||
'contract.contract_line_wizard_uncancel_form_view'
|
||||
).id
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Un-Cancel Contract Line',
|
||||
'res_model': 'account.analytic.invoice.line.wizard',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'views': [(view_id, 'form')],
|
||||
'target': 'new',
|
||||
'context': context,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_plan_successor(self):
|
||||
self.ensure_one()
|
||||
context = {
|
||||
'default_contract_line_id': self.id,
|
||||
'default_is_auto_renew': self.is_auto_renew,
|
||||
}
|
||||
context.update(self.env.context)
|
||||
view_id = self.env.ref(
|
||||
'contract.contract_line_wizard_plan_successor_form_view'
|
||||
).id
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Plan contract line successor',
|
||||
'res_model': 'account.analytic.invoice.line.wizard',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'views': [(view_id, 'form')],
|
||||
'target': 'new',
|
||||
'context': context,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_stop(self):
|
||||
self.ensure_one()
|
||||
context = {
|
||||
'default_contract_line_id': self.id,
|
||||
'default_date_end': self.date_end,
|
||||
}
|
||||
context.update(self.env.context)
|
||||
view_id = self.env.ref(
|
||||
'contract.contract_line_wizard_stop_form_view'
|
||||
).id
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Resiliate contract line',
|
||||
'res_model': 'account.analytic.invoice.line.wizard',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'views': [(view_id, 'form')],
|
||||
'target': 'new',
|
||||
'context': context,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_stop_plan_successor(self):
|
||||
self.ensure_one()
|
||||
context = {
|
||||
'default_contract_line_id': self.id,
|
||||
'default_is_auto_renew': self.is_auto_renew,
|
||||
}
|
||||
context.update(self.env.context)
|
||||
view_id = self.env.ref(
|
||||
'contract.contract_line_wizard_stop_plan_successor_form_view'
|
||||
).id
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Suspend contract line',
|
||||
'res_model': 'account.analytic.invoice.line.wizard',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'views': [(view_id, 'form')],
|
||||
'target': 'new',
|
||||
'context': context,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def _get_renewal_dates(self):
|
||||
self.ensure_one()
|
||||
date_start = self.date_end
|
||||
date_end = date_start + self.get_relative_delta(
|
||||
self.auto_renew_rule_type, self.auto_renew_interval
|
||||
)
|
||||
return date_start, date_end
|
||||
|
||||
@api.multi
|
||||
def renew(self):
|
||||
res = self.env['account.analytic.invoice.line']
|
||||
for rec in self:
|
||||
is_auto_renew = rec.is_auto_renew
|
||||
rec.stop(rec.date_end)
|
||||
date_start, date_end = rec._get_renewal_dates()
|
||||
new_line = rec.plan_successor(date_start, date_end, is_auto_renew)
|
||||
new_line._onchange_date_start()
|
||||
res |= new_line
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _contract_line_to_renew_domain(self):
|
||||
date_ref = fields.datetime.today() + self.get_relative_delta(
|
||||
self.termination_notice_rule_type, self.termination_notice_interval
|
||||
)
|
||||
return [
|
||||
('is_auto_renew', '=', True),
|
||||
('date_end', '<=', date_ref),
|
||||
('is_canceled', '=', False),
|
||||
]
|
||||
|
||||
@api.model
|
||||
def cron_renew_contract_line(self):
|
||||
domain = self._contract_line_to_renew_domain()
|
||||
to_renew = self.search(domain)
|
||||
to_renew.renew()
|
||||
|
||||
Reference in New Issue
Block a user