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:
@@ -1 +1,2 @@
|
||||
from . import models
|
||||
from . import wizards
|
||||
|
||||
@@ -20,11 +20,13 @@
|
||||
'website': 'https://github.com/oca/contract',
|
||||
'depends': ['base', 'account', 'analytic'],
|
||||
'data': [
|
||||
'wizards/contract_line_wizard.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/contract_security.xml',
|
||||
'report/report_contract.xml',
|
||||
'report/contract_views.xml',
|
||||
'data/contract_cron.xml',
|
||||
'data/contract_renew_cron.xml',
|
||||
'data/mail_template.xml',
|
||||
'views/abstract_contract_line.xml',
|
||||
'views/contract.xml',
|
||||
|
||||
1
contract/data/__init__.py
Normal file
1
contract/data/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import contract_line_constraints
|
||||
230
contract/data/contract_line_constraints.py
Normal file
230
contract/data/contract_line_constraints.py
Normal file
@@ -0,0 +1,230 @@
|
||||
# Copyright 2018 ACSONE SA/NV.
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from collections import namedtuple
|
||||
from odoo.fields import Date
|
||||
|
||||
CRITERIA = namedtuple(
|
||||
'CRITERIA',
|
||||
['WHEN', 'HAS_DATE_END', 'IS_AUTO_RENEW', 'HAS_SUCCESSOR', 'CANCELED'],
|
||||
)
|
||||
ALLOWED = namedtuple(
|
||||
'ALLOWED',
|
||||
['PLAN_SUCCESSOR', 'STOP_PLAN_SUCCESSOR', 'STOP', 'CANCEL', 'UN_CANCEL'],
|
||||
)
|
||||
|
||||
CRITERIA_ALLOWED_DICT = {
|
||||
CRITERIA(
|
||||
WHEN='BEFORE',
|
||||
HAS_DATE_END=True,
|
||||
IS_AUTO_RENEW=True,
|
||||
HAS_SUCCESSOR=False,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=False,
|
||||
STOP_PLAN_SUCCESSOR=True,
|
||||
STOP=True,
|
||||
CANCEL=True,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='BEFORE',
|
||||
HAS_DATE_END=True,
|
||||
IS_AUTO_RENEW=False,
|
||||
HAS_SUCCESSOR=True,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=False,
|
||||
STOP_PLAN_SUCCESSOR=False,
|
||||
STOP=True,
|
||||
CANCEL=True,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='BEFORE',
|
||||
HAS_DATE_END=True,
|
||||
IS_AUTO_RENEW=False,
|
||||
HAS_SUCCESSOR=False,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=True,
|
||||
STOP_PLAN_SUCCESSOR=True,
|
||||
STOP=True,
|
||||
CANCEL=True,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='BEFORE',
|
||||
HAS_DATE_END=False,
|
||||
IS_AUTO_RENEW=False,
|
||||
HAS_SUCCESSOR=False,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=False,
|
||||
STOP_PLAN_SUCCESSOR=True,
|
||||
STOP=True,
|
||||
CANCEL=True,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='IN',
|
||||
HAS_DATE_END=True,
|
||||
IS_AUTO_RENEW=True,
|
||||
HAS_SUCCESSOR=False,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=False,
|
||||
STOP_PLAN_SUCCESSOR=True,
|
||||
STOP=True,
|
||||
CANCEL=True,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='IN',
|
||||
HAS_DATE_END=True,
|
||||
IS_AUTO_RENEW=False,
|
||||
HAS_SUCCESSOR=True,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=False,
|
||||
STOP_PLAN_SUCCESSOR=False,
|
||||
STOP=True,
|
||||
CANCEL=True,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='IN',
|
||||
HAS_DATE_END=True,
|
||||
IS_AUTO_RENEW=False,
|
||||
HAS_SUCCESSOR=False,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=True,
|
||||
STOP_PLAN_SUCCESSOR=True,
|
||||
STOP=True,
|
||||
CANCEL=True,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='IN',
|
||||
HAS_DATE_END=False,
|
||||
IS_AUTO_RENEW=False,
|
||||
HAS_SUCCESSOR=False,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=False,
|
||||
STOP_PLAN_SUCCESSOR=True,
|
||||
STOP=True,
|
||||
CANCEL=True,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='AFTER',
|
||||
HAS_DATE_END=True,
|
||||
IS_AUTO_RENEW=True,
|
||||
HAS_SUCCESSOR=False,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=False,
|
||||
STOP_PLAN_SUCCESSOR=False,
|
||||
STOP=False,
|
||||
CANCEL=False,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='AFTER',
|
||||
HAS_DATE_END=True,
|
||||
IS_AUTO_RENEW=False,
|
||||
HAS_SUCCESSOR=True,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=False,
|
||||
STOP_PLAN_SUCCESSOR=False,
|
||||
STOP=False,
|
||||
CANCEL=False,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN='AFTER',
|
||||
HAS_DATE_END=True,
|
||||
IS_AUTO_RENEW=False,
|
||||
HAS_SUCCESSOR=False,
|
||||
CANCELED=False,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=True,
|
||||
STOP_PLAN_SUCCESSOR=False,
|
||||
STOP=False,
|
||||
CANCEL=False,
|
||||
UN_CANCEL=False,
|
||||
),
|
||||
CRITERIA(
|
||||
WHEN=None,
|
||||
HAS_DATE_END=None,
|
||||
IS_AUTO_RENEW=None,
|
||||
HAS_SUCCESSOR=None,
|
||||
CANCELED=True,
|
||||
): ALLOWED(
|
||||
PLAN_SUCCESSOR=False,
|
||||
STOP_PLAN_SUCCESSOR=False,
|
||||
STOP=False,
|
||||
CANCEL=False,
|
||||
UN_CANCEL=True,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def compute_when(date_start, date_end):
|
||||
today = Date.today()
|
||||
if today < date_start:
|
||||
return 'BEFORE'
|
||||
if date_end and today > date_end:
|
||||
return 'AFTER'
|
||||
return 'IN'
|
||||
|
||||
|
||||
def compute_criteria(
|
||||
date_start,
|
||||
date_end,
|
||||
is_auto_renew,
|
||||
successor_contract_line_id,
|
||||
is_canceled,
|
||||
):
|
||||
if is_canceled:
|
||||
return CRITERIA(
|
||||
WHEN=None,
|
||||
HAS_DATE_END=None,
|
||||
IS_AUTO_RENEW=None,
|
||||
HAS_SUCCESSOR=None,
|
||||
CANCELED=True,
|
||||
)
|
||||
when = compute_when(date_start, date_end)
|
||||
has_date_end = date_end if not date_end else True
|
||||
is_auto_renew = is_auto_renew
|
||||
has_successor = True if successor_contract_line_id else False
|
||||
canceled = is_canceled
|
||||
return CRITERIA(
|
||||
WHEN=when,
|
||||
HAS_DATE_END=has_date_end,
|
||||
IS_AUTO_RENEW=is_auto_renew,
|
||||
HAS_SUCCESSOR=has_successor,
|
||||
CANCELED=canceled,
|
||||
)
|
||||
|
||||
|
||||
def get_allowed(
|
||||
date_start,
|
||||
date_end,
|
||||
is_auto_renew,
|
||||
successor_contract_line_id,
|
||||
is_canceled,
|
||||
):
|
||||
criteria = compute_criteria(
|
||||
date_start,
|
||||
date_end,
|
||||
is_auto_renew,
|
||||
successor_contract_line_id,
|
||||
is_canceled,
|
||||
)
|
||||
if criteria in CRITERIA_ALLOWED_DICT:
|
||||
return CRITERIA_ALLOWED_DICT[criteria]
|
||||
return False
|
||||
16
contract/data/contract_renew_cron.xml
Normal file
16
contract/data/contract_renew_cron.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding='UTF-8'?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record model="ir.cron" id="contract_line_cron_for_renew">
|
||||
<field name="name">Renew Contract lines</field>
|
||||
<field name="model_id" ref="model_account_analytic_invoice_line"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.cron_renew_contract_line()</field>
|
||||
<field name="user_id" ref="base.user_root" />
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall" />
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -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()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Copyright 2016 Tecnativa - Carlos Dauden
|
||||
# Copyright 2017 Tecnativa - Pedro M. Baeza
|
||||
# Copyright 2018 Tecnativa - Carlos Dauden
|
||||
# Copyright 2018 Tecnativa - Pedro M. Baeza
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import fields
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests import common
|
||||
@@ -75,8 +76,8 @@ class TestContractBase(common.SavepointCase):
|
||||
'discount': 50,
|
||||
'recurring_rule_type': 'monthly',
|
||||
'recurring_interval': 1,
|
||||
'date_start': '2016-02-15',
|
||||
'recurring_next_date': '2016-02-29',
|
||||
'date_start': '2018-02-15',
|
||||
'recurring_next_date': '2018-02-22',
|
||||
},
|
||||
)
|
||||
],
|
||||
@@ -92,8 +93,10 @@ class TestContractBase(common.SavepointCase):
|
||||
'discount': 50,
|
||||
'recurring_rule_type': 'monthly',
|
||||
'recurring_interval': 1,
|
||||
'date_start': '2016-02-15',
|
||||
'recurring_next_date': '2016-02-29',
|
||||
'date_start': '2018-01-01',
|
||||
'date_end': '2019-01-01',
|
||||
'recurring_next_date': '2018-01-15',
|
||||
'is_auto_renew': True,
|
||||
}
|
||||
cls.acct_line = cls.env['account.analytic.invoice.line'].create(
|
||||
cls.line_vals
|
||||
@@ -107,6 +110,7 @@ class TestContract(TestContractBase):
|
||||
vals = self.line_vals.copy()
|
||||
del vals['contract_id']
|
||||
del vals['date_start']
|
||||
del vals['date_end']
|
||||
vals['contract_template_id'] = self.template.id
|
||||
vals.update(overrides)
|
||||
return self.env['account.analytic.contract.line'].create(vals)
|
||||
@@ -130,7 +134,7 @@ class TestContract(TestContractBase):
|
||||
self.assertEqual(self.acct_line.price_unit, 10)
|
||||
|
||||
def test_contract(self):
|
||||
recurring_next_date = to_date('2016-03-29')
|
||||
recurring_next_date = to_date('2018-02-15')
|
||||
self.assertAlmostEqual(self.acct_line.price_subtotal, 50.0)
|
||||
res = self.acct_line._onchange_product_id()
|
||||
self.assertIn('uom_id', res['domain'])
|
||||
@@ -154,8 +158,8 @@ class TestContract(TestContractBase):
|
||||
)
|
||||
|
||||
def test_contract_daily(self):
|
||||
recurring_next_date = to_date('2016-03-01')
|
||||
self.acct_line.recurring_next_date = '2016-02-29'
|
||||
recurring_next_date = to_date('2018-02-23')
|
||||
self.acct_line.recurring_next_date = '2018-02-22'
|
||||
self.acct_line.recurring_rule_type = 'daily'
|
||||
self.contract.pricelist_id = False
|
||||
self.contract.recurring_create_invoice()
|
||||
@@ -168,8 +172,8 @@ class TestContract(TestContractBase):
|
||||
)
|
||||
|
||||
def test_contract_weekly(self):
|
||||
recurring_next_date = to_date('2016-03-07')
|
||||
self.acct_line.recurring_next_date = '2016-02-29'
|
||||
recurring_next_date = to_date('2018-03-01')
|
||||
self.acct_line.recurring_next_date = '2018-02-22'
|
||||
self.acct_line.recurring_rule_type = 'weekly'
|
||||
self.acct_line.recurring_invoicing_type = 'post-paid'
|
||||
self.contract.recurring_create_invoice()
|
||||
@@ -182,8 +186,8 @@ class TestContract(TestContractBase):
|
||||
)
|
||||
|
||||
def test_contract_yearly(self):
|
||||
recurring_next_date = to_date('2017-02-28')
|
||||
self.acct_line.recurring_next_date = '2016-02-29'
|
||||
recurring_next_date = to_date('2019-02-22')
|
||||
self.acct_line.recurring_next_date = '2018-02-22'
|
||||
self.acct_line.recurring_rule_type = 'yearly'
|
||||
self.contract.recurring_create_invoice()
|
||||
invoices_weekly = self.env['account.invoice'].search(
|
||||
@@ -195,8 +199,8 @@ class TestContract(TestContractBase):
|
||||
)
|
||||
|
||||
def test_contract_monthly_lastday(self):
|
||||
recurring_next_date = to_date('2016-03-31')
|
||||
self.acct_line.recurring_next_date = '2016-02-29'
|
||||
recurring_next_date = to_date('2018-03-31')
|
||||
self.acct_line.recurring_next_date = '2018-02-22'
|
||||
self.acct_line.recurring_invoicing_type = 'post-paid'
|
||||
self.acct_line.recurring_rule_type = 'monthlylastday'
|
||||
self.contract.recurring_create_invoice()
|
||||
@@ -216,7 +220,7 @@ class TestContract(TestContractBase):
|
||||
)
|
||||
|
||||
def test_onchange_date_start(self):
|
||||
recurring_next_date = to_date('2016-01-01')
|
||||
recurring_next_date = to_date('2018-01-01')
|
||||
self.acct_line.date_start = recurring_next_date
|
||||
self.acct_line._onchange_date_start()
|
||||
self.assertEqual(
|
||||
@@ -255,8 +259,8 @@ class TestContract(TestContractBase):
|
||||
with self.assertRaises(ValidationError):
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': '2017-01-01',
|
||||
'recurring_next_date': '2016-01-01',
|
||||
'date_start': '2018-01-01',
|
||||
'recurring_next_date': '2017-01-01',
|
||||
}
|
||||
)
|
||||
|
||||
@@ -403,16 +407,17 @@ class TestContract(TestContractBase):
|
||||
def test_compute_create_invoice_visibility(self):
|
||||
self.acct_line.write(
|
||||
{
|
||||
'recurring_next_date': '2017-01-01',
|
||||
'date_start': '2016-01-01',
|
||||
'recurring_next_date': '2018-01-15',
|
||||
'date_start': '2018-01-01',
|
||||
'is_auto_renew': False,
|
||||
'date_end': False,
|
||||
}
|
||||
)
|
||||
self.assertTrue(self.contract.create_invoice_visibility)
|
||||
self.acct_line.date_end = '2017-01-01'
|
||||
self.acct_line.date_end = '2018-02-01'
|
||||
self.contract.refresh()
|
||||
self.assertTrue(self.contract.create_invoice_visibility)
|
||||
self.acct_line.date_end = '2016-01-01'
|
||||
self.acct_line.date_end = '2018-01-01'
|
||||
self.contract.refresh()
|
||||
self.assertFalse(self.contract.create_invoice_visibility)
|
||||
|
||||
@@ -521,12 +526,534 @@ class TestContract(TestContractBase):
|
||||
|
||||
def test_date_end(self):
|
||||
"""recurring next date for a contract is the min for all lines"""
|
||||
self.assertFalse(self.contract.date_end)
|
||||
self.assertEqual(self.acct_line.date_end, to_date('2019-01-01'))
|
||||
self.acct_line.date_end = '2018-01-01'
|
||||
self.assertEqual(
|
||||
self.contract.date_end,
|
||||
max(self.contract.recurring_invoice_line_ids.mapped('date_end')),
|
||||
)
|
||||
self.assertEqual(self.acct_line.date_end, to_date('2018-01-01'))
|
||||
self.acct_line.copy()
|
||||
self.acct_line.date_end = False
|
||||
self.acct_line.write({'date_end': False, 'is_auto_renew': False})
|
||||
self.assertFalse(self.contract.date_end)
|
||||
|
||||
def test_stop_contract_line(self):
|
||||
"""It should put end to the contract line"""
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': fields.Date.today(),
|
||||
'recurring_next_date': fields.Date.today(),
|
||||
'date_end': fields.Date.today() + relativedelta(months=7),
|
||||
'is_auto_renew': True,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop(fields.Date.today() + relativedelta(months=5))
|
||||
self.assertEqual(
|
||||
self.acct_line.date_end,
|
||||
fields.Date.today() + relativedelta(months=5),
|
||||
)
|
||||
|
||||
def test_stop_upcoming_contract_line(self):
|
||||
"""It should put end to the contract line"""
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': fields.Date.today() + relativedelta(months=3),
|
||||
'recurring_next_date': fields.Date.today()
|
||||
+ relativedelta(months=3),
|
||||
'date_end': fields.Date.today() + relativedelta(months=7),
|
||||
'is_auto_renew': True,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop(fields.Date.today())
|
||||
self.assertEqual(
|
||||
self.acct_line.date_end,
|
||||
fields.Date.today() + relativedelta(months=7),
|
||||
)
|
||||
self.assertTrue(self.acct_line.is_canceled)
|
||||
|
||||
def test_stop_past_contract_line(self):
|
||||
"""Past contract line are ignored on stop"""
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_end': fields.Date.today() + relativedelta(months=5),
|
||||
'is_auto_renew': True,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop(fields.Date.today() + relativedelta(months=7))
|
||||
self.assertEqual(
|
||||
self.acct_line.date_end,
|
||||
fields.Date.today() + relativedelta(months=5),
|
||||
)
|
||||
|
||||
def test_stop_contract_line_without_date_end(self):
|
||||
"""Past contract line are ignored on stop"""
|
||||
self.acct_line.write({'date_end': False, 'is_auto_renew': False})
|
||||
self.acct_line.stop(fields.Date.today() + relativedelta(months=7))
|
||||
self.assertEqual(
|
||||
self.acct_line.date_end,
|
||||
fields.Date.today() + relativedelta(months=7),
|
||||
)
|
||||
|
||||
def test_stop_plan_successor_wizard(self):
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': fields.Date.today(),
|
||||
'recurring_next_date': fields.Date.today(),
|
||||
'date_end': fields.Date.today() + relativedelta(months=5),
|
||||
'is_auto_renew': True,
|
||||
}
|
||||
)
|
||||
wizard = self.env['account.analytic.invoice.line.wizard'].create(
|
||||
{
|
||||
'date_end': fields.Date.today() + relativedelta(months=7),
|
||||
'contract_line_id': self.acct_line.id,
|
||||
}
|
||||
)
|
||||
wizard.stop()
|
||||
self.assertEqual(
|
||||
self.acct_line.date_end,
|
||||
fields.Date.today() + relativedelta(months=7),
|
||||
)
|
||||
self.assertFalse(self.acct_line.is_auto_renew)
|
||||
|
||||
def test_stop_plan_successor_contract_line_1(self):
|
||||
"""
|
||||
* contract line end's before the suspension period:
|
||||
-> apply stop
|
||||
"""
|
||||
suspension_start = fields.Date.today() + relativedelta(months=5)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=6)
|
||||
start_date = fields.Date.today()
|
||||
end_date = fields.Date.today() + relativedelta(months=4)
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop_plan_successor(
|
||||
suspension_start, suspension_end, True
|
||||
)
|
||||
self.assertEqual(self.acct_line.date_end, end_date)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertFalse(new_line)
|
||||
|
||||
def test_stop_plan_successor_contract_line_2(self):
|
||||
"""
|
||||
* 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: suspension.date_end + (contract_line.date_end
|
||||
- suspension.date_start)
|
||||
"""
|
||||
suspension_start = fields.Date.today() + relativedelta(months=3)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=5)
|
||||
start_date = fields.Date.today()
|
||||
end_date = fields.Date.today() + relativedelta(months=4)
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop_plan_successor(
|
||||
suspension_start, suspension_end, True
|
||||
)
|
||||
self.assertEqual(self.acct_line.date_end, suspension_start)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertTrue(new_line)
|
||||
new_date_end = suspension_end + (end_date - suspension_start)
|
||||
self.assertEqual(new_line.date_start, suspension_end)
|
||||
self.assertEqual(new_line.date_end, new_date_end)
|
||||
|
||||
def test_stop_plan_successor_contract_line_3(self):
|
||||
"""
|
||||
* 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: suspension.date_end + (suspension.date_end
|
||||
- suspension.date_start)
|
||||
"""
|
||||
suspension_start = fields.Date.today() + relativedelta(months=3)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=5)
|
||||
start_date = fields.Date.today()
|
||||
end_date = fields.Date.today() + relativedelta(months=6)
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop_plan_successor(
|
||||
suspension_start, suspension_end, True
|
||||
)
|
||||
self.assertEqual(self.acct_line.date_end, suspension_start)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertTrue(new_line)
|
||||
new_date_end = end_date + (suspension_end - suspension_start)
|
||||
self.assertEqual(new_line.date_start, suspension_end)
|
||||
self.assertEqual(new_line.date_end, new_date_end)
|
||||
|
||||
def test_stop_plan_successor_contract_line_3_without_end_date(self):
|
||||
"""
|
||||
* 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: suspension.date_end + (suspension.date_end
|
||||
- suspension.date_start)
|
||||
"""
|
||||
suspension_start = fields.Date.today() + relativedelta(months=3)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=5)
|
||||
start_date = fields.Date.today()
|
||||
end_date = False
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
'is_auto_renew': False,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop_plan_successor(
|
||||
suspension_start, suspension_end, False
|
||||
)
|
||||
self.assertEqual(self.acct_line.date_end, suspension_start)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertTrue(new_line)
|
||||
self.assertEqual(new_line.date_start, suspension_end)
|
||||
self.assertFalse(new_line.date_end)
|
||||
|
||||
def test_stop_plan_successor_contract_line_4(self):
|
||||
"""
|
||||
* contract line start and end's in the suspension period
|
||||
-> apply delay
|
||||
- delay: suspension.date_end - contract_line.end_date
|
||||
"""
|
||||
suspension_start = fields.Date.today() + relativedelta(months=2)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=5)
|
||||
start_date = fields.Date.today() + relativedelta(months=3)
|
||||
end_date = fields.Date.today() + relativedelta(months=4)
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop_plan_successor(
|
||||
suspension_start, suspension_end, True
|
||||
)
|
||||
self.assertEqual(
|
||||
self.acct_line.date_start, start_date + (suspension_end - end_date)
|
||||
)
|
||||
self.assertEqual(
|
||||
self.acct_line.date_end, end_date + (suspension_end - end_date)
|
||||
)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertFalse(new_line)
|
||||
|
||||
def test_stop_plan_successor_contract_line_5(self):
|
||||
"""
|
||||
* contract line start in the suspension period and end after it
|
||||
-> apply delay
|
||||
- delay: suspension.date_end - contract_line.date_start
|
||||
"""
|
||||
suspension_start = fields.Date.today() + relativedelta(months=2)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=5)
|
||||
start_date = fields.Date.today() + relativedelta(months=3)
|
||||
end_date = fields.Date.today() + relativedelta(months=6)
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop_plan_successor(
|
||||
suspension_start, suspension_end, True
|
||||
)
|
||||
self.assertEqual(
|
||||
self.acct_line.date_start,
|
||||
start_date + (suspension_end - start_date),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.acct_line.date_end, end_date + (suspension_end - start_date)
|
||||
)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertFalse(new_line)
|
||||
|
||||
def test_stop_plan_successor_contract_line_5_without_date_end(self):
|
||||
"""
|
||||
* contract line start in the suspension period and end after it
|
||||
-> apply delay
|
||||
- delay: suspension.date_end - contract_line.date_start
|
||||
"""
|
||||
suspension_start = fields.Date.today() + relativedelta(months=2)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=5)
|
||||
start_date = fields.Date.today() + relativedelta(months=3)
|
||||
end_date = False
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
'is_auto_renew': False,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop_plan_successor(
|
||||
suspension_start, suspension_end, True
|
||||
)
|
||||
self.assertEqual(
|
||||
self.acct_line.date_start,
|
||||
start_date + (suspension_end - start_date),
|
||||
)
|
||||
self.assertFalse(self.acct_line.date_end)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertFalse(new_line)
|
||||
|
||||
def test_stop_plan_successor_contract_line_6(self):
|
||||
"""
|
||||
* contract line start and end after the suspension period
|
||||
-> apply delay
|
||||
- delay: suspension.date_end - suspension.start_date
|
||||
"""
|
||||
suspension_start = fields.Date.today() + relativedelta(months=2)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=3)
|
||||
start_date = fields.Date.today() + relativedelta(months=4)
|
||||
end_date = fields.Date.today() + relativedelta(months=6)
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop_plan_successor(
|
||||
suspension_start, suspension_end, True
|
||||
)
|
||||
self.assertEqual(
|
||||
self.acct_line.date_start,
|
||||
start_date + (suspension_end - suspension_start),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.acct_line.date_end,
|
||||
end_date + (suspension_end - suspension_start),
|
||||
)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertFalse(new_line)
|
||||
|
||||
def test_stop_plan_successor_contract_line_6_without_date_end(self):
|
||||
"""
|
||||
* contract line start and end after the suspension period
|
||||
-> apply delay
|
||||
- delay: suspension.date_end - suspension.start_date
|
||||
"""
|
||||
suspension_start = fields.Date.today() + relativedelta(months=2)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=3)
|
||||
start_date = fields.Date.today() + relativedelta(months=4)
|
||||
end_date = False
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
'is_auto_renew': False,
|
||||
}
|
||||
)
|
||||
self.acct_line.stop_plan_successor(
|
||||
suspension_start, suspension_end, True
|
||||
)
|
||||
self.assertEqual(
|
||||
self.acct_line.date_start,
|
||||
start_date + (suspension_end - suspension_start),
|
||||
)
|
||||
self.assertFalse(self.acct_line.date_end)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertFalse(new_line)
|
||||
|
||||
def test_stop_plan_successor_wizard(self):
|
||||
suspension_start = fields.Date.today() + relativedelta(months=2)
|
||||
suspension_end = fields.Date.today() + relativedelta(months=3)
|
||||
start_date = fields.Date.today() + relativedelta(months=4)
|
||||
end_date = fields.Date.today() + relativedelta(months=6)
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': start_date,
|
||||
'recurring_next_date': start_date,
|
||||
'date_end': end_date,
|
||||
}
|
||||
)
|
||||
wizard = self.env['account.analytic.invoice.line.wizard'].create(
|
||||
{
|
||||
'date_start': suspension_start,
|
||||
'date_end': suspension_end,
|
||||
'is_auto_renew': False,
|
||||
'contract_line_id': self.acct_line.id,
|
||||
}
|
||||
)
|
||||
wizard.stop_plan_successor()
|
||||
self.assertEqual(
|
||||
self.acct_line.date_start,
|
||||
start_date + (suspension_end - suspension_start),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.acct_line.date_end,
|
||||
end_date + (suspension_end - suspension_start),
|
||||
)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertFalse(new_line)
|
||||
|
||||
def test_plan_successor_contract_line(self):
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': fields.Date.today(),
|
||||
'recurring_next_date': fields.Date.today(),
|
||||
'date_end': fields.Date.today() + relativedelta(months=3),
|
||||
'is_auto_renew': False,
|
||||
}
|
||||
)
|
||||
self.acct_line.plan_successor(
|
||||
fields.Date.today() + relativedelta(months=5),
|
||||
fields.Date.today() + relativedelta(months=7),
|
||||
True,
|
||||
)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertFalse(self.acct_line.is_auto_renew)
|
||||
self.assertTrue(new_line.is_auto_renew)
|
||||
self.assertTrue(new_line, "should create a new contract line")
|
||||
self.assertEqual(
|
||||
new_line.date_start, fields.Date.today() + relativedelta(months=5)
|
||||
)
|
||||
self.assertEqual(
|
||||
new_line.date_end, fields.Date.today() + relativedelta(months=7)
|
||||
)
|
||||
|
||||
def test_overlap(self):
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': fields.Date.today(),
|
||||
'recurring_next_date': fields.Date.today(),
|
||||
'date_end': fields.Date.today() + relativedelta(months=3),
|
||||
'is_auto_renew': False,
|
||||
}
|
||||
)
|
||||
self.acct_line.plan_successor(
|
||||
fields.Date.today() + relativedelta(months=5),
|
||||
fields.Date.today() + relativedelta(months=7),
|
||||
True,
|
||||
)
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
new_line.date_start = fields.Date.today() + relativedelta(months=2)
|
||||
with self.assertRaises(ValidationError):
|
||||
self.acct_line.date_end = fields.Date.today() + relativedelta(
|
||||
months=6
|
||||
)
|
||||
|
||||
def test_plan_successor_wizard(self):
|
||||
self.acct_line.write(
|
||||
{
|
||||
'date_start': fields.Date.today(),
|
||||
'recurring_next_date': fields.Date.today(),
|
||||
'date_end': fields.Date.today() + relativedelta(months=2),
|
||||
'is_auto_renew': False,
|
||||
}
|
||||
)
|
||||
wizard = self.env['account.analytic.invoice.line.wizard'].create(
|
||||
{
|
||||
'date_start': fields.Date.today() + relativedelta(months=3),
|
||||
'date_end': fields.Date.today() + relativedelta(months=5),
|
||||
'is_auto_renew': True,
|
||||
'contract_line_id': self.acct_line.id,
|
||||
}
|
||||
)
|
||||
wizard.plan_successor()
|
||||
new_line = self.env['account.analytic.invoice.line'].search(
|
||||
[('predecessor_contract_line_id', '=', self.acct_line.id)]
|
||||
)
|
||||
self.assertFalse(self.acct_line.is_auto_renew)
|
||||
self.assertTrue(new_line.is_auto_renew)
|
||||
self.assertTrue(new_line, "should create a new contract line")
|
||||
self.assertEqual(
|
||||
new_line.date_start, fields.Date.today() + relativedelta(months=3)
|
||||
)
|
||||
self.assertEqual(
|
||||
new_line.date_end, fields.Date.today() + relativedelta(months=5)
|
||||
)
|
||||
|
||||
def test_cancel(self):
|
||||
self.acct_line.cancel()
|
||||
self.assertTrue(self.acct_line.is_canceled)
|
||||
self.acct_line.uncancel(fields.Date.today())
|
||||
self.assertFalse(self.acct_line.is_canceled)
|
||||
|
||||
def test_check_has_not_date_end_has_successor(self):
|
||||
self.acct_line.write({'date_end': False, 'is_auto_renew': False})
|
||||
with self.assertRaises(ValidationError):
|
||||
self.acct_line.plan_successor(
|
||||
to_date('2016-03-01'), to_date('2016-09-01'), False
|
||||
)
|
||||
|
||||
def test_check_has_not_date_end_is_auto_renew(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
self.acct_line.write({'date_end': False, 'is_auto_renew': True})
|
||||
|
||||
def test_check_has_successor_is_auto_renew(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
self.acct_line.plan_successor(
|
||||
to_date('2016-03-01'), to_date('2018-09-01'), False
|
||||
)
|
||||
|
||||
def test_search_contract_line_to_renew(self):
|
||||
self.acct_line.write({'date_end': fields.Date.today()})
|
||||
line_1 = self.acct_line.copy(
|
||||
{'date_end': fields.Date.today() + relativedelta(months=1)}
|
||||
)
|
||||
line_2 = self.acct_line.copy(
|
||||
{'date_end': fields.Date.today() - relativedelta(months=1)}
|
||||
)
|
||||
line_3 = self.acct_line.copy(
|
||||
{'date_end': fields.Date.today() - relativedelta(months=2)}
|
||||
)
|
||||
self.acct_line.copy(
|
||||
{'date_end': fields.Date.today() + relativedelta(months=2)}
|
||||
)
|
||||
to_renew = self.acct_line.search(
|
||||
self.acct_line._contract_line_to_renew_domain()
|
||||
)
|
||||
self.assertEqual(
|
||||
set(to_renew), set((self.acct_line, line_1, line_2, line_3))
|
||||
)
|
||||
|
||||
def test_renew(self):
|
||||
new_line = self.acct_line.renew()
|
||||
self.assertFalse(self.acct_line.is_auto_renew)
|
||||
self.assertTrue(new_line.is_auto_renew)
|
||||
self.assertEqual(new_line.date_start, to_date('2019-01-01'))
|
||||
self.assertEqual(new_line.date_end, to_date('2020-01-01'))
|
||||
|
||||
@@ -1,22 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_abstract_analytic_contract_line_view_form" model="ir.ui.view">
|
||||
<field name="name">Account Abstract Analytic Contract Line Form View</field>
|
||||
<record id="account_abstract_analytic_contract_line_view_form"
|
||||
model="ir.ui.view">
|
||||
<field name="name">Account Abstract Analytic Contract Line Form View
|
||||
</field>
|
||||
<field name="model">account.abstract.analytic.contract.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="product_id"/>
|
||||
<field name="name"/>
|
||||
<field name="quantity" colspan="2"/>
|
||||
<field name="uom_id" colspan="2"/>
|
||||
<field name="automatic_price"/>
|
||||
<sheet>
|
||||
<group col="4">
|
||||
<field colspan="4" name="product_id"/>
|
||||
<field colspan="4" name="name"/>
|
||||
<field colspan="2" name="quantity"/>
|
||||
<field colspan="2" name="uom_id"/>
|
||||
<field colspan="2" name="automatic_price"/>
|
||||
<field name="specific_price" invisible="1"/>
|
||||
<field name="price_unit"
|
||||
attrs="{'readonly': [('automatic_price', '=', True)]}"
|
||||
colspan="2"/>
|
||||
<field name="discount" colspan="2"/>
|
||||
<field colspan="2" name="price_unit"
|
||||
attrs="{'readonly': [('automatic_price', '=', True)]}"/>
|
||||
<field colspan="2" name="discount"/>
|
||||
</group>
|
||||
<group col="4">
|
||||
<field colspan="2" name="is_auto_renew"/>
|
||||
<field colspan="2" name="is_canceled" invisible="1"/>
|
||||
</group>
|
||||
<group attrs="{'invisible':[('is_auto_renew', '=', False)]}">
|
||||
<group>
|
||||
<label for="auto_renew_interval"/>
|
||||
<div>
|
||||
<field name="auto_renew_interval"
|
||||
class="oe_inline" nolabel="1"
|
||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
||||
<field name="auto_renew_rule_type"
|
||||
class="oe_inline" nolabel="1"
|
||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<label for="termination_notice_interval"/>
|
||||
<div>
|
||||
<field name="termination_notice_interval"
|
||||
class="oe_inline" nolabel="1"
|
||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
||||
<field name="termination_notice_rule_type"
|
||||
class="oe_inline" nolabel="1"
|
||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group name="recurrence_info">
|
||||
<group>
|
||||
@@ -26,12 +56,14 @@
|
||||
<label for="recurring_interval"/>
|
||||
<div>
|
||||
<field name="recurring_interval"
|
||||
class="oe_inline"/>
|
||||
class="oe_inline" nolabel="1"/>
|
||||
<field name="recurring_rule_type"
|
||||
class="oe_inline"/>
|
||||
class="oe_inline" nolabel="1"/>
|
||||
</div>
|
||||
</group>
|
||||
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_analytic_account_recurring_form_form" model="ir.ui.view">
|
||||
<record id="account_analytic_account_recurring_form_form"
|
||||
model="ir.ui.view">
|
||||
<field name="name">Contract form</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/>
|
||||
<field name="inherit_id"
|
||||
ref="analytic.view_account_analytic_account_form"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority" eval="9999"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="attributes">
|
||||
<attribute name="attrs">{'required': [('recurring_invoices', '=', True)]}</attribute>
|
||||
<attribute name="attrs">{'required': [('recurring_invoices',
|
||||
'=', True)]}
|
||||
</attribute>
|
||||
</field>
|
||||
<xpath expr="//div[@name='button_box']/.." position="before">
|
||||
<header>
|
||||
<button name="action_contract_send" type="object" string="Send by Email" groups="base.group_user"/>
|
||||
<button name="action_contract_send" type="object"
|
||||
string="Send by Email" groups="base.group_user"/>
|
||||
</header>
|
||||
</xpath>
|
||||
<xpath expr='//field[@name="code"]' position='before'>
|
||||
@@ -34,15 +39,18 @@
|
||||
class="oe_link"
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
<button name="button_show_recurring_invoices"
|
||||
type="object"
|
||||
<button name="contract.act_recurring_invoices"
|
||||
type="action"
|
||||
attrs="{'invisible': [('recurring_invoices','!=',True)]}"
|
||||
string="⇒ Show recurring invoices"
|
||||
class="oe_link"
|
||||
/>
|
||||
</div>
|
||||
<group col="4" attrs="{'invisible': [('recurring_invoices','!=',True)]}">
|
||||
<field name="contract_template_id" colspan="4" domain="['|', ('contract_type', '=', contract_type), ('contract_type', '=', False)]" context="{'default_contract_type': contract_type}"/>
|
||||
<group col="4"
|
||||
attrs="{'invisible': [('recurring_invoices','!=',True)]}">
|
||||
<field name="contract_template_id" colspan="4"
|
||||
domain="['|', ('contract_type', '=', contract_type), ('contract_type', '=', False)]"
|
||||
context="{'default_contract_type': contract_type}"/>
|
||||
<field name="journal_id"
|
||||
domain="[('type', '=', contract_type),('company_id', '=', company_id)]"
|
||||
attrs="{'required': [('recurring_invoices', '=', True)]}"
|
||||
@@ -50,37 +58,85 @@
|
||||
<field name="pricelist_id"/>
|
||||
<field name="recurring_next_date"/>
|
||||
<field name="date_end"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<label for="recurring_invoice_line_ids"
|
||||
attrs="{'invisible': [('recurring_invoices','=',False)]}"
|
||||
/>
|
||||
<div attrs="{'invisible': [('recurring_invoices','=',False)]}">
|
||||
<field name="recurring_invoice_line_ids">
|
||||
<tree string="Account Analytic Lines">
|
||||
<tree decoration-muted="is_canceled">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="product_id"/>
|
||||
<field name="name"/>
|
||||
<field name="quantity"/>
|
||||
<field name="uom_id"/>
|
||||
<field name="automatic_price"/>
|
||||
<field name="price_unit" attrs="{'readonly': [('automatic_price', '=', True)]}"/>
|
||||
<field name="price_unit"
|
||||
attrs="{'readonly': [('automatic_price', '=', True)]}"/>
|
||||
<field name="specific_price" invisible="1"/>
|
||||
<field name="discount" groups="base.group_no_one"/>
|
||||
<field name="price_subtotal"/>
|
||||
<field name="recurring_interval" invisible="1"/>
|
||||
<field name="recurring_rule_type" invisible="1"/>
|
||||
<field name="recurring_invoicing_type" invisible="1"/>
|
||||
<field name="recurring_invoicing_type"
|
||||
invisible="1"/>
|
||||
<field name="date_start" required="1"/>
|
||||
<field name="date_end"/>
|
||||
<field name="recurring_next_date" required="1"/>
|
||||
<field name="create_invoice_visibility"
|
||||
invisible="1"/>
|
||||
<field name="is_plan_successor_allowed" invisible="1"/>
|
||||
<field name="is_stop_plan_successor_allowed" invisible="1"/>
|
||||
<field name="is_stop_allowed" invisible="1"/>
|
||||
<field name="is_cancel_allowed" invisible="1"/>
|
||||
<field name="is_un_cancel_allowed" invisible="1"/>
|
||||
<field name="is_auto_renew" invisible="1"/>
|
||||
<field name="is_canceled" invisible="1"/>
|
||||
<button name="action_plan_successor"
|
||||
string="Plan Start"
|
||||
type="object"
|
||||
icon="fa-calendar text-success"
|
||||
attrs="{'invisible': [('is_plan_successor_allowed', '=', False)]}"/>
|
||||
<button name="action_stop_plan_successor"
|
||||
string="Stop Plan Successor"
|
||||
type="object"
|
||||
icon="fa-pause text-muted"
|
||||
attrs="{'invisible': [('is_stop_plan_successor_allowed', '=', False)]}"/>
|
||||
<button name="action_stop"
|
||||
string="Stop"
|
||||
type="object"
|
||||
icon="fa-stop text-danger"
|
||||
attrs="{'invisible': [('is_stop_allowed', '=', False)]}"/>
|
||||
<button name="cancel"
|
||||
string="Cancel"
|
||||
type="object"
|
||||
icon="fa-ban text-danger"
|
||||
attrs="{'invisible': [('is_cancel_allowed', '=', False)]}"/>
|
||||
<button name="action_uncancel"
|
||||
string="Un-cancel"
|
||||
type="object"
|
||||
icon="fa-ban text-success"
|
||||
attrs="{'invisible': [('is_un_cancel_allowed', '=', False)]}"/>
|
||||
<button name="renew"
|
||||
string="Renew"
|
||||
type="object"
|
||||
icon="fa-fast-forward text-success"
|
||||
groups="base.group_no_one"
|
||||
attrs="{'invisible': [('is_auto_renew', '=', False)]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
<group string="Legend (for the markers inside invoice lines description)"
|
||||
name="group_legend" attrs="{'invisible': [('recurring_invoices','!=',True)]}">
|
||||
<p colspan="2"> <strong>#START#</strong>: Start date of the invoiced period</p>
|
||||
<p colspan="2"> <strong>#END#</strong>: End date of the invoiced period</p>
|
||||
name="group_legend"
|
||||
attrs="{'invisible': [('recurring_invoices','!=',True)]}">
|
||||
<p colspan="2"><strong>#START#</strong>: Start date of the
|
||||
invoiced period
|
||||
</p>
|
||||
<p colspan="2"><strong>#END#</strong>: End date of the
|
||||
invoiced period
|
||||
</p>
|
||||
</group>
|
||||
</group>
|
||||
</field>
|
||||
@@ -89,14 +145,17 @@
|
||||
<record id="account_analytic_account_sale_form" model="ir.ui.view">
|
||||
<field name="name">account.analytic.account.sale.form</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
<field name="inherit_id" ref="account_analytic_account_recurring_form_form"/>
|
||||
<field name="inherit_id"
|
||||
ref="account_analytic_account_recurring_form_form"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="attributes">
|
||||
<attribute name="string">Customer</attribute>
|
||||
<attribute name="domain">[('customer', '=', True)]</attribute>
|
||||
<attribute name="context">{'default_customer': True, 'default_supplier': False}</attribute>
|
||||
<attribute name="context">{'default_customer': True,
|
||||
'default_supplier': False}
|
||||
</attribute>
|
||||
</field>
|
||||
<field name="journal_id" position="attributes">
|
||||
<attribute name="domain">[('type', '=', 'sale'),('company_id', '=', company_id)]</attribute>
|
||||
@@ -110,22 +169,27 @@
|
||||
<record id="account_analytic_account_purchase_form" model="ir.ui.view">
|
||||
<field name="name">account.analytic.account.purchase.form</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
<field name="inherit_id" ref="account_analytic_account_recurring_form_form"/>
|
||||
<field name="inherit_id"
|
||||
ref="account_analytic_account_recurring_form_form"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="attributes">
|
||||
<attribute name="string">Supplier</attribute>
|
||||
<attribute name="domain">[('supplier', '=', True)]</attribute>
|
||||
<attribute name="context">{'default_customer': False, 'default_supplier': True}</attribute>
|
||||
<attribute name="context">{'default_customer': False,
|
||||
'default_supplier': True}
|
||||
</attribute>
|
||||
</field>
|
||||
<field name="journal_id" position="attributes">
|
||||
<attribute name="domain">[('type', '=', 'purchase'),('company_id', '=', company_id)]</attribute>
|
||||
</field>
|
||||
<field name="product_id" position="attributes">
|
||||
<attribute name="domain">[('purchase_ok', '=', True)]</attribute>
|
||||
<attribute name="domain">[('purchase_ok', '=', True)]
|
||||
</attribute>
|
||||
</field>
|
||||
<xpath expr="//field[@name='recurring_invoice_line_ids']/tree/field[@name='automatic_price']" position="attributes">
|
||||
<xpath expr="//field[@name='recurring_invoice_line_ids']/tree/field[@name='automatic_price']"
|
||||
position="attributes">
|
||||
<attribute name="invisible">True</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
@@ -135,7 +199,8 @@
|
||||
<record id="view_account_analytic_account_journal_tree" model="ir.ui.view">
|
||||
<field name="name">Contract list</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_account_list" />
|
||||
<field name="inherit_id"
|
||||
ref="analytic.view_account_analytic_account_list"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority" eval="9999"/>
|
||||
<field name="arch" type="xml">
|
||||
@@ -146,13 +211,16 @@
|
||||
</record>
|
||||
|
||||
<!-- Analytic Account search view for contract -->
|
||||
<record id="view_account_analytic_account_contract_search" model="ir.ui.view">
|
||||
<record id="view_account_analytic_account_contract_search"
|
||||
model="ir.ui.view">
|
||||
<field name="name">Contract search</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_account_search"/>
|
||||
<field name="inherit_id"
|
||||
ref="analytic.view_account_analytic_account_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="after">
|
||||
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"
|
||||
<field name="partner_id"
|
||||
filter_domain="[('partner_id', 'child_of', self)]"
|
||||
string="Partner and dependents"/>
|
||||
</field>
|
||||
<field name="name" position="after">
|
||||
@@ -189,14 +257,20 @@
|
||||
</record>
|
||||
|
||||
<!-- Action Sales/Sales/Contracts -->
|
||||
<record id="action_account_analytic_sale_overdue_all" model="ir.actions.act_window">
|
||||
<record id="action_account_analytic_sale_overdue_all"
|
||||
model="ir.actions.act_window">
|
||||
<field name="name">Customer Contracts</field>
|
||||
<field name="res_model">account.analytic.account</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('contract_type', '=', 'sale')]</field>
|
||||
<field name="context">{'is_contract':1, 'search_default_not_finished':1, 'search_default_recurring_invoices':1, 'default_recurring_invoices': 1, 'default_contract_type': 'sale'}</field>
|
||||
<field name="search_view_id" ref="view_account_analytic_account_contract_search"/>
|
||||
<field name="context">{'is_contract':1,
|
||||
'search_default_not_finished':1,
|
||||
'search_default_recurring_invoices':1,
|
||||
'default_recurring_invoices': 1, 'default_contract_type': 'sale'}
|
||||
</field>
|
||||
<field name="search_view_id"
|
||||
ref="view_account_analytic_account_contract_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new contract.
|
||||
@@ -204,18 +278,23 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_analytic_sale_overdue_all_tree" model="ir.actions.act_window.view">
|
||||
<record id="action_account_analytic_sale_overdue_all_tree"
|
||||
model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="view_account_analytic_account_journal_tree"/>
|
||||
<field name="act_window_id" ref="action_account_analytic_sale_overdue_all"/>
|
||||
<field name="view_id"
|
||||
ref="view_account_analytic_account_journal_tree"/>
|
||||
<field name="act_window_id"
|
||||
ref="action_account_analytic_sale_overdue_all"/>
|
||||
</record>
|
||||
|
||||
<record id="action_account_analytic_sale_overdue_all_form" model="ir.actions.act_window.view">
|
||||
<record id="action_account_analytic_sale_overdue_all_form"
|
||||
model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="account_analytic_account_sale_form"/>
|
||||
<field name="act_window_id" ref="action_account_analytic_sale_overdue_all"/>
|
||||
<field name="act_window_id"
|
||||
ref="action_account_analytic_sale_overdue_all"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_action_account_analytic_sale_overdue_all"
|
||||
@@ -225,14 +304,21 @@
|
||||
/>
|
||||
|
||||
<!-- Action Purchases/Purchases/Contracts -->
|
||||
<record id="action_account_analytic_purchase_overdue_all" model="ir.actions.act_window">
|
||||
<record id="action_account_analytic_purchase_overdue_all"
|
||||
model="ir.actions.act_window">
|
||||
<field name="name">Supplier Contracts</field>
|
||||
<field name="res_model">account.analytic.account</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('contract_type', '=', 'purchase')]</field>
|
||||
<field name="context">{'is_contract':1, 'search_default_not_finished':1, 'search_default_recurring_invoices':1, 'default_recurring_invoices': 1, 'default_contract_type': 'purchase'}</field>
|
||||
<field name="search_view_id" ref="view_account_analytic_account_contract_search"/>
|
||||
<field name="context">{'is_contract':1,
|
||||
'search_default_not_finished':1,
|
||||
'search_default_recurring_invoices':1,
|
||||
'default_recurring_invoices': 1, 'default_contract_type':
|
||||
'purchase'}
|
||||
</field>
|
||||
<field name="search_view_id"
|
||||
ref="view_account_analytic_account_contract_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new contract.
|
||||
@@ -240,18 +326,23 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_analytic_purchase_overdue_all_tree" model="ir.actions.act_window.view">
|
||||
<record id="action_account_analytic_purchase_overdue_all_tree"
|
||||
model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="view_account_analytic_account_journal_tree"/>
|
||||
<field name="act_window_id" ref="action_account_analytic_purchase_overdue_all"/>
|
||||
<field name="view_id"
|
||||
ref="view_account_analytic_account_journal_tree"/>
|
||||
<field name="act_window_id"
|
||||
ref="action_account_analytic_purchase_overdue_all"/>
|
||||
</record>
|
||||
|
||||
<record id="action_account_analytic_purchase_overdue_all_form" model="ir.actions.act_window.view">
|
||||
<record id="action_account_analytic_purchase_overdue_all_form"
|
||||
model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="account_analytic_account_purchase_form"/>
|
||||
<field name="act_window_id" ref="action_account_analytic_purchase_overdue_all"/>
|
||||
<field name="act_window_id"
|
||||
ref="action_account_analytic_purchase_overdue_all"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_action_account_analytic_purchase_overdue_all"
|
||||
|
||||
@@ -8,19 +8,28 @@
|
||||
ref="account_abstract_analytic_contract_line_view_form"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="before">
|
||||
<header>
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
</xpath>
|
||||
<xpath expr="//form" position="attributes">
|
||||
<attribute name="string">Contract Line</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='recurrence_info']" position="inside">
|
||||
<group>
|
||||
<field name="date_start" required="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date_end"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="recurring_next_date"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date_end" attrs="{'required': [('is_auto_renew', '=', True)]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="predecessor_contract_line_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="successor_contract_line_id"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
1
contract/wizards/__init__.py
Normal file
1
contract/wizards/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import contract_line_wizard
|
||||
48
contract/wizards/contract_line_wizard.py
Normal file
48
contract/wizards/contract_line_wizard.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Copyright 2018 ACSONE SA/NV
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountAnalyticInvoiceLineWizard(models.TransientModel):
|
||||
|
||||
_name = 'account.analytic.invoice.line.wizard'
|
||||
_description = 'Contract Line Wizard'
|
||||
|
||||
date_start = fields.Date(string='Date Start')
|
||||
date_end = fields.Date(string='Date End')
|
||||
recurring_next_date = fields.Date(string='Next Invoice Date')
|
||||
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
|
||||
contract_line_id = fields.Many2one(
|
||||
comodel_name="account.analytic.invoice.line",
|
||||
string="Contract Line",
|
||||
required=True,
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def stop(self):
|
||||
for wizard in self:
|
||||
wizard.contract_line_id.stop(wizard.date_end)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def plan_successor(self):
|
||||
for wizard in self:
|
||||
wizard.contract_line_id.plan_successor(
|
||||
wizard.date_start, wizard.date_end, wizard.is_auto_renew
|
||||
)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def stop_plan_successor(self):
|
||||
for wizard in self:
|
||||
wizard.contract_line_id.stop_plan_successor(
|
||||
wizard.date_start, wizard.date_end, wizard.is_auto_renew
|
||||
)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def uncancel(self):
|
||||
for wizard in self:
|
||||
wizard.contract_line_id.uncancel(wizard.recurring_next_date)
|
||||
return True
|
||||
99
contract/wizards/contract_line_wizard.xml
Normal file
99
contract/wizards/contract_line_wizard.xml
Normal file
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2018 ACSONE SA/NV
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="contract_line_wizard_stop_form_view">
|
||||
<field name="name">contract.line.stop.wizard.form (in contract)</field>
|
||||
<field name="model">account.analytic.invoice.line.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="contract_line_id" invisible="True"/>
|
||||
<field string="Stop Date" name="date_end" required="True"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="stop"
|
||||
string="Validate"
|
||||
class="btn-primary"
|
||||
type="object"/>
|
||||
<button string="Cancel"
|
||||
class="btn-default"
|
||||
special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="contract_line_wizard_plan_successor_form_view">
|
||||
<field name="name">contract.line.plan_successor.wizard.form (in contract)</field>
|
||||
<field name="model">account.analytic.invoice.line.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="contract_line_id" invisible="True"/>
|
||||
<field name="date_start" required="True"/>
|
||||
<field name="date_end" attrs="{'required': [('is_auto_renew', '=', True)]}"/>
|
||||
<field name="is_auto_renew"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="plan_successor"
|
||||
string="Validate"
|
||||
class="btn-primary"
|
||||
type="object"/>
|
||||
<button string="Cancel"
|
||||
class="btn-default"
|
||||
special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="contract_line_wizard_stop_plan_successor_form_view">
|
||||
<field name="name">contract.line.stop_plan_successor.wizard.form (in contract)</field>
|
||||
<field name="model">account.analytic.invoice.line.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="contract_line_id" invisible="True"/>
|
||||
<field string="Suspension Start Date" name="date_start" required="True"/>
|
||||
<field string="Suspension End Date" name="date_end" required="True"/>
|
||||
<field name="is_auto_renew" invisible="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="stop_plan_successor"
|
||||
string="Validate"
|
||||
class="btn-primary"
|
||||
type="object"/>
|
||||
<button string="Cancel"
|
||||
class="btn-default"
|
||||
special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="contract_line_wizard_uncancel_form_view">
|
||||
<field name="name">contract.line.stop_plan_successor.wizard.form (in contract)</field>
|
||||
<field name="model">account.analytic.invoice.line.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="contract_line_id" invisible="True"/>
|
||||
<field name="recurring_next_date" required="True"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="uncancel"
|
||||
string="Validate"
|
||||
class="btn-primary"
|
||||
type="object"/>
|
||||
<button string="Cancel"
|
||||
class="btn-default"
|
||||
special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user