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 models
|
||||||
|
from . import wizards
|
||||||
|
|||||||
@@ -20,11 +20,13 @@
|
|||||||
'website': 'https://github.com/oca/contract',
|
'website': 'https://github.com/oca/contract',
|
||||||
'depends': ['base', 'account', 'analytic'],
|
'depends': ['base', 'account', 'analytic'],
|
||||||
'data': [
|
'data': [
|
||||||
|
'wizards/contract_line_wizard.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'security/contract_security.xml',
|
'security/contract_security.xml',
|
||||||
'report/report_contract.xml',
|
'report/report_contract.xml',
|
||||||
'report/contract_views.xml',
|
'report/contract_views.xml',
|
||||||
'data/contract_cron.xml',
|
'data/contract_cron.xml',
|
||||||
|
'data/contract_renew_cron.xml',
|
||||||
'data/mail_template.xml',
|
'data/mail_template.xml',
|
||||||
'views/abstract_contract_line.xml',
|
'views/abstract_contract_line.xml',
|
||||||
'views/contract.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(
|
pricelist_id = fields.Many2one(
|
||||||
comodel_name='product.pricelist', string='Pricelist'
|
comodel_name='product.pricelist', string='Pricelist'
|
||||||
)
|
)
|
||||||
recurring_next_date = fields.Date(
|
recurring_next_date = fields.Date(string='Date of Next Invoice')
|
||||||
copy=False, 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(
|
@api.depends(
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ from dateutil.relativedelta import relativedelta
|
|||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
from ..data.contract_line_constraints import get_allowed
|
||||||
|
|
||||||
|
|
||||||
class AccountAnalyticInvoiceLine(models.Model):
|
class AccountAnalyticInvoiceLine(models.Model):
|
||||||
_name = 'account.analytic.invoice.line'
|
_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_start = fields.Date(string='Date Start', default=fields.Date.today())
|
||||||
date_end = fields.Date(string='Date End', index=True)
|
date_end = fields.Date(string='Date End', index=True)
|
||||||
recurring_next_date = fields.Date(
|
recurring_next_date = fields.Date(string='Date of Next Invoice')
|
||||||
copy=False, string='Date of Next Invoice'
|
|
||||||
)
|
|
||||||
create_invoice_visibility = fields.Boolean(
|
create_invoice_visibility = fields.Boolean(
|
||||||
compute='_compute_create_invoice_visibility'
|
compute='_compute_create_invoice_visibility'
|
||||||
)
|
)
|
||||||
@@ -40,6 +40,139 @@ class AccountAnalyticInvoiceLine(models.Model):
|
|||||||
store=True,
|
store=True,
|
||||||
readonly=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
|
@api.model
|
||||||
def _compute_first_recurring_next_date(
|
def _compute_first_recurring_next_date(
|
||||||
@@ -59,6 +192,17 @@ class AccountAnalyticInvoiceLine(models.Model):
|
|||||||
recurring_rule_type, recurring_interval
|
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(
|
@api.onchange(
|
||||||
'date_start',
|
'date_start',
|
||||||
'recurring_invoicing_type',
|
'recurring_invoicing_type',
|
||||||
@@ -143,6 +287,7 @@ class AccountAnalyticInvoiceLine(models.Model):
|
|||||||
[
|
[
|
||||||
('contract_id.recurring_invoices', '=', True),
|
('contract_id.recurring_invoices', '=', True),
|
||||||
('recurring_next_date', '<=', date_ref),
|
('recurring_next_date', '<=', date_ref),
|
||||||
|
('is_canceled', '=', False),
|
||||||
'|',
|
'|',
|
||||||
('date_end', '=', False),
|
('date_end', '=', False),
|
||||||
('date_end', '>=', date_ref),
|
('date_end', '>=', date_ref),
|
||||||
@@ -248,3 +393,386 @@ class AccountAnalyticInvoiceLine(models.Model):
|
|||||||
return relativedelta(months=interval, day=31)
|
return relativedelta(months=interval, day=31)
|
||||||
else:
|
else:
|
||||||
return relativedelta(years=interval)
|
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 2018 Tecnativa - Carlos Dauden
|
||||||
# Copyright 2017 Tecnativa - Pedro M. Baeza
|
# Copyright 2018 Tecnativa - Pedro M. Baeza
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
from odoo.tests import common
|
from odoo.tests import common
|
||||||
@@ -75,8 +76,8 @@ class TestContractBase(common.SavepointCase):
|
|||||||
'discount': 50,
|
'discount': 50,
|
||||||
'recurring_rule_type': 'monthly',
|
'recurring_rule_type': 'monthly',
|
||||||
'recurring_interval': 1,
|
'recurring_interval': 1,
|
||||||
'date_start': '2016-02-15',
|
'date_start': '2018-02-15',
|
||||||
'recurring_next_date': '2016-02-29',
|
'recurring_next_date': '2018-02-22',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -92,8 +93,10 @@ class TestContractBase(common.SavepointCase):
|
|||||||
'discount': 50,
|
'discount': 50,
|
||||||
'recurring_rule_type': 'monthly',
|
'recurring_rule_type': 'monthly',
|
||||||
'recurring_interval': 1,
|
'recurring_interval': 1,
|
||||||
'date_start': '2016-02-15',
|
'date_start': '2018-01-01',
|
||||||
'recurring_next_date': '2016-02-29',
|
'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.acct_line = cls.env['account.analytic.invoice.line'].create(
|
||||||
cls.line_vals
|
cls.line_vals
|
||||||
@@ -107,6 +110,7 @@ class TestContract(TestContractBase):
|
|||||||
vals = self.line_vals.copy()
|
vals = self.line_vals.copy()
|
||||||
del vals['contract_id']
|
del vals['contract_id']
|
||||||
del vals['date_start']
|
del vals['date_start']
|
||||||
|
del vals['date_end']
|
||||||
vals['contract_template_id'] = self.template.id
|
vals['contract_template_id'] = self.template.id
|
||||||
vals.update(overrides)
|
vals.update(overrides)
|
||||||
return self.env['account.analytic.contract.line'].create(vals)
|
return self.env['account.analytic.contract.line'].create(vals)
|
||||||
@@ -130,7 +134,7 @@ class TestContract(TestContractBase):
|
|||||||
self.assertEqual(self.acct_line.price_unit, 10)
|
self.assertEqual(self.acct_line.price_unit, 10)
|
||||||
|
|
||||||
def test_contract(self):
|
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)
|
self.assertAlmostEqual(self.acct_line.price_subtotal, 50.0)
|
||||||
res = self.acct_line._onchange_product_id()
|
res = self.acct_line._onchange_product_id()
|
||||||
self.assertIn('uom_id', res['domain'])
|
self.assertIn('uom_id', res['domain'])
|
||||||
@@ -154,8 +158,8 @@ class TestContract(TestContractBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_contract_daily(self):
|
def test_contract_daily(self):
|
||||||
recurring_next_date = to_date('2016-03-01')
|
recurring_next_date = to_date('2018-02-23')
|
||||||
self.acct_line.recurring_next_date = '2016-02-29'
|
self.acct_line.recurring_next_date = '2018-02-22'
|
||||||
self.acct_line.recurring_rule_type = 'daily'
|
self.acct_line.recurring_rule_type = 'daily'
|
||||||
self.contract.pricelist_id = False
|
self.contract.pricelist_id = False
|
||||||
self.contract.recurring_create_invoice()
|
self.contract.recurring_create_invoice()
|
||||||
@@ -168,8 +172,8 @@ class TestContract(TestContractBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_contract_weekly(self):
|
def test_contract_weekly(self):
|
||||||
recurring_next_date = to_date('2016-03-07')
|
recurring_next_date = to_date('2018-03-01')
|
||||||
self.acct_line.recurring_next_date = '2016-02-29'
|
self.acct_line.recurring_next_date = '2018-02-22'
|
||||||
self.acct_line.recurring_rule_type = 'weekly'
|
self.acct_line.recurring_rule_type = 'weekly'
|
||||||
self.acct_line.recurring_invoicing_type = 'post-paid'
|
self.acct_line.recurring_invoicing_type = 'post-paid'
|
||||||
self.contract.recurring_create_invoice()
|
self.contract.recurring_create_invoice()
|
||||||
@@ -182,8 +186,8 @@ class TestContract(TestContractBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_contract_yearly(self):
|
def test_contract_yearly(self):
|
||||||
recurring_next_date = to_date('2017-02-28')
|
recurring_next_date = to_date('2019-02-22')
|
||||||
self.acct_line.recurring_next_date = '2016-02-29'
|
self.acct_line.recurring_next_date = '2018-02-22'
|
||||||
self.acct_line.recurring_rule_type = 'yearly'
|
self.acct_line.recurring_rule_type = 'yearly'
|
||||||
self.contract.recurring_create_invoice()
|
self.contract.recurring_create_invoice()
|
||||||
invoices_weekly = self.env['account.invoice'].search(
|
invoices_weekly = self.env['account.invoice'].search(
|
||||||
@@ -195,8 +199,8 @@ class TestContract(TestContractBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_contract_monthly_lastday(self):
|
def test_contract_monthly_lastday(self):
|
||||||
recurring_next_date = to_date('2016-03-31')
|
recurring_next_date = to_date('2018-03-31')
|
||||||
self.acct_line.recurring_next_date = '2016-02-29'
|
self.acct_line.recurring_next_date = '2018-02-22'
|
||||||
self.acct_line.recurring_invoicing_type = 'post-paid'
|
self.acct_line.recurring_invoicing_type = 'post-paid'
|
||||||
self.acct_line.recurring_rule_type = 'monthlylastday'
|
self.acct_line.recurring_rule_type = 'monthlylastday'
|
||||||
self.contract.recurring_create_invoice()
|
self.contract.recurring_create_invoice()
|
||||||
@@ -216,7 +220,7 @@ class TestContract(TestContractBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_onchange_date_start(self):
|
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.date_start = recurring_next_date
|
||||||
self.acct_line._onchange_date_start()
|
self.acct_line._onchange_date_start()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -255,8 +259,8 @@ class TestContract(TestContractBase):
|
|||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
self.acct_line.write(
|
self.acct_line.write(
|
||||||
{
|
{
|
||||||
'date_start': '2017-01-01',
|
'date_start': '2018-01-01',
|
||||||
'recurring_next_date': '2016-01-01',
|
'recurring_next_date': '2017-01-01',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -403,16 +407,17 @@ class TestContract(TestContractBase):
|
|||||||
def test_compute_create_invoice_visibility(self):
|
def test_compute_create_invoice_visibility(self):
|
||||||
self.acct_line.write(
|
self.acct_line.write(
|
||||||
{
|
{
|
||||||
'recurring_next_date': '2017-01-01',
|
'recurring_next_date': '2018-01-15',
|
||||||
'date_start': '2016-01-01',
|
'date_start': '2018-01-01',
|
||||||
|
'is_auto_renew': False,
|
||||||
'date_end': False,
|
'date_end': False,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.assertTrue(self.contract.create_invoice_visibility)
|
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.contract.refresh()
|
||||||
self.assertTrue(self.contract.create_invoice_visibility)
|
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.contract.refresh()
|
||||||
self.assertFalse(self.contract.create_invoice_visibility)
|
self.assertFalse(self.contract.create_invoice_visibility)
|
||||||
|
|
||||||
@@ -521,12 +526,534 @@ class TestContract(TestContractBase):
|
|||||||
|
|
||||||
def test_date_end(self):
|
def test_date_end(self):
|
||||||
"""recurring next date for a contract is the min for all lines"""
|
"""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.acct_line.date_end = '2018-01-01'
|
||||||
self.assertEqual(
|
self.assertEqual(self.acct_line.date_end, to_date('2018-01-01'))
|
||||||
self.contract.date_end,
|
|
||||||
max(self.contract.recurring_invoice_line_ids.mapped('date_end')),
|
|
||||||
)
|
|
||||||
self.acct_line.copy()
|
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)
|
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,37 +1,69 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="account_abstract_analytic_contract_line_view_form" model="ir.ui.view">
|
<record id="account_abstract_analytic_contract_line_view_form"
|
||||||
<field name="name">Account Abstract Analytic Contract Line Form View</field>
|
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="model">account.abstract.analytic.contract.line</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form>
|
<form>
|
||||||
<group>
|
<sheet>
|
||||||
<field name="product_id"/>
|
<group col="4">
|
||||||
<field name="name"/>
|
<field colspan="4" name="product_id"/>
|
||||||
<field name="quantity" colspan="2"/>
|
<field colspan="4" name="name"/>
|
||||||
<field name="uom_id" colspan="2"/>
|
<field colspan="2" name="quantity"/>
|
||||||
<field name="automatic_price"/>
|
<field colspan="2" name="uom_id"/>
|
||||||
<field name="specific_price" invisible="1"/>
|
<field colspan="2" name="automatic_price"/>
|
||||||
<field name="price_unit"
|
<field name="specific_price" invisible="1"/>
|
||||||
attrs="{'readonly': [('automatic_price', '=', True)]}"
|
<field colspan="2" name="price_unit"
|
||||||
colspan="2"/>
|
attrs="{'readonly': [('automatic_price', '=', True)]}"/>
|
||||||
<field name="discount" colspan="2"/>
|
<field colspan="2" name="discount"/>
|
||||||
</group>
|
|
||||||
<group name="recurrence_info">
|
|
||||||
<group>
|
|
||||||
<field name="recurring_invoicing_type"/>
|
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group col="4">
|
||||||
<label for="recurring_interval"/>
|
<field colspan="2" name="is_auto_renew"/>
|
||||||
<div>
|
<field colspan="2" name="is_canceled" invisible="1"/>
|
||||||
<field name="recurring_interval"
|
|
||||||
class="oe_inline"/>
|
|
||||||
<field name="recurring_rule_type"
|
|
||||||
class="oe_inline"/>
|
|
||||||
</div>
|
|
||||||
</group>
|
</group>
|
||||||
</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>
|
||||||
|
<field name="recurring_invoicing_type"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<label for="recurring_interval"/>
|
||||||
|
<div>
|
||||||
|
<field name="recurring_interval"
|
||||||
|
class="oe_inline" nolabel="1"/>
|
||||||
|
<field name="recurring_rule_type"
|
||||||
|
class="oe_inline" nolabel="1"/>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<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="name">Contract form</field>
|
||||||
<field name="model">account.analytic.account</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="mode">primary</field>
|
||||||
<field name="priority" eval="9999"/>
|
<field name="priority" eval="9999"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="partner_id" position="attributes">
|
<field name="partner_id" position="attributes">
|
||||||
<attribute name="attrs">{'required': [('recurring_invoices', '=', True)]}</attribute>
|
<attribute name="attrs">{'required': [('recurring_invoices',
|
||||||
|
'=', True)]}
|
||||||
|
</attribute>
|
||||||
</field>
|
</field>
|
||||||
<xpath expr="//div[@name='button_box']/.." position="before">
|
<xpath expr="//div[@name='button_box']/.." position="before">
|
||||||
<header>
|
<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>
|
</header>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr='//field[@name="code"]' position='before'>
|
<xpath expr='//field[@name="code"]' position='before'>
|
||||||
@@ -22,27 +27,30 @@
|
|||||||
<group name="main" position="after">
|
<group name="main" position="after">
|
||||||
<separator string="Recurring Invoices"
|
<separator string="Recurring Invoices"
|
||||||
attrs="{'invisible': [('recurring_invoices','!=',True)]}"
|
attrs="{'invisible': [('recurring_invoices','!=',True)]}"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<field name="recurring_invoices" class="oe_inline"/>
|
<field name="recurring_invoices" class="oe_inline"/>
|
||||||
<field name="create_invoice_visibility" invisible="1"/>
|
<field name="create_invoice_visibility" invisible="1"/>
|
||||||
<label for="recurring_invoices" />
|
<label for="recurring_invoices"/>
|
||||||
<button name="recurring_create_invoice"
|
<button name="recurring_create_invoice"
|
||||||
type="object"
|
type="object"
|
||||||
attrs="{'invisible': ['|', ('recurring_invoices', '!=', True), ('create_invoice_visibility', '=', False)]}"
|
attrs="{'invisible': ['|', ('recurring_invoices', '!=', True), ('create_invoice_visibility', '=', False)]}"
|
||||||
string="Create invoices"
|
string="Create invoices"
|
||||||
class="oe_link"
|
class="oe_link"
|
||||||
groups="base.group_no_one"
|
groups="base.group_no_one"
|
||||||
/>
|
/>
|
||||||
<button name="button_show_recurring_invoices"
|
<button name="contract.act_recurring_invoices"
|
||||||
type="object"
|
type="action"
|
||||||
attrs="{'invisible': [('recurring_invoices','!=',True)]}"
|
attrs="{'invisible': [('recurring_invoices','!=',True)]}"
|
||||||
string="⇒ Show recurring invoices"
|
string="⇒ Show recurring invoices"
|
||||||
class="oe_link"
|
class="oe_link"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<group col="4" attrs="{'invisible': [('recurring_invoices','!=',True)]}">
|
<group col="4"
|
||||||
<field name="contract_template_id" colspan="4" domain="['|', ('contract_type', '=', contract_type), ('contract_type', '=', False)]" context="{'default_contract_type': contract_type}"/>
|
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"
|
<field name="journal_id"
|
||||||
domain="[('type', '=', contract_type),('company_id', '=', company_id)]"
|
domain="[('type', '=', contract_type),('company_id', '=', company_id)]"
|
||||||
attrs="{'required': [('recurring_invoices', '=', True)]}"
|
attrs="{'required': [('recurring_invoices', '=', True)]}"
|
||||||
@@ -50,37 +58,85 @@
|
|||||||
<field name="pricelist_id"/>
|
<field name="pricelist_id"/>
|
||||||
<field name="recurring_next_date"/>
|
<field name="recurring_next_date"/>
|
||||||
<field name="date_end"/>
|
<field name="date_end"/>
|
||||||
<field name="company_id" groups="base.group_multi_company"/>
|
<field name="company_id"
|
||||||
|
groups="base.group_multi_company"/>
|
||||||
</group>
|
</group>
|
||||||
<label for="recurring_invoice_line_ids"
|
<label for="recurring_invoice_line_ids"
|
||||||
attrs="{'invisible': [('recurring_invoices','=',False)]}"
|
attrs="{'invisible': [('recurring_invoices','=',False)]}"
|
||||||
/>
|
/>
|
||||||
<div attrs="{'invisible': [('recurring_invoices','=',False)]}">
|
<div attrs="{'invisible': [('recurring_invoices','=',False)]}">
|
||||||
<field name="recurring_invoice_line_ids">
|
<field name="recurring_invoice_line_ids">
|
||||||
<tree string="Account Analytic Lines">
|
<tree decoration-muted="is_canceled">
|
||||||
<field name="sequence" widget="handle" />
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="product_id"/>
|
<field name="product_id"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="quantity"/>
|
<field name="quantity"/>
|
||||||
<field name="uom_id"/>
|
<field name="uom_id"/>
|
||||||
<field name="automatic_price"/>
|
<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="specific_price" invisible="1"/>
|
||||||
<field name="discount" groups="base.group_no_one" />
|
<field name="discount" groups="base.group_no_one"/>
|
||||||
<field name="price_subtotal"/>
|
<field name="price_subtotal"/>
|
||||||
<field name="recurring_interval" invisible="1"/>
|
<field name="recurring_interval" invisible="1"/>
|
||||||
<field name="recurring_rule_type" 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_start" required="1"/>
|
||||||
<field name="date_end"/>
|
<field name="date_end"/>
|
||||||
<field name="recurring_next_date" required="1"/>
|
<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>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</div>
|
</div>
|
||||||
<group string="Legend (for the markers inside invoice lines description)"
|
<group string="Legend (for the markers inside invoice lines description)"
|
||||||
name="group_legend" attrs="{'invisible': [('recurring_invoices','!=',True)]}">
|
name="group_legend"
|
||||||
<p colspan="2"> <strong>#START#</strong>: Start date of the invoiced period</p>
|
attrs="{'invisible': [('recurring_invoices','!=',True)]}">
|
||||||
<p colspan="2"> <strong>#END#</strong>: End date of the invoiced period</p>
|
<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>
|
||||||
</group>
|
</group>
|
||||||
</field>
|
</field>
|
||||||
@@ -89,14 +145,17 @@
|
|||||||
<record id="account_analytic_account_sale_form" model="ir.ui.view">
|
<record id="account_analytic_account_sale_form" model="ir.ui.view">
|
||||||
<field name="name">account.analytic.account.sale.form</field>
|
<field name="name">account.analytic.account.sale.form</field>
|
||||||
<field name="model">account.analytic.account</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="mode">primary</field>
|
||||||
<field name="priority" eval="20"/>
|
<field name="priority" eval="20"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="partner_id" position="attributes">
|
<field name="partner_id" position="attributes">
|
||||||
<attribute name="string">Customer</attribute>
|
<attribute name="string">Customer</attribute>
|
||||||
<attribute name="domain">[('customer', '=', True)]</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>
|
||||||
<field name="journal_id" position="attributes">
|
<field name="journal_id" position="attributes">
|
||||||
<attribute name="domain">[('type', '=', 'sale'),('company_id', '=', company_id)]</attribute>
|
<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">
|
<record id="account_analytic_account_purchase_form" model="ir.ui.view">
|
||||||
<field name="name">account.analytic.account.purchase.form</field>
|
<field name="name">account.analytic.account.purchase.form</field>
|
||||||
<field name="model">account.analytic.account</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="mode">primary</field>
|
||||||
<field name="priority" eval="20"/>
|
<field name="priority" eval="20"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="partner_id" position="attributes">
|
<field name="partner_id" position="attributes">
|
||||||
<attribute name="string">Supplier</attribute>
|
<attribute name="string">Supplier</attribute>
|
||||||
<attribute name="domain">[('supplier', '=', True)]</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>
|
||||||
<field name="journal_id" position="attributes">
|
<field name="journal_id" position="attributes">
|
||||||
<attribute name="domain">[('type', '=', 'purchase'),('company_id', '=', company_id)]</attribute>
|
<attribute name="domain">[('type', '=', 'purchase'),('company_id', '=', company_id)]</attribute>
|
||||||
</field>
|
</field>
|
||||||
<field name="product_id" position="attributes">
|
<field name="product_id" position="attributes">
|
||||||
<attribute name="domain">[('purchase_ok', '=', True)]</attribute>
|
<attribute name="domain">[('purchase_ok', '=', True)]
|
||||||
|
</attribute>
|
||||||
</field>
|
</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>
|
<attribute name="invisible">True</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
@@ -135,7 +199,8 @@
|
|||||||
<record id="view_account_analytic_account_journal_tree" model="ir.ui.view">
|
<record id="view_account_analytic_account_journal_tree" model="ir.ui.view">
|
||||||
<field name="name">Contract list</field>
|
<field name="name">Contract list</field>
|
||||||
<field name="model">account.analytic.account</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="mode">primary</field>
|
||||||
<field name="priority" eval="9999"/>
|
<field name="priority" eval="9999"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
@@ -146,13 +211,16 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Analytic Account search view for contract -->
|
<!-- 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="name">Contract search</field>
|
||||||
<field name="model">account.analytic.account</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="arch" type="xml">
|
||||||
<field name="partner_id" position="after">
|
<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"/>
|
string="Partner and dependents"/>
|
||||||
</field>
|
</field>
|
||||||
<field name="name" position="after">
|
<field name="name" position="after">
|
||||||
@@ -162,41 +230,47 @@
|
|||||||
<filter name="recurring_invoices"
|
<filter name="recurring_invoices"
|
||||||
string="Recurring Invoices"
|
string="Recurring Invoices"
|
||||||
domain="[('recurring_invoices','=',True)]"
|
domain="[('recurring_invoices','=',True)]"
|
||||||
/>
|
/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter name="not_finished"
|
<filter name="not_finished"
|
||||||
string="Valid"
|
string="Valid"
|
||||||
domain="['|', ('date_end', '=', False), ('date_end', '>=', time.strftime('%Y-%m-%d'))]"
|
domain="['|', ('date_end', '=', False), ('date_end', '>=', time.strftime('%Y-%m-%d'))]"
|
||||||
/>
|
/>
|
||||||
<filter name="finished"
|
<filter name="finished"
|
||||||
string="Finished"
|
string="Finished"
|
||||||
domain="[('date_end', '<', time.strftime('%Y-%m-%d'))]"
|
domain="[('date_end', '<', time.strftime('%Y-%m-%d'))]"
|
||||||
/>
|
/>
|
||||||
<group expand="0" string="Group By...">
|
<group expand="0" string="Group By...">
|
||||||
<filter name="next_invoice"
|
<filter name="next_invoice"
|
||||||
string="Next Invoice"
|
string="Next Invoice"
|
||||||
domain="[]"
|
domain="[]"
|
||||||
context="{'group_by':'recurring_next_date'}"
|
context="{'group_by':'recurring_next_date'}"
|
||||||
/>
|
/>
|
||||||
<filter name="date_end"
|
<filter name="date_end"
|
||||||
string="Date End"
|
string="Date End"
|
||||||
domain="[]"
|
domain="[]"
|
||||||
context="{'group_by':'date_end'}"
|
context="{'group_by':'date_end'}"
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Action Sales/Sales/Contracts -->
|
<!-- 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="name">Customer Contracts</field>
|
||||||
<field name="res_model">account.analytic.account</field>
|
<field name="res_model">account.analytic.account</field>
|
||||||
<field name="view_type">form</field>
|
<field name="view_type">form</field>
|
||||||
<field name="view_mode">tree,form</field>
|
<field name="view_mode">tree,form</field>
|
||||||
<field name="domain">[('contract_type', '=', 'sale')]</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="context">{'is_contract':1,
|
||||||
<field name="search_view_id" ref="view_account_analytic_account_contract_search"/>
|
'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">
|
<field name="help" type="html">
|
||||||
<p class="oe_view_nocontent_create">
|
<p class="oe_view_nocontent_create">
|
||||||
Click to create a new contract.
|
Click to create a new contract.
|
||||||
@@ -204,60 +278,77 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</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="sequence" eval="1"/>
|
||||||
<field name="view_mode">tree</field>
|
<field name="view_mode">tree</field>
|
||||||
<field name="view_id" ref="view_account_analytic_account_journal_tree"/>
|
<field name="view_id"
|
||||||
<field name="act_window_id" ref="action_account_analytic_sale_overdue_all"/>
|
ref="view_account_analytic_account_journal_tree"/>
|
||||||
|
<field name="act_window_id"
|
||||||
|
ref="action_account_analytic_sale_overdue_all"/>
|
||||||
</record>
|
</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="sequence" eval="2"/>
|
||||||
<field name="view_mode">form</field>
|
<field name="view_mode">form</field>
|
||||||
<field name="view_id" ref="account_analytic_account_sale_form"/>
|
<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>
|
</record>
|
||||||
|
|
||||||
<menuitem id="menu_action_account_analytic_sale_overdue_all"
|
<menuitem id="menu_action_account_analytic_sale_overdue_all"
|
||||||
parent="account.menu_finance_receivables"
|
parent="account.menu_finance_receivables"
|
||||||
action="action_account_analytic_sale_overdue_all"
|
action="action_account_analytic_sale_overdue_all"
|
||||||
sequence="99"
|
sequence="99"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Action Purchases/Purchases/Contracts -->
|
<!-- 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="name">Supplier Contracts</field>
|
||||||
<field name="res_model">account.analytic.account</field>
|
<field name="res_model">account.analytic.account</field>
|
||||||
<field name="view_type">form</field>
|
<field name="view_type">form</field>
|
||||||
<field name="view_mode">tree,form</field>
|
<field name="view_mode">tree,form</field>
|
||||||
<field name="domain">[('contract_type', '=', 'purchase')]</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="context">{'is_contract':1,
|
||||||
<field name="search_view_id" ref="view_account_analytic_account_contract_search"/>
|
'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">
|
<field name="help" type="html">
|
||||||
<p class="oe_view_nocontent_create">
|
<p class="oe_view_nocontent_create">
|
||||||
Click to create a new contract.
|
Click to create a new contract.
|
||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</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="sequence" eval="1"/>
|
||||||
<field name="view_mode">tree</field>
|
<field name="view_mode">tree</field>
|
||||||
<field name="view_id" ref="view_account_analytic_account_journal_tree"/>
|
<field name="view_id"
|
||||||
<field name="act_window_id" ref="action_account_analytic_purchase_overdue_all"/>
|
ref="view_account_analytic_account_journal_tree"/>
|
||||||
|
<field name="act_window_id"
|
||||||
|
ref="action_account_analytic_purchase_overdue_all"/>
|
||||||
</record>
|
</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="sequence" eval="2"/>
|
||||||
<field name="view_mode">form</field>
|
<field name="view_mode">form</field>
|
||||||
<field name="view_id" ref="account_analytic_account_purchase_form"/>
|
<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>
|
</record>
|
||||||
|
|
||||||
<menuitem id="menu_action_account_analytic_purchase_overdue_all"
|
<menuitem id="menu_action_account_analytic_purchase_overdue_all"
|
||||||
parent="account.menu_finance_payables"
|
parent="account.menu_finance_payables"
|
||||||
action="action_account_analytic_purchase_overdue_all"
|
action="action_account_analytic_purchase_overdue_all"
|
||||||
sequence="99"
|
sequence="99"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -8,19 +8,28 @@
|
|||||||
ref="account_abstract_analytic_contract_line_view_form"/>
|
ref="account_abstract_analytic_contract_line_view_form"/>
|
||||||
<field name="mode">primary</field>
|
<field name="mode">primary</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//sheet" position="before">
|
||||||
|
<header>
|
||||||
|
<field name="state" widget="statusbar"/>
|
||||||
|
</header>
|
||||||
|
</xpath>
|
||||||
<xpath expr="//form" position="attributes">
|
<xpath expr="//form" position="attributes">
|
||||||
<attribute name="string">Contract Line</attribute>
|
<attribute name="string">Contract Line</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//group[@name='recurrence_info']" position="inside">
|
<xpath expr="//group[@name='recurrence_info']" position="inside">
|
||||||
<group>
|
<group>
|
||||||
<field name="date_start" required="1"/>
|
<field name="date_start" required="1"/>
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="date_end"/>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="recurring_next_date"/>
|
<field name="recurring_next_date"/>
|
||||||
</group>
|
</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>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</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