mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[13.0][MIG] - migration product_contract
This commit is contained in:
@@ -3,22 +3,21 @@
|
|||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Recurring - Product Contract',
|
"name": "Recurring - Product Contract",
|
||||||
'version': '12.0.5.1.0',
|
"version": "13.0.1.0.0",
|
||||||
'category': 'Contract Management',
|
"category": "Contract Management",
|
||||||
'license': 'AGPL-3',
|
"license": "AGPL-3",
|
||||||
'author': "LasLabs, "
|
"author": "LasLabs, " "ACSONE SA/NV, " "Odoo Community Association (OCA)",
|
||||||
"ACSONE SA/NV, "
|
"website": "https://github.com/oca/contract",
|
||||||
"Odoo Community Association (OCA)",
|
"depends": ["product", "contract", "sale"],
|
||||||
'website': 'https://github.com/oca/contract',
|
"data": [
|
||||||
'depends': ['product', 'contract_sale'],
|
"views/res_config_settings.xml",
|
||||||
'data': [
|
"views/contract.xml",
|
||||||
'views/res_config_settings.xml',
|
"views/product_template.xml",
|
||||||
'views/contract.xml',
|
"views/sale_order.xml",
|
||||||
'views/product_template.xml',
|
|
||||||
'views/sale_order.xml'
|
|
||||||
],
|
],
|
||||||
'installable': True,
|
"installable": True,
|
||||||
'application': False,
|
"application": False,
|
||||||
"external_dependencies": {"python": ["dateutil"]},
|
"external_dependencies": {"python": ["dateutil"]},
|
||||||
|
"maintainers": ["sbejaoui"],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# Copyright 2019 ACSONE SA/NV
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from openupgradelib import openupgrade
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def migrate(cr, version):
|
|
||||||
xmlids_to_rename = [
|
|
||||||
(
|
|
||||||
'product_contract.account_analytic_account_recurring_form_form',
|
|
||||||
'product_contract.contract_contract_customer_form_view',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
openupgrade.rename_xmlids(cr, xmlids_to_rename)
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
from odoo.tools import parse_version
|
|
||||||
|
|
||||||
|
|
||||||
def migrate(cr, version):
|
|
||||||
if parse_version(version) == parse_version('12.0.2.0.0'):
|
|
||||||
# pre-paid/post-paid becomes significant for monthlylastday too,
|
|
||||||
# make sure it has the value that was implied for previous versions.
|
|
||||||
cr.execute(
|
|
||||||
"""\
|
|
||||||
UPDATE product_template
|
|
||||||
SET recurring_invoicing_type = 'post-paid'
|
|
||||||
WHERE recurring_rule_type = 'monthlylastday'
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Copyright 2019 Tecnativa - Ernesto Tejeda
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
from openupgradelib import openupgrade
|
|
||||||
|
|
||||||
|
|
||||||
@openupgrade.migrate()
|
|
||||||
def migrate(env, version):
|
|
||||||
# Convert contract_template_id field of the product_template table
|
|
||||||
# to a company dependent field
|
|
||||||
model_name = "product.template"
|
|
||||||
model_table_name = "product_template"
|
|
||||||
origin_field_name = "contract_template_id"
|
|
||||||
destination_field_name = "property_contract_template_id"
|
|
||||||
# Add ir.model.fields entry
|
|
||||||
env.cr.execute(
|
|
||||||
"SELECT id FROM ir_model WHERE model = %s", (model_name, ),
|
|
||||||
)
|
|
||||||
model_id = env.cr.fetchone()[0]
|
|
||||||
openupgrade.logged_query(
|
|
||||||
env.cr, """
|
|
||||||
INSERT INTO ir_model_fields (
|
|
||||||
model_id, model, name, field_description, ttype, state, relation
|
|
||||||
) VALUES (
|
|
||||||
%s, %s, %s, %s, %s, %s, %s
|
|
||||||
)""",
|
|
||||||
(model_id, model_name, destination_field_name, 'OU', "many2one",
|
|
||||||
'base', 'contract.template'),
|
|
||||||
)
|
|
||||||
openupgrade.convert_to_company_dependent(
|
|
||||||
env,
|
|
||||||
model_name,
|
|
||||||
origin_field_name,
|
|
||||||
destination_field_name,
|
|
||||||
model_table_name,
|
|
||||||
)
|
|
||||||
@@ -7,29 +7,24 @@ from odoo.tools.translate import _
|
|||||||
|
|
||||||
|
|
||||||
class ContractContract(models.Model):
|
class ContractContract(models.Model):
|
||||||
_inherit = 'contract.contract'
|
_inherit = "contract.contract"
|
||||||
|
|
||||||
sale_order_count = fields.Integer(compute="_compute_sale_order_count")
|
sale_order_count = fields.Integer(compute="_compute_sale_order_count")
|
||||||
|
|
||||||
@api.depends('contract_line_ids')
|
@api.depends("contract_line_ids")
|
||||||
def _compute_sale_order_count(self):
|
def _compute_sale_order_count(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
try:
|
try:
|
||||||
order_count = len(
|
order_count = len(
|
||||||
rec.contract_line_ids.mapped(
|
rec.contract_line_ids.mapped("sale_order_line_id.order_id")
|
||||||
'sale_order_line_id.order_id'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except AccessError:
|
except AccessError:
|
||||||
order_count = 0
|
order_count = 0
|
||||||
rec.sale_order_count = order_count
|
rec.sale_order_count = order_count
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def action_view_sales_orders(self):
|
def action_view_sales_orders(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
orders = self.contract_line_ids.mapped(
|
orders = self.contract_line_ids.mapped("sale_order_line_id.order_id")
|
||||||
'sale_order_line_id.order_id'
|
|
||||||
)
|
|
||||||
action = {
|
action = {
|
||||||
"name": _("Sales Orders"),
|
"name": _("Sales Orders"),
|
||||||
"view_mode": "tree,form",
|
"view_mode": "tree,form",
|
||||||
@@ -39,5 +34,5 @@ class ContractContract(models.Model):
|
|||||||
}
|
}
|
||||||
if len(orders) == 1:
|
if len(orders) == 1:
|
||||||
# If there is only one order, open it directly
|
# If there is only one order, open it directly
|
||||||
action.update({'view_mode': "form", "res_id": orders.id})
|
action.update({"view_mode": "form", "res_id": orders.id})
|
||||||
return action
|
return action
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from odoo import api, fields, models
|
|||||||
|
|
||||||
|
|
||||||
class ContractLine(models.Model):
|
class ContractLine(models.Model):
|
||||||
_inherit = 'contract.line'
|
_inherit = "contract.line"
|
||||||
_rec_name = 'display_name'
|
_rec_name = "display_name"
|
||||||
|
|
||||||
sale_order_line_id = fields.Many2one(
|
sale_order_line_id = fields.Many2one(
|
||||||
comodel_name="sale.order.line",
|
comodel_name="sale.order.line",
|
||||||
@@ -16,18 +16,14 @@ class ContractLine(models.Model):
|
|||||||
required=False,
|
required=False,
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
display_name = fields.Char(compute='_compute_display_name_2')
|
display_name = fields.Char(compute="_compute_display_name_2")
|
||||||
|
|
||||||
@api.multi
|
def _prepare_invoice_line(self, move_form):
|
||||||
def _prepare_invoice_line(self, invoice_id=False, invoice_values=False):
|
res = super(ContractLine, self)._prepare_invoice_line(move_form)
|
||||||
res = super(ContractLine, self)._prepare_invoice_line(
|
|
||||||
invoice_id=invoice_id, invoice_values=invoice_values,
|
|
||||||
)
|
|
||||||
if self.sale_order_line_id and res:
|
if self.sale_order_line_id and res:
|
||||||
res['sale_line_ids'] = [(6, 0, [self.sale_order_line_id.id])]
|
res["sale_line_ids"] = [(6, 0, [self.sale_order_line_id.id])]
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _get_auto_renew_rule_type(self):
|
def _get_auto_renew_rule_type(self):
|
||||||
"""monthly last day don't make sense for auto_renew_rule_type"""
|
"""monthly last day don't make sense for auto_renew_rule_type"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -35,15 +31,13 @@ class ContractLine(models.Model):
|
|||||||
return "monthly"
|
return "monthly"
|
||||||
return self.recurring_rule_type
|
return self.recurring_rule_type
|
||||||
|
|
||||||
@api.onchange('product_id')
|
@api.onchange("product_id")
|
||||||
def _onchange_product_id_recurring_info(self):
|
def _onchange_product_id_recurring_info(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
rec.date_start = fields.Date.today()
|
rec.date_start = fields.Date.today()
|
||||||
if rec.product_id.is_contract:
|
if rec.product_id.is_contract:
|
||||||
rec.recurring_rule_type = rec.product_id.recurring_rule_type
|
rec.recurring_rule_type = rec.product_id.recurring_rule_type
|
||||||
rec.recurring_invoicing_type = (
|
rec.recurring_invoicing_type = rec.product_id.recurring_invoicing_type
|
||||||
rec.product_id.recurring_invoicing_type
|
|
||||||
)
|
|
||||||
rec.recurring_interval = 1
|
rec.recurring_interval = 1
|
||||||
rec.is_auto_renew = rec.product_id.is_auto_renew
|
rec.is_auto_renew = rec.product_id.is_auto_renew
|
||||||
rec.auto_renew_interval = rec.product_id.auto_renew_interval
|
rec.auto_renew_interval = rec.product_id.auto_renew_interval
|
||||||
@@ -55,7 +49,7 @@ class ContractLine(models.Model):
|
|||||||
rec.product_id.termination_notice_rule_type
|
rec.product_id.termination_notice_rule_type
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends('name', 'date_start')
|
@api.depends("name", "date_start")
|
||||||
def _compute_display_name_2(self):
|
def _compute_display_name_2(self):
|
||||||
# FIXME: _compute_display_name depends on rec_name (display_name)
|
# FIXME: _compute_display_name depends on rec_name (display_name)
|
||||||
# and this trigger a WARNING : display_name depends on itself;
|
# and this trigger a WARNING : display_name depends on itself;
|
||||||
|
|||||||
@@ -2,78 +2,76 @@
|
|||||||
# Copyright 2018 ACSONE SA/NV.
|
# Copyright 2018 ACSONE SA/NV.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from odoo import api, fields, models, _
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
class ProductTemplate(models.Model):
|
class ProductTemplate(models.Model):
|
||||||
_inherit = 'product.template'
|
_inherit = "product.template"
|
||||||
|
|
||||||
is_contract = fields.Boolean('Is a contract')
|
is_contract = fields.Boolean("Is a contract")
|
||||||
property_contract_template_id = fields.Many2one(
|
property_contract_template_id = fields.Many2one(
|
||||||
comodel_name='contract.template',
|
comodel_name="contract.template",
|
||||||
string='Contract Template',
|
string="Contract Template",
|
||||||
company_dependent=True,
|
company_dependent=True,
|
||||||
)
|
)
|
||||||
default_qty = fields.Integer(string="Default Quantity", default=1)
|
default_qty = fields.Integer(string="Default Quantity", default=1)
|
||||||
recurring_rule_type = fields.Selection(
|
recurring_rule_type = fields.Selection(
|
||||||
[
|
[
|
||||||
('daily', 'Day(s)'),
|
("daily", "Day(s)"),
|
||||||
('weekly', 'Week(s)'),
|
("weekly", "Week(s)"),
|
||||||
('monthly', 'Month(s)'),
|
("monthly", "Month(s)"),
|
||||||
('monthlylastday', 'Month(s) last day'),
|
("monthlylastday", "Month(s) last day"),
|
||||||
('quarterly', 'Quarter(s)'),
|
("quarterly", "Quarter(s)"),
|
||||||
('semesterly', 'Semester(s)'),
|
("semesterly", "Semester(s)"),
|
||||||
('yearly', 'Year(s)'),
|
("yearly", "Year(s)"),
|
||||||
],
|
],
|
||||||
default='monthly',
|
default="monthly",
|
||||||
string='Invoice Every',
|
string="Invoice Every",
|
||||||
help="Specify Interval for automatic invoice generation.",
|
help="Specify Interval for automatic invoice generation.",
|
||||||
)
|
)
|
||||||
recurring_invoicing_type = fields.Selection(
|
recurring_invoicing_type = fields.Selection(
|
||||||
[('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')],
|
[("pre-paid", "Pre-paid"), ("post-paid", "Post-paid")],
|
||||||
default='pre-paid',
|
default="pre-paid",
|
||||||
string='Invoicing type',
|
string="Invoicing type",
|
||||||
help="Specify if process date is 'from' or 'to' invoicing date",
|
help="Specify if process date is 'from' or 'to' invoicing date",
|
||||||
)
|
)
|
||||||
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
|
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
|
||||||
termination_notice_interval = fields.Integer(
|
termination_notice_interval = fields.Integer(
|
||||||
default=1, string='Termination Notice Before'
|
default=1, string="Termination Notice Before"
|
||||||
)
|
)
|
||||||
termination_notice_rule_type = fields.Selection(
|
termination_notice_rule_type = fields.Selection(
|
||||||
[('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)')],
|
[("daily", "Day(s)"), ("weekly", "Week(s)"), ("monthly", "Month(s)")],
|
||||||
default='monthly',
|
default="monthly",
|
||||||
string='Termination Notice type',
|
string="Termination Notice type",
|
||||||
)
|
)
|
||||||
auto_renew_interval = fields.Integer(
|
auto_renew_interval = fields.Integer(
|
||||||
default=1,
|
default=1, string="Renew Every", help="Renew every (Days/Week/Month/Year)",
|
||||||
string='Renew Every',
|
|
||||||
help="Renew every (Days/Week/Month/Year)",
|
|
||||||
)
|
)
|
||||||
auto_renew_rule_type = fields.Selection(
|
auto_renew_rule_type = fields.Selection(
|
||||||
[
|
[
|
||||||
('daily', 'Day(s)'),
|
("daily", "Day(s)"),
|
||||||
('weekly', 'Week(s)'),
|
("weekly", "Week(s)"),
|
||||||
('monthly', 'Month(s)'),
|
("monthly", "Month(s)"),
|
||||||
('yearly', 'Year(s)'),
|
("yearly", "Year(s)"),
|
||||||
],
|
],
|
||||||
default='yearly',
|
default="yearly",
|
||||||
string='Renewal type',
|
string="Renewal type",
|
||||||
help="Specify Interval for automatic renewal.",
|
help="Specify Interval for automatic renewal.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
if 'is_contract' in vals and vals['is_contract'] is False:
|
if "is_contract" in vals and vals["is_contract"] is False:
|
||||||
for company in self.env['res.company'].search([]):
|
for company in self.env["res.company"].search([]):
|
||||||
self.with_context(force_company=company.id).write(
|
self.with_context(force_company=company.id).write(
|
||||||
{'property_contract_template_id': False})
|
{"property_contract_template_id": False}
|
||||||
|
)
|
||||||
super().write(vals)
|
super().write(vals)
|
||||||
|
|
||||||
@api.constrains('is_contract', 'type')
|
@api.constrains("is_contract", "type")
|
||||||
def _check_contract_product_type(self):
|
def _check_contract_product_type(self):
|
||||||
"""
|
"""
|
||||||
Contract product should be service type
|
Contract product should be service type
|
||||||
"""
|
"""
|
||||||
if self.is_contract and self.type != 'service':
|
if self.is_contract and self.type != "service":
|
||||||
raise ValidationError(_("Contract product should be service type"))
|
raise ValidationError(_("Contract product should be service type"))
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from odoo import fields, models
|
|||||||
|
|
||||||
class ResCompany(models.Model):
|
class ResCompany(models.Model):
|
||||||
|
|
||||||
_inherit = 'res.company'
|
_inherit = "res.company"
|
||||||
|
|
||||||
create_contract_at_sale_order_confirmation = fields.Boolean(
|
create_contract_at_sale_order_confirmation = fields.Boolean(
|
||||||
string="Automatically Create Contracts At Sale Order Confirmation",
|
string="Automatically Create Contracts At Sale Order Confirmation",
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ from odoo import fields, models
|
|||||||
|
|
||||||
class ResConfigSettings(models.TransientModel):
|
class ResConfigSettings(models.TransientModel):
|
||||||
|
|
||||||
_inherit = 'res.config.settings'
|
_inherit = "res.config.settings"
|
||||||
|
|
||||||
create_contract_at_sale_order_confirmation = fields.Boolean(
|
create_contract_at_sale_order_confirmation = fields.Boolean(
|
||||||
related="company_id.create_contract_at_sale_order_confirmation",
|
related="company_id.create_contract_at_sale_order_confirmation", readonly=False
|
||||||
readonly=False
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,37 +2,32 @@
|
|||||||
# Copyright 2018 ACSONE SA/NV.
|
# Copyright 2018 ACSONE SA/NV.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from odoo import _, fields, api, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
class SaleOrder(models.Model):
|
class SaleOrder(models.Model):
|
||||||
_inherit = 'sale.order'
|
_inherit = "sale.order"
|
||||||
|
|
||||||
is_contract = fields.Boolean(
|
is_contract = fields.Boolean(string="Is a contract", compute="_compute_is_contract")
|
||||||
string='Is a contract', compute='_compute_is_contract'
|
contract_count = fields.Integer(compute="_compute_contract_count")
|
||||||
)
|
need_contract_creation = fields.Boolean(compute="_compute_need_contract_creation")
|
||||||
contract_count = fields.Integer(compute='_compute_contract_count')
|
|
||||||
need_contract_creation = fields.Boolean(
|
|
||||||
compute='_compute_need_contract_creation'
|
|
||||||
)
|
|
||||||
|
|
||||||
@api.constrains('state')
|
@api.constrains("state")
|
||||||
def check_contact_is_not_terminated(self):
|
def check_contact_is_not_terminated(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.state not in (
|
if rec.state not in ("sale", "done", "cancel",) and rec.order_line.filtered(
|
||||||
'sale',
|
"contract_id.is_terminated"
|
||||||
'done',
|
):
|
||||||
'cancel',
|
|
||||||
) and rec.order_line.filtered('contract_id.is_terminated'):
|
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("You can't upsell or downsell a terminated contract")
|
_("You can't upsell or downsell a terminated contract")
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends('order_line.contract_id', 'state')
|
@api.depends("order_line.contract_id", "state")
|
||||||
def _compute_need_contract_creation(self):
|
def _compute_need_contract_creation(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.state in ('sale', 'done'):
|
rec.need_contract_creation = False
|
||||||
|
if rec.state in ("sale", "done"):
|
||||||
line_to_create_contract = rec.order_line.filtered(
|
line_to_create_contract = rec.order_line.filtered(
|
||||||
lambda r: not r.contract_id and r.product_id.is_contract
|
lambda r: not r.contract_id and r.product_id.is_contract
|
||||||
)
|
)
|
||||||
@@ -40,38 +35,35 @@ class SaleOrder(models.Model):
|
|||||||
lambda r: r.contract_id
|
lambda r: r.contract_id
|
||||||
and r.product_id.is_contract
|
and r.product_id.is_contract
|
||||||
and r
|
and r
|
||||||
not in r.contract_id.contract_line_ids.mapped(
|
not in r.contract_id.contract_line_ids.mapped("sale_order_line_id")
|
||||||
'sale_order_line_id'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if line_to_create_contract or line_to_update_contract:
|
if line_to_create_contract or line_to_update_contract:
|
||||||
rec.need_contract_creation = True
|
rec.need_contract_creation = True
|
||||||
|
|
||||||
@api.depends('order_line')
|
@api.depends("order_line")
|
||||||
def _compute_is_contract(self):
|
def _compute_is_contract(self):
|
||||||
self.is_contract = any(self.order_line.mapped('is_contract'))
|
self.is_contract = any(self.order_line.mapped("is_contract"))
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _prepare_contract_value(self, contract_template):
|
def _prepare_contract_value(self, contract_template):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
'name': '{template_name}: {sale_name}'.format(
|
"name": "{template_name}: {sale_name}".format(
|
||||||
template_name=contract_template.name, sale_name=self.name
|
template_name=contract_template.name, sale_name=self.name
|
||||||
),
|
),
|
||||||
'partner_id': self.partner_id.id,
|
"partner_id": self.partner_id.id,
|
||||||
'company_id': self.company_id.id,
|
"company_id": self.company_id.id,
|
||||||
'contract_template_id': contract_template.id,
|
"contract_template_id": contract_template.id,
|
||||||
'user_id': self.user_id.id,
|
"user_id": self.user_id.id,
|
||||||
'payment_term_id': self.payment_term_id.id,
|
"payment_term_id": self.payment_term_id.id,
|
||||||
'fiscal_position_id': self.fiscal_position_id.id,
|
"fiscal_position_id": self.fiscal_position_id.id,
|
||||||
'invoice_partner_id': self.partner_invoice_id.id,
|
"invoice_partner_id": self.partner_invoice_id.id,
|
||||||
|
"line_recurrence": self.partner_invoice_id.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def action_create_contract(self):
|
def action_create_contract(self):
|
||||||
contract_model = self.env['contract.contract']
|
contract_model = self.env["contract.contract"]
|
||||||
contracts = self.env['contract.contract']
|
contracts = self.env["contract.contract"]
|
||||||
for rec in self.filtered('is_contract'):
|
for rec in self.filtered("is_contract"):
|
||||||
line_to_create_contract = rec.order_line.filtered(
|
line_to_create_contract = rec.order_line.filtered(
|
||||||
lambda r: not r.contract_id and r.product_id.is_contract
|
lambda r: not r.contract_id and r.product_id.is_contract
|
||||||
)
|
)
|
||||||
@@ -79,9 +71,7 @@ class SaleOrder(models.Model):
|
|||||||
lambda r: r.contract_id
|
lambda r: r.contract_id
|
||||||
and r.product_id.is_contract
|
and r.product_id.is_contract
|
||||||
and r
|
and r
|
||||||
not in r.contract_id.contract_line_ids.mapped(
|
not in r.contract_id.contract_line_ids.mapped("sale_order_line_id")
|
||||||
'sale_order_line_id'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
contract_templates = self.env["contract.template"]
|
contract_templates = self.env["contract.template"]
|
||||||
for order_line in line_to_create_contract:
|
for order_line in line_to_create_contract:
|
||||||
@@ -90,19 +80,18 @@ class SaleOrder(models.Model):
|
|||||||
).property_contract_template_id
|
).property_contract_template_id
|
||||||
if not contract_template:
|
if not contract_template:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("You must specify a contract "
|
_(
|
||||||
"template for '{}' product in '{}' company.").format(
|
"You must specify a contract "
|
||||||
order_line.product_id.name,
|
"template for '{}' product in '{}' company."
|
||||||
rec.company_id.name
|
).format(order_line.product_id.name, rec.company_id.name)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
contract_templates |= contract_template
|
contract_templates |= contract_template
|
||||||
for contract_template in contract_templates:
|
for contract_template in contract_templates:
|
||||||
order_lines = line_to_create_contract.filtered(
|
order_lines = line_to_create_contract.filtered(
|
||||||
lambda r, template=contract_template:
|
lambda r, template=contract_template: r.product_id.with_context(
|
||||||
r.product_id.with_context(
|
|
||||||
force_company=r.order_id.company_id.id
|
force_company=r.order_id.company_id.id
|
||||||
).property_contract_template_id == template
|
).property_contract_template_id
|
||||||
|
== template
|
||||||
)
|
)
|
||||||
contract = contract_model.create(
|
contract = contract_model.create(
|
||||||
rec._prepare_contract_value(contract_template)
|
rec._prepare_contract_value(contract_template)
|
||||||
@@ -111,39 +100,32 @@ class SaleOrder(models.Model):
|
|||||||
contract._onchange_contract_template_id()
|
contract._onchange_contract_template_id()
|
||||||
contract._onchange_contract_type()
|
contract._onchange_contract_type()
|
||||||
order_lines.create_contract_line(contract)
|
order_lines.create_contract_line(contract)
|
||||||
order_lines.write({'contract_id': contract.id})
|
order_lines.write({"contract_id": contract.id})
|
||||||
for line in line_to_update_contract:
|
for line in line_to_update_contract:
|
||||||
line.create_contract_line(line.contract_id)
|
line.create_contract_line(line.contract_id)
|
||||||
return contracts
|
return contracts
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def action_confirm(self):
|
def action_confirm(self):
|
||||||
""" If we have a contract in the order, set it up """
|
""" If we have a contract in the order, set it up """
|
||||||
self.filtered(
|
self.filtered(
|
||||||
lambda order: (
|
lambda order: (order.company_id.create_contract_at_sale_order_confirmation)
|
||||||
order.company_id.create_contract_at_sale_order_confirmation
|
|
||||||
)
|
|
||||||
).action_create_contract()
|
).action_create_contract()
|
||||||
return super(SaleOrder, self).action_confirm()
|
return super(SaleOrder, self).action_confirm()
|
||||||
|
|
||||||
@api.multi
|
|
||||||
@api.depends("order_line")
|
@api.depends("order_line")
|
||||||
def _compute_contract_count(self):
|
def _compute_contract_count(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
rec.contract_count = len(
|
rec.contract_count = len(
|
||||||
rec.order_line.mapped('contract_id').filtered(
|
rec.order_line.mapped("contract_id").filtered(lambda r: r.active)
|
||||||
lambda r: r.active))
|
)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def action_show_contracts(self):
|
def action_show_contracts(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
action = self.env.ref(
|
action = self.env.ref("contract.action_customer_contract").read()[0]
|
||||||
"contract.action_customer_contract"
|
|
||||||
).read()[0]
|
|
||||||
contracts = (
|
contracts = (
|
||||||
self.env['contract.line']
|
self.env["contract.line"]
|
||||||
.search([('sale_order_line_id', 'in', self.order_line.ids)])
|
.search([("sale_order_line_id", "in", self.order_line.ids)])
|
||||||
.mapped('contract_id')
|
.mapped("contract_id")
|
||||||
)
|
)
|
||||||
action["domain"] = [("id", "in", contracts.ids)]
|
action["domain"] = [("id", "in", contracts.ids)]
|
||||||
if len(contracts) == 1:
|
if len(contracts) == 1:
|
||||||
@@ -152,9 +134,7 @@ class SaleOrder(models.Model):
|
|||||||
{
|
{
|
||||||
"res_id": contracts.id,
|
"res_id": contracts.id,
|
||||||
"view_mode": "form",
|
"view_mode": "form",
|
||||||
"views": filter(
|
"views": filter(lambda view: view[1] == "form", action["views"]),
|
||||||
lambda view: view[1] == 'form', action['views']
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return action
|
return action
|
||||||
|
|||||||
@@ -3,47 +3,48 @@
|
|||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
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
|
||||||
|
|
||||||
|
|
||||||
class SaleOrderLine(models.Model):
|
class SaleOrderLine(models.Model):
|
||||||
_inherit = 'sale.order.line'
|
_inherit = "sale.order.line"
|
||||||
|
|
||||||
is_contract = fields.Boolean(
|
is_contract = fields.Boolean(
|
||||||
string='Is a contract', related="product_id.is_contract"
|
string="Is a contract", related="product_id.is_contract"
|
||||||
)
|
)
|
||||||
contract_id = fields.Many2one(
|
contract_id = fields.Many2one(
|
||||||
comodel_name='contract.contract', string='Contract', copy=False
|
comodel_name="contract.contract", string="Contract", copy=False
|
||||||
)
|
)
|
||||||
contract_template_id = fields.Many2one(
|
contract_template_id = fields.Many2one(
|
||||||
comodel_name='contract.template',
|
comodel_name="contract.template",
|
||||||
string='Contract Template',
|
string="Contract Template",
|
||||||
compute='_compute_contract_template_id',
|
compute="_compute_contract_template_id",
|
||||||
)
|
)
|
||||||
recurring_rule_type = fields.Selection(
|
recurring_rule_type = fields.Selection(
|
||||||
[
|
[
|
||||||
('daily', 'Day(s)'),
|
("daily", "Day(s)"),
|
||||||
('weekly', 'Week(s)'),
|
("weekly", "Week(s)"),
|
||||||
('monthly', 'Month(s)'),
|
("monthly", "Month(s)"),
|
||||||
('monthlylastday', 'Month(s) last day'),
|
("monthlylastday", "Month(s) last day"),
|
||||||
('quarterly', 'Quarter(s)'),
|
("quarterly", "Quarter(s)"),
|
||||||
('semesterly', 'Semester(s)'),
|
("semesterly", "Semester(s)"),
|
||||||
('yearly', 'Year(s)'),
|
("yearly", "Year(s)"),
|
||||||
],
|
],
|
||||||
default='monthly',
|
default="monthly",
|
||||||
string='Invoice Every',
|
string="Invoice Every",
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
recurring_invoicing_type = fields.Selection(
|
recurring_invoicing_type = fields.Selection(
|
||||||
[('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')],
|
[("pre-paid", "Pre-paid"), ("post-paid", "Post-paid")],
|
||||||
default='pre-paid',
|
default="pre-paid",
|
||||||
string='Invoicing type',
|
string="Invoicing type",
|
||||||
help="Specify if process date is 'from' or 'to' invoicing date",
|
help="Specify if process date is 'from' or 'to' invoicing date",
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
date_start = fields.Date(string='Date Start')
|
date_start = fields.Date(string="Date Start")
|
||||||
date_end = fields.Date(string='Date End')
|
date_end = fields.Date(string="Date End")
|
||||||
|
|
||||||
contract_line_id = fields.Many2one(
|
contract_line_id = fields.Many2one(
|
||||||
comodel_name="contract.line",
|
comodel_name="contract.line",
|
||||||
@@ -53,42 +54,38 @@ class SaleOrderLine(models.Model):
|
|||||||
)
|
)
|
||||||
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
|
is_auto_renew = fields.Boolean(string="Auto Renew", default=False)
|
||||||
auto_renew_interval = fields.Integer(
|
auto_renew_interval = fields.Integer(
|
||||||
default=1,
|
default=1, string="Renew Every", help="Renew every (Days/Week/Month/Year)",
|
||||||
string='Renew Every',
|
|
||||||
help="Renew every (Days/Week/Month/Year)",
|
|
||||||
)
|
)
|
||||||
auto_renew_rule_type = fields.Selection(
|
auto_renew_rule_type = fields.Selection(
|
||||||
[
|
[
|
||||||
('daily', 'Day(s)'),
|
("daily", "Day(s)"),
|
||||||
('weekly', 'Week(s)'),
|
("weekly", "Week(s)"),
|
||||||
('monthly', 'Month(s)'),
|
("monthly", "Month(s)"),
|
||||||
('yearly', 'Year(s)'),
|
("yearly", "Year(s)"),
|
||||||
],
|
],
|
||||||
default='yearly',
|
default="yearly",
|
||||||
string='Renewal type',
|
string="Renewal type",
|
||||||
help="Specify Interval for automatic renewal.",
|
help="Specify Interval for automatic renewal.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.constrains('contract_id')
|
@api.constrains("contract_id")
|
||||||
def check_contact_is_not_terminated(self):
|
def check_contact_is_not_terminated(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if (
|
if (
|
||||||
rec.order_id.state not in ('sale', 'done', 'cancel')
|
rec.order_id.state not in ("sale", "done", "cancel")
|
||||||
and rec.contract_id.is_terminated
|
and rec.contract_id.is_terminated
|
||||||
):
|
):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("You can't upsell or downsell a terminated contract")
|
_("You can't upsell or downsell a terminated contract")
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.multi
|
@api.depends("product_id")
|
||||||
@api.depends('product_id')
|
|
||||||
def _compute_contract_template_id(self):
|
def _compute_contract_template_id(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
rec.contract_template_id = rec.product_id.with_context(
|
rec.contract_template_id = rec.product_id.with_context(
|
||||||
force_company=rec.order_id.company_id.id
|
force_company=rec.order_id.company_id.id
|
||||||
).property_contract_template_id
|
).property_contract_template_id
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _get_auto_renew_rule_type(self):
|
def _get_auto_renew_rule_type(self):
|
||||||
"""monthly last day don't make sense for auto_renew_rule_type"""
|
"""monthly last day don't make sense for auto_renew_rule_type"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -96,52 +93,43 @@ class SaleOrderLine(models.Model):
|
|||||||
return "monthly"
|
return "monthly"
|
||||||
return self.recurring_rule_type
|
return self.recurring_rule_type
|
||||||
|
|
||||||
@api.onchange('product_id')
|
@api.onchange("product_id")
|
||||||
def onchange_product(self):
|
def onchange_product(self):
|
||||||
contract_line_model = self.env['contract.line']
|
contract_line_model = self.env["contract.line"]
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.product_id.is_contract:
|
if rec.product_id.is_contract:
|
||||||
rec.product_uom_qty = rec.product_id.default_qty
|
rec.product_uom_qty = rec.product_id.default_qty
|
||||||
rec.recurring_rule_type = rec.product_id.recurring_rule_type
|
rec.recurring_rule_type = rec.product_id.recurring_rule_type
|
||||||
rec.recurring_invoicing_type = (
|
rec.recurring_invoicing_type = rec.product_id.recurring_invoicing_type
|
||||||
rec.product_id.recurring_invoicing_type
|
|
||||||
)
|
|
||||||
rec.date_start = rec.date_start or fields.Date.today()
|
rec.date_start = rec.date_start or fields.Date.today()
|
||||||
|
|
||||||
rec.date_end = (
|
rec.date_end = (
|
||||||
rec.date_start
|
rec.date_start
|
||||||
+ contract_line_model.get_relative_delta(
|
+ contract_line_model.get_relative_delta(
|
||||||
rec._get_auto_renew_rule_type(),
|
rec._get_auto_renew_rule_type(), int(rec.product_uom_qty),
|
||||||
int(rec.product_uom_qty),
|
|
||||||
)
|
)
|
||||||
- relativedelta(days=1)
|
- relativedelta(days=1)
|
||||||
)
|
)
|
||||||
rec.is_auto_renew = rec.product_id.is_auto_renew
|
rec.is_auto_renew = rec.product_id.is_auto_renew
|
||||||
if rec.is_auto_renew:
|
if rec.is_auto_renew:
|
||||||
rec.auto_renew_interval = (
|
rec.auto_renew_interval = rec.product_id.auto_renew_interval
|
||||||
rec.product_id.auto_renew_interval
|
rec.auto_renew_rule_type = rec.product_id.auto_renew_rule_type
|
||||||
)
|
|
||||||
rec.auto_renew_rule_type = (
|
|
||||||
rec.product_id.auto_renew_rule_type
|
|
||||||
)
|
|
||||||
|
|
||||||
@api.onchange('date_start', 'product_uom_qty', 'recurring_rule_type')
|
@api.onchange("date_start", "product_uom_qty", "recurring_rule_type")
|
||||||
def onchange_date_start(self):
|
def onchange_date_start(self):
|
||||||
contract_line_model = self.env['contract.line']
|
contract_line_model = self.env["contract.line"]
|
||||||
for rec in self.filtered('product_id.is_contract'):
|
for rec in self.filtered("product_id.is_contract"):
|
||||||
if not rec.date_start:
|
if not rec.date_start:
|
||||||
rec.date_end = False
|
rec.date_end = False
|
||||||
else:
|
else:
|
||||||
rec.date_end = (
|
rec.date_end = (
|
||||||
rec.date_start
|
rec.date_start
|
||||||
+ contract_line_model.get_relative_delta(
|
+ contract_line_model.get_relative_delta(
|
||||||
rec._get_auto_renew_rule_type(),
|
rec._get_auto_renew_rule_type(), int(rec.product_uom_qty),
|
||||||
int(rec.product_uom_qty),
|
|
||||||
)
|
)
|
||||||
- relativedelta(days=1)
|
- relativedelta(days=1)
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _prepare_contract_line_values(
|
def _prepare_contract_line_values(
|
||||||
self, contract, predecessor_contract_line_id=False
|
self, contract, predecessor_contract_line_id=False
|
||||||
):
|
):
|
||||||
@@ -152,23 +140,19 @@ class SaleOrderLine(models.Model):
|
|||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
recurring_next_date = self.env[
|
recurring_next_date = self.env[
|
||||||
'contract.line'
|
"contract.line"
|
||||||
]._compute_first_recurring_next_date(
|
]._compute_first_recurring_next_date(
|
||||||
self.date_start or fields.Date.today(),
|
self.date_start or fields.Date.today(),
|
||||||
self.recurring_invoicing_type,
|
self.recurring_invoicing_type,
|
||||||
self.recurring_rule_type,
|
self.recurring_rule_type,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
termination_notice_interval = (
|
termination_notice_interval = self.product_id.termination_notice_interval
|
||||||
self.product_id.termination_notice_interval
|
termination_notice_rule_type = self.product_id.termination_notice_rule_type
|
||||||
)
|
|
||||||
termination_notice_rule_type = (
|
|
||||||
self.product_id.termination_notice_rule_type
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
'sequence': self.sequence,
|
"sequence": self.sequence,
|
||||||
'product_id': self.product_id.id,
|
"product_id": self.product_id.id,
|
||||||
'name': self.name,
|
"name": self.name,
|
||||||
# The quantity on the generated contract line is 1, as it
|
# The quantity on the generated contract line is 1, as it
|
||||||
# correspond to the most common use cases:
|
# correspond to the most common use cases:
|
||||||
# - quantity on the SO line = number of periods sold and unit
|
# - quantity on the SO line = number of periods sold and unit
|
||||||
@@ -181,31 +165,30 @@ class SaleOrderLine(models.Model):
|
|||||||
# quantity formula, in which case the quantity on the contract
|
# quantity formula, in which case the quantity on the contract
|
||||||
# line is not used
|
# line is not used
|
||||||
# Other use cases are easy to implement by overriding this method.
|
# Other use cases are easy to implement by overriding this method.
|
||||||
'quantity': 1.0,
|
"quantity": 1.0,
|
||||||
'uom_id': self.product_uom.id,
|
"uom_id": self.product_uom.id,
|
||||||
'price_unit': self.price_unit,
|
"price_unit": self.price_unit,
|
||||||
'discount': self.discount,
|
"discount": self.discount,
|
||||||
'date_end': self.date_end,
|
"date_end": self.date_end,
|
||||||
'date_start': self.date_start or fields.Date.today(),
|
"date_start": self.date_start or fields.Date.today(),
|
||||||
'recurring_next_date': recurring_next_date,
|
"recurring_next_date": recurring_next_date,
|
||||||
'recurring_interval': 1,
|
"recurring_interval": 1,
|
||||||
'recurring_invoicing_type': self.recurring_invoicing_type,
|
"recurring_invoicing_type": self.recurring_invoicing_type,
|
||||||
'recurring_rule_type': self.recurring_rule_type,
|
"recurring_rule_type": self.recurring_rule_type,
|
||||||
'is_auto_renew': self.is_auto_renew,
|
"is_auto_renew": self.is_auto_renew,
|
||||||
'auto_renew_interval': self.auto_renew_interval,
|
"auto_renew_interval": self.auto_renew_interval,
|
||||||
'auto_renew_rule_type': self.auto_renew_rule_type,
|
"auto_renew_rule_type": self.auto_renew_rule_type,
|
||||||
'termination_notice_interval': termination_notice_interval,
|
"termination_notice_interval": termination_notice_interval,
|
||||||
'termination_notice_rule_type': termination_notice_rule_type,
|
"termination_notice_rule_type": termination_notice_rule_type,
|
||||||
'contract_id': contract.id,
|
"contract_id": contract.id,
|
||||||
'sale_order_line_id': self.id,
|
"sale_order_line_id": self.id,
|
||||||
'predecessor_contract_line_id': predecessor_contract_line_id,
|
"predecessor_contract_line_id": predecessor_contract_line_id,
|
||||||
'analytic_account_id': self.order_id.analytic_account_id.id,
|
"analytic_account_id": self.order_id.analytic_account_id.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def create_contract_line(self, contract):
|
def create_contract_line(self, contract):
|
||||||
contract_line_model = self.env['contract.line']
|
contract_line_model = self.env["contract.line"]
|
||||||
contract_line = self.env['contract.line']
|
contract_line = self.env["contract.line"]
|
||||||
predecessor_contract_line = False
|
predecessor_contract_line = False
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.contract_line_id:
|
if rec.contract_line_id:
|
||||||
@@ -221,9 +204,7 @@ class SaleOrderLine(models.Model):
|
|||||||
not rec.contract_line_id.date_end
|
not rec.contract_line_id.date_end
|
||||||
or rec.date_start <= rec.contract_line_id.date_end
|
or rec.date_start <= rec.contract_line_id.date_end
|
||||||
):
|
):
|
||||||
rec.contract_line_id.stop(
|
rec.contract_line_id.stop(rec.date_start - relativedelta(days=1))
|
||||||
rec.date_start - relativedelta(days=1)
|
|
||||||
)
|
|
||||||
predecessor_contract_line = rec.contract_line_id
|
predecessor_contract_line = rec.contract_line_id
|
||||||
if predecessor_contract_line:
|
if predecessor_contract_line:
|
||||||
new_contract_line = contract_line_model.create(
|
new_contract_line = contract_line_model.create(
|
||||||
@@ -231,9 +212,7 @@ class SaleOrderLine(models.Model):
|
|||||||
contract, predecessor_contract_line.id
|
contract, predecessor_contract_line.id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
predecessor_contract_line.successor_contract_line_id = (
|
predecessor_contract_line.successor_contract_line_id = new_contract_line
|
||||||
new_contract_line
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
new_contract_line = contract_line_model.create(
|
new_contract_line = contract_line_model.create(
|
||||||
rec._prepare_contract_line_values(contract)
|
rec._prepare_contract_line_values(contract)
|
||||||
@@ -241,7 +220,7 @@ class SaleOrderLine(models.Model):
|
|||||||
contract_line |= new_contract_line
|
contract_line |= new_contract_line
|
||||||
return contract_line
|
return contract_line
|
||||||
|
|
||||||
@api.constrains('contract_id')
|
@api.constrains("contract_id")
|
||||||
def _check_contract_sale_partner(self):
|
def _check_contract_sale_partner(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.contract_id:
|
if rec.contract_id:
|
||||||
@@ -253,14 +232,13 @@ class SaleOrderLine(models.Model):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.constrains('product_id', 'contract_id')
|
@api.constrains("product_id", "contract_id")
|
||||||
def _check_contract_sale_contract_template(self):
|
def _check_contract_sale_contract_template(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.contract_id:
|
if rec.contract_id:
|
||||||
if (
|
if (
|
||||||
rec.contract_id.contract_template_id
|
rec.contract_id.contract_template_id
|
||||||
and rec.contract_template_id
|
and rec.contract_template_id != rec.contract_id.contract_template_id
|
||||||
!= rec.contract_id.contract_template_id
|
|
||||||
):
|
):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Contract product has different contract template")
|
_("Contract product has different contract template")
|
||||||
@@ -268,27 +246,26 @@ class SaleOrderLine(models.Model):
|
|||||||
|
|
||||||
def _compute_invoice_status(self):
|
def _compute_invoice_status(self):
|
||||||
res = super(SaleOrderLine, self)._compute_invoice_status()
|
res = super(SaleOrderLine, self)._compute_invoice_status()
|
||||||
for line in self.filtered('contract_id'):
|
for line in self.filtered("contract_id"):
|
||||||
line.invoice_status = 'no'
|
line.invoice_status = "no"
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def invoice_line_create(self, invoice_id, qty):
|
def invoice_line_create(self, invoice_id, qty):
|
||||||
return super(
|
return super(
|
||||||
SaleOrderLine, self.filtered(lambda l: not l.contract_id)
|
SaleOrderLine, self.filtered(lambda l: not l.contract_id)
|
||||||
).invoice_line_create(invoice_id, qty)
|
).invoice_line_create(invoice_id, qty)
|
||||||
|
|
||||||
@api.depends(
|
@api.depends(
|
||||||
'qty_invoiced',
|
"qty_invoiced",
|
||||||
'qty_delivered',
|
"qty_delivered",
|
||||||
'product_uom_qty',
|
"product_uom_qty",
|
||||||
'order_id.state',
|
"order_id.state",
|
||||||
'product_id.is_contract',
|
"product_id.is_contract",
|
||||||
)
|
)
|
||||||
def _get_to_invoice_qty(self):
|
def _get_to_invoice_qty(self):
|
||||||
"""
|
"""
|
||||||
sale line linked to contracts must not be invoiced from sale order
|
sale line linked to contracts must not be invoiced from sale order
|
||||||
"""
|
"""
|
||||||
res = super()._get_to_invoice_qty()
|
res = super()._get_to_invoice_qty()
|
||||||
self.filtered('product_id.is_contract').update({'qty_to_invoice': 0.0})
|
self.filtered("product_id.is_contract").update({"qty_to_invoice": 0.0})
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -2,18 +2,16 @@
|
|||||||
# Copyright 2018 ACSONE SA/NV.
|
# Copyright 2018 ACSONE SA/NV.
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from odoo.tests.common import TransactionCase
|
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
class TestProductTemplate(TransactionCase):
|
class TestProductTemplate(TransactionCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestProductTemplate, self).setUp()
|
super(TestProductTemplate, self).setUp()
|
||||||
self.service_product = self.env.ref('product.product_product_1')
|
self.service_product = self.env.ref("product.product_product_1")
|
||||||
self.consu_product = self.env.ref('product.product_product_5')
|
self.consu_product = self.env.ref("product.product_product_5")
|
||||||
self.contract = self.env['contract.template'].create(
|
self.contract = self.env["contract.template"].create({"name": "Test"})
|
||||||
{'name': 'Test'}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_change_is_contract(self):
|
def test_change_is_contract(self):
|
||||||
""" It should verify that the property_contract_template_id
|
""" It should verify that the property_contract_template_id
|
||||||
@@ -22,8 +20,7 @@ class TestProductTemplate(TransactionCase):
|
|||||||
self.service_product.is_contract = True
|
self.service_product.is_contract = True
|
||||||
self.service_product.property_contract_template_id = self.contract.id
|
self.service_product.property_contract_template_id = self.contract.id
|
||||||
self.service_product.is_contract = False
|
self.service_product.is_contract = False
|
||||||
self.assertEquals(len(
|
self.assertEquals(len(self.service_product.property_contract_template_id), 0)
|
||||||
self.service_product.property_contract_template_id), 0)
|
|
||||||
|
|
||||||
def test_check_contract_product_type(self):
|
def test_check_contract_product_type(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -3,62 +3,61 @@
|
|||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from odoo.tests.common import TransactionCase
|
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
from odoo.fields import Date
|
from odoo.fields import Date
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
class TestSaleOrder(TransactionCase):
|
class TestSaleOrder(TransactionCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestSaleOrder, self).setUp()
|
super(TestSaleOrder, self).setUp()
|
||||||
self.product1 = self.env.ref('product.product_product_1')
|
self.product1 = self.env.ref("product.product_product_1")
|
||||||
self.product2 = self.env.ref('product.product_product_2')
|
self.product2 = self.env.ref("product.product_product_2")
|
||||||
self.sale = self.env.ref('sale.sale_order_2')
|
self.sale = self.env.ref("sale.sale_order_2")
|
||||||
self.contract_template1 = self.env['contract.template'].create(
|
self.contract_template1 = self.env["contract.template"].create(
|
||||||
{'name': 'Template 1'}
|
{"name": "Template 1"}
|
||||||
)
|
)
|
||||||
self.contract_template2 = self.env['contract.template'].create(
|
self.contract_template2 = self.env["contract.template"].create(
|
||||||
{
|
{
|
||||||
'name': 'Template 2',
|
"name": "Template 2",
|
||||||
'contract_line_ids': [
|
"contract_line_ids": [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
{
|
{
|
||||||
'product_id': self.product2.id,
|
"product_id": self.product2.id,
|
||||||
'name': 'Services from #START# to #END#',
|
"name": "Services from #START# to #END#",
|
||||||
'quantity': 1,
|
"quantity": 1,
|
||||||
'uom_id': self.product2.uom_id.id,
|
"uom_id": self.product2.uom_id.id,
|
||||||
'price_unit': 100,
|
"price_unit": 100,
|
||||||
'discount': 50,
|
"discount": 50,
|
||||||
'recurring_rule_type': 'yearly',
|
"recurring_rule_type": "yearly",
|
||||||
'recurring_interval': 1,
|
"recurring_interval": 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.product1.with_context(
|
self.product1.with_context(force_company=self.sale.company_id.id).write(
|
||||||
force_company=self.sale.company_id.id).write(
|
|
||||||
{
|
{
|
||||||
'is_contract': True,
|
"is_contract": True,
|
||||||
'default_qty': 12,
|
"default_qty": 12,
|
||||||
'recurring_rule_type': "monthlylastday",
|
"recurring_rule_type": "monthlylastday",
|
||||||
'recurring_invoicing_type': "post-paid",
|
"recurring_invoicing_type": "post-paid",
|
||||||
'property_contract_template_id': self.contract_template1.id,
|
"property_contract_template_id": self.contract_template1.id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.product2.with_context(
|
self.product2.with_context(force_company=self.sale.company_id.id).write(
|
||||||
force_company=self.sale.company_id.id).write(
|
|
||||||
{
|
{
|
||||||
'is_contract': True,
|
"is_contract": True,
|
||||||
'property_contract_template_id': self.contract_template2.id,
|
"property_contract_template_id": self.contract_template2.id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.order_line1 = self.sale.order_line.filtered(
|
self.order_line1 = self.sale.order_line.filtered(
|
||||||
lambda l: l.product_id == self.product1
|
lambda l: l.product_id == self.product1
|
||||||
)
|
)
|
||||||
self.order_line1.date_start = '2018-01-01'
|
self.order_line1.date_start = "2018-01-01"
|
||||||
self.order_line1.product_uom_qty = 12
|
self.order_line1.product_uom_qty = 12
|
||||||
pricelist = self.sale.partner_id.property_product_pricelist.id
|
pricelist = self.sale.partner_id.property_product_pricelist.id
|
||||||
self.contract = self.env["contract.contract"].create(
|
self.contract = self.env["contract.contract"].create(
|
||||||
@@ -67,6 +66,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
"partner_id": self.sale.partner_id.id,
|
"partner_id": self.sale.partner_id.id,
|
||||||
"pricelist_id": pricelist,
|
"pricelist_id": pricelist,
|
||||||
"contract_type": "sale",
|
"contract_type": "sale",
|
||||||
|
"line_recurrence": True,
|
||||||
"contract_template_id": self.contract_template1.id,
|
"contract_template_id": self.contract_template1.id,
|
||||||
"contract_line_ids": [
|
"contract_line_ids": [
|
||||||
(
|
(
|
||||||
@@ -100,23 +100,20 @@ class TestSaleOrder(TransactionCase):
|
|||||||
order_line """
|
order_line """
|
||||||
self.order_line1.onchange_product()
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
contracts = self.sale.order_line.mapped('contract_id')
|
contracts = self.sale.order_line.mapped("contract_id")
|
||||||
self.assertEqual(len(contracts), 2)
|
self.assertEqual(len(contracts), 2)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.order_line1.contract_id.contract_template_id,
|
self.order_line1.contract_id.contract_template_id, self.contract_template1,
|
||||||
self.contract_template1,
|
|
||||||
)
|
)
|
||||||
contract_line = self.order_line1.contract_id.contract_line_ids
|
contract_line = self.order_line1.contract_id.contract_line_ids
|
||||||
self.assertEqual(contract_line.date_start, Date.to_date('2018-01-01'))
|
self.assertEqual(contract_line.date_start, Date.to_date("2018-01-01"))
|
||||||
self.assertEqual(contract_line.date_end, Date.to_date('2018-12-31'))
|
self.assertEqual(contract_line.date_end, Date.to_date("2018-12-31"))
|
||||||
self.assertEqual(
|
self.assertEqual(contract_line.recurring_next_date, Date.to_date("2018-01-31"))
|
||||||
contract_line.recurring_next_date, Date.to_date('2018-01-31')
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_change_sale_company(self):
|
def test_change_sale_company(self):
|
||||||
self.assertTrue(self.sale.company_id)
|
self.assertTrue(self.sale.company_id)
|
||||||
other_company = self.env['res.company'].create(
|
other_company = self.env["res.company"].create(
|
||||||
{'name': 'other company', 'parent_id': self.sale.company_id.id}
|
{"name": "other company", "parent_id": self.sale.company_id.id}
|
||||||
)
|
)
|
||||||
self.sale.company_id = other_company
|
self.sale.company_id = other_company
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
@@ -125,8 +122,8 @@ class TestSaleOrder(TransactionCase):
|
|||||||
def test_change_sale_company_2(self):
|
def test_change_sale_company_2(self):
|
||||||
"""Contract company must be the sale order company."""
|
"""Contract company must be the sale order company."""
|
||||||
self.assertTrue(self.sale.company_id)
|
self.assertTrue(self.sale.company_id)
|
||||||
other_company = self.env['res.company'].create(
|
other_company = self.env["res.company"].create(
|
||||||
{'name': 'other company', 'parent_id': self.sale.company_id.id}
|
{"name": "other company", "parent_id": self.sale.company_id.id}
|
||||||
)
|
)
|
||||||
self.product1.with_context(
|
self.product1.with_context(
|
||||||
force_company=other_company.id
|
force_company=other_company.id
|
||||||
@@ -136,15 +133,15 @@ class TestSaleOrder(TransactionCase):
|
|||||||
).property_contract_template_id = self.contract_template2
|
).property_contract_template_id = self.contract_template2
|
||||||
self.sale.company_id = other_company
|
self.sale.company_id = other_company
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
contracts = self.sale.order_line.mapped('contract_id')
|
contracts = self.sale.order_line.mapped("contract_id")
|
||||||
self.assertEqual(contracts.mapped('company_id'), other_company)
|
self.assertEqual(contracts.mapped("company_id"), other_company)
|
||||||
|
|
||||||
def test_sale_order_invoice_status(self):
|
def test_sale_order_invoice_status(self):
|
||||||
"""
|
"""
|
||||||
sale line linked to contracts must not be invoiced from sale order
|
sale line linked to contracts must not be invoiced from sale order
|
||||||
"""
|
"""
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.assertEqual(self.order_line1.invoice_status, 'no')
|
self.assertEqual(self.order_line1.invoice_status, "no")
|
||||||
invoice = self.order_line1.contract_id.recurring_create_invoice()
|
invoice = self.order_line1.contract_id.recurring_create_invoice()
|
||||||
self.assertTrue(invoice)
|
self.assertTrue(invoice)
|
||||||
self.assertEqual(self.order_line1.qty_invoiced, 1)
|
self.assertEqual(self.order_line1.qty_invoiced, 1)
|
||||||
@@ -156,21 +153,18 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.sale.company_id.create_contract_at_sale_order_confirmation = False
|
self.sale.company_id.create_contract_at_sale_order_confirmation = False
|
||||||
self.order_line1.onchange_product()
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.assertEqual(len(self.sale.order_line.mapped('contract_id')), 0)
|
self.assertEqual(len(self.sale.order_line.mapped("contract_id")), 0)
|
||||||
self.assertTrue(self.sale.need_contract_creation)
|
self.assertTrue(self.sale.need_contract_creation)
|
||||||
self.sale.action_create_contract()
|
self.sale.action_create_contract()
|
||||||
self.assertEqual(len(self.sale.order_line.mapped('contract_id')), 2)
|
self.assertEqual(len(self.sale.order_line.mapped("contract_id")), 2)
|
||||||
self.assertFalse(self.sale.need_contract_creation)
|
self.assertFalse(self.sale.need_contract_creation)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.order_line1.contract_id.contract_template_id,
|
self.order_line1.contract_id.contract_template_id, self.contract_template1,
|
||||||
self.contract_template1,
|
|
||||||
)
|
)
|
||||||
contract_line = self.order_line1.contract_id.contract_line_ids
|
contract_line = self.order_line1.contract_id.contract_line_ids
|
||||||
self.assertEqual(contract_line.date_start, Date.to_date('2018-01-01'))
|
self.assertEqual(contract_line.date_start, Date.to_date("2018-01-01"))
|
||||||
self.assertEqual(contract_line.date_end, Date.to_date('2018-12-31'))
|
self.assertEqual(contract_line.date_end, Date.to_date("2018-12-31"))
|
||||||
self.assertEqual(
|
self.assertEqual(contract_line.recurring_next_date, Date.to_date("2018-01-31"))
|
||||||
contract_line.recurring_next_date, Date.to_date('2018-01-31')
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_sale_contract_count(self):
|
def test_sale_contract_count(self):
|
||||||
"""It should count contracts as many different contract template used
|
"""It should count contracts as many different contract template used
|
||||||
@@ -184,23 +178,23 @@ class TestSaleOrder(TransactionCase):
|
|||||||
its product """
|
its product """
|
||||||
self.order_line1.onchange_product()
|
self.order_line1.onchange_product()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.order_line1.recurring_rule_type,
|
self.order_line1.recurring_rule_type, self.product1.recurring_rule_type,
|
||||||
self.product1.recurring_rule_type,
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.order_line1.recurring_invoicing_type,
|
self.order_line1.recurring_invoicing_type,
|
||||||
self.product1.recurring_invoicing_type,
|
self.product1.recurring_invoicing_type,
|
||||||
)
|
)
|
||||||
self.assertEqual(self.order_line1.date_end, Date.to_date('2018-12-31'))
|
self.assertEqual(self.order_line1.date_end, Date.to_date("2018-12-31"))
|
||||||
|
|
||||||
def test_check_contract_sale_partner(self):
|
def test_check_contract_sale_partner(self):
|
||||||
"""Can't link order line to a partner contract different then the
|
"""Can't link order line to a partner contract different then the
|
||||||
order one"""
|
order one"""
|
||||||
contract2 = self.env['contract.contract'].create(
|
contract2 = self.env["contract.contract"].create(
|
||||||
{
|
{
|
||||||
'name': 'Contract',
|
"name": "Contract",
|
||||||
'contract_template_id': self.contract_template2.id,
|
"contract_template_id": self.contract_template2.id,
|
||||||
'partner_id': self.sale.partner_id.id,
|
"partner_id": self.sale.partner_id.id,
|
||||||
|
"line_recurrence": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
@@ -209,11 +203,12 @@ class TestSaleOrder(TransactionCase):
|
|||||||
def test_check_contract_sale_contract_template(self):
|
def test_check_contract_sale_contract_template(self):
|
||||||
"""Can't link order line to a contract with different contract
|
"""Can't link order line to a contract with different contract
|
||||||
template then the product one"""
|
template then the product one"""
|
||||||
contract1 = self.env['contract.contract'].create(
|
contract1 = self.env["contract.contract"].create(
|
||||||
{
|
{
|
||||||
'name': 'Contract',
|
"name": "Contract",
|
||||||
'partner_id': self.env.user.partner_id.id,
|
"partner_id": self.env.user.partner_id.id,
|
||||||
'contract_template_id': self.contract_template1.id,
|
"contract_template_id": self.contract_template1.id,
|
||||||
|
"line_recurrence": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
@@ -230,7 +225,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
invoice as status"""
|
invoice as status"""
|
||||||
self.order_line1.onchange_product()
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.assertEqual(self.order_line1.invoice_status, 'no')
|
self.assertEqual(self.order_line1.invoice_status, "no")
|
||||||
|
|
||||||
def test_sale_order_invoice_status_2(self):
|
def test_sale_order_invoice_status_2(self):
|
||||||
"""Sale order with only contract product should have nothing to
|
"""Sale order with only contract product should have nothing to
|
||||||
@@ -240,15 +235,15 @@ class TestSaleOrder(TransactionCase):
|
|||||||
).unlink()
|
).unlink()
|
||||||
self.order_line1.onchange_product()
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.assertEqual(self.sale.invoice_status, 'no')
|
self.assertEqual(self.sale.invoice_status, "no")
|
||||||
|
|
||||||
def test_sale_order_create_invoice(self):
|
def test_sale_order_create_invoice(self):
|
||||||
"""Should not invoice contract product on sale order create invoice"""
|
"""Should not invoice contract product on sale order create invoice"""
|
||||||
self.product2.is_contract = False
|
self.product2.is_contract = False
|
||||||
self.product2.invoice_policy = 'order'
|
self.product2.invoice_policy = "order"
|
||||||
self.order_line1.onchange_product()
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.sale.action_invoice_create()
|
self.sale._create_invoices()
|
||||||
self.assertEqual(len(self.sale.invoice_ids), 1)
|
self.assertEqual(len(self.sale.invoice_ids), 1)
|
||||||
invoice_line = self.sale.invoice_ids.invoice_line_ids.filtered(
|
invoice_line = self.sale.invoice_ids.invoice_line_ids.filtered(
|
||||||
lambda line: line.product_id.is_contract
|
lambda line: line.product_id.is_contract
|
||||||
@@ -271,12 +266,10 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.order_line1.date_start = "2018-06-01"
|
self.order_line1.date_start = "2018-06-01"
|
||||||
self.order_line1.onchange_product()
|
self.order_line1.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.assertEqual(
|
self.assertEqual(self.contract_line.date_end, Date.to_date("2018-05-31"))
|
||||||
self.contract_line.date_end, Date.to_date("2018-05-31")
|
|
||||||
)
|
|
||||||
self.assertFalse(self.contract_line.is_auto_renew)
|
self.assertFalse(self.contract_line.is_auto_renew)
|
||||||
new_contract_line = self.env['contract.line'].search(
|
new_contract_line = self.env["contract.line"].search(
|
||||||
[('sale_order_line_id', '=', self.order_line1.id)]
|
[("sale_order_line_id", "=", self.order_line1.id)]
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.contract_line.successor_contract_line_id, new_contract_line
|
self.contract_line.successor_contract_line_id, new_contract_line
|
||||||
@@ -291,9 +284,9 @@ class TestSaleOrder(TransactionCase):
|
|||||||
self.order_line1.contract_line_id = self.contract_line
|
self.order_line1.contract_line_id = self.contract_line
|
||||||
self.contract_line.write(
|
self.contract_line.write(
|
||||||
{
|
{
|
||||||
'date_start': "2018-06-01",
|
"date_start": "2018-06-01",
|
||||||
'recurring_next_date': "2018-06-01",
|
"recurring_next_date": "2018-06-01",
|
||||||
'date_end': False,
|
"date_end": False,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.order_line1.date_start = "2018-06-01"
|
self.order_line1.date_start = "2018-06-01"
|
||||||
@@ -305,42 +298,38 @@ class TestSaleOrder(TransactionCase):
|
|||||||
def test_onchange_product_id_recurring_info(self):
|
def test_onchange_product_id_recurring_info(self):
|
||||||
self.product2.write(
|
self.product2.write(
|
||||||
{
|
{
|
||||||
'recurring_rule_type': 'monthly',
|
"recurring_rule_type": "monthly",
|
||||||
'recurring_invoicing_type': 'pre-paid',
|
"recurring_invoicing_type": "pre-paid",
|
||||||
'is_auto_renew': True,
|
"is_auto_renew": True,
|
||||||
'default_qty': 12,
|
"default_qty": 12,
|
||||||
'termination_notice_interval': '6',
|
"termination_notice_interval": "6",
|
||||||
'termination_notice_rule_type': 'weekly',
|
"termination_notice_rule_type": "weekly",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.contract_line.write(
|
self.contract_line.write(
|
||||||
{
|
{
|
||||||
'date_start': Date.today(),
|
"date_start": Date.today(),
|
||||||
'date_end': Date.today() + relativedelta(years=1),
|
"date_end": Date.today() + relativedelta(years=1),
|
||||||
'recurring_next_date': Date.today(),
|
"recurring_next_date": Date.today(),
|
||||||
'product_id': self.product2.id,
|
"product_id": self.product2.id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.contract_line._onchange_product_id_recurring_info()
|
self.contract_line._onchange_product_id_recurring_info()
|
||||||
self.assertEqual(self.contract_line.recurring_rule_type, 'monthly')
|
self.assertEqual(self.contract_line.recurring_rule_type, "monthly")
|
||||||
self.assertEqual(
|
self.assertEqual(self.contract_line.recurring_invoicing_type, "pre-paid")
|
||||||
self.contract_line.recurring_invoicing_type, 'pre-paid'
|
|
||||||
)
|
|
||||||
self.assertEqual(self.contract_line.recurring_interval, 1)
|
self.assertEqual(self.contract_line.recurring_interval, 1)
|
||||||
self.assertEqual(self.contract_line.is_auto_renew, True)
|
self.assertEqual(self.contract_line.is_auto_renew, True)
|
||||||
self.assertEqual(self.contract_line.auto_renew_interval, 1)
|
self.assertEqual(self.contract_line.auto_renew_interval, 1)
|
||||||
self.assertEqual(self.contract_line.auto_renew_rule_type, 'yearly')
|
self.assertEqual(self.contract_line.auto_renew_rule_type, "yearly")
|
||||||
self.assertEqual(self.contract_line.termination_notice_interval, 6)
|
self.assertEqual(self.contract_line.termination_notice_interval, 6)
|
||||||
self.assertEqual(
|
self.assertEqual(self.contract_line.termination_notice_rule_type, "weekly")
|
||||||
self.contract_line.termination_notice_rule_type, 'weekly'
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_action_show_contracts(self):
|
def test_action_show_contracts(self):
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
action = self.sale.action_show_contracts()
|
action = self.sale.action_show_contracts()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.env['contract.contract'].search(action['domain']),
|
self.env["contract.contract"].search(action["domain"]),
|
||||||
self.sale.order_line.mapped('contract_id'),
|
self.sale.order_line.mapped("contract_id"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_check_contact_is_not_terminated(self):
|
def test_check_contact_is_not_terminated(self):
|
||||||
@@ -348,7 +337,7 @@ class TestSaleOrder(TransactionCase):
|
|||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
self.order_line1.contract_id = self.contract
|
self.order_line1.contract_id = self.contract
|
||||||
|
|
||||||
def test_check_contact_is_not_terminated(self):
|
def test_check_contact_is_not_terminated_1(self):
|
||||||
self.order_line1.contract_id = self.contract
|
self.order_line1.contract_id = self.contract
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
self.contract.is_terminated = True
|
self.contract.is_terminated = True
|
||||||
@@ -361,22 +350,20 @@ class TestSaleOrder(TransactionCase):
|
|||||||
def test_order_lines_with_the_same_contract_template(self):
|
def test_order_lines_with_the_same_contract_template(self):
|
||||||
""" It should create one contract with two lines grouped by contract
|
""" It should create one contract with two lines grouped by contract
|
||||||
template"""
|
template"""
|
||||||
self.product2.with_context(
|
self.product2.with_context(force_company=self.sale.company_id.id).write(
|
||||||
force_company=self.sale.company_id.id
|
|
||||||
).write(
|
|
||||||
{
|
{
|
||||||
'is_contract': True,
|
"is_contract": True,
|
||||||
'property_contract_template_id': self.contract_template1.id,
|
"property_contract_template_id": self.contract_template1.id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.sale.order_line.onchange_product()
|
self.sale.order_line.onchange_product()
|
||||||
self.sale.action_confirm()
|
self.sale.action_confirm()
|
||||||
contracts = self.sale.order_line.mapped('contract_id')
|
contracts = self.sale.order_line.mapped("contract_id")
|
||||||
self.assertEqual(len(contracts), 1)
|
self.assertEqual(len(contracts), 1)
|
||||||
self.assertEqual(len(contracts.contract_line_ids), 2)
|
self.assertEqual(len(contracts.contract_line_ids), 2)
|
||||||
contracts = (
|
contracts = (
|
||||||
self.env['contract.line']
|
self.env["contract.line"]
|
||||||
.search([('sale_order_line_id', 'in', self.sale.order_line.ids)])
|
.search([("sale_order_line_id", "in", self.sale.order_line.ids)])
|
||||||
.mapped('contract_id')
|
.mapped("contract_id")
|
||||||
)
|
)
|
||||||
self.assertEqual(len(contracts), 1)
|
self.assertEqual(len(contracts), 1)
|
||||||
|
|||||||
@@ -4,17 +4,18 @@
|
|||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
-->
|
-->
|
||||||
<odoo>
|
<odoo>
|
||||||
|
<record id="contract_contract_customer_form_view" model="ir.ui.view">
|
||||||
<record id="contract_contract_customer_form_view"
|
|
||||||
model="ir.ui.view">
|
|
||||||
<field name="model">contract.contract</field>
|
<field name="model">contract.contract</field>
|
||||||
<field name="inherit_id"
|
<field name="inherit_id" ref="contract.contract_contract_customer_form_view" />
|
||||||
ref="contract.contract_contract_customer_form_view"/>
|
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//div[@name='button_box']" position="inside">
|
<xpath expr="//div[@name='button_box']" position="inside">
|
||||||
<button class="oe_stat_button" name="action_view_sales_orders"
|
<button
|
||||||
type="object" icon="fa-edit"
|
class="oe_stat_button"
|
||||||
attrs="{'invisible': [('sale_order_count', '=', 0)]}">
|
name="action_view_sales_orders"
|
||||||
|
type="object"
|
||||||
|
icon="fa-edit"
|
||||||
|
attrs="{'invisible': [('sale_order_count', '=', 0)]}"
|
||||||
|
>
|
||||||
<div class="o_field_widget o_stat_info">
|
<div class="o_field_widget o_stat_info">
|
||||||
<span class="o_stat_value">
|
<span class="o_stat_value">
|
||||||
<field name="sale_order_count" />
|
<field name="sale_order_count" />
|
||||||
@@ -25,5 +26,4 @@
|
|||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Copyright 2017 LasLabs Inc.
|
Copyright 2017 LasLabs Inc.
|
||||||
Copyright 2018 ACSONE SA/NV.
|
Copyright 2018 ACSONE SA/NV.
|
||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="product_template_form_contract_view" model="ir.ui.view">
|
<record id="product_template_form_contract_view" model="ir.ui.view">
|
||||||
<field name="name">account.invoice.select.contract</field>
|
<field name="name">account.invoice.select.contract</field>
|
||||||
<field name="model">product.template</field>
|
<field name="model">product.template</field>
|
||||||
@@ -20,9 +17,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//notebook" position="inside">
|
<xpath expr="//notebook" position="inside">
|
||||||
<page string="Contract"
|
<page
|
||||||
|
string="Contract"
|
||||||
name="contract"
|
name="contract"
|
||||||
attrs="{'invisible': [('is_contract', '=', False)],}">
|
attrs="{'invisible': [('is_contract', '=', False)],}"
|
||||||
|
>
|
||||||
<group>
|
<group>
|
||||||
<field name="property_contract_template_id" />
|
<field name="property_contract_template_id" />
|
||||||
</group>
|
</group>
|
||||||
@@ -42,23 +41,35 @@
|
|||||||
<group attrs="{'invisible':[('is_auto_renew', '=', False)]}">
|
<group attrs="{'invisible':[('is_auto_renew', '=', False)]}">
|
||||||
<label for="auto_renew_interval" />
|
<label for="auto_renew_interval" />
|
||||||
<div>
|
<div>
|
||||||
<field name="auto_renew_interval"
|
<field
|
||||||
class="oe_inline" nolabel="1"
|
name="auto_renew_interval"
|
||||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
class="oe_inline"
|
||||||
<field name="auto_renew_rule_type"
|
nolabel="1"
|
||||||
class="oe_inline" nolabel="1"
|
attrs="{'required':[('is_auto_renew', '=', True)]}"
|
||||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
/>
|
||||||
|
<field
|
||||||
|
name="auto_renew_rule_type"
|
||||||
|
class="oe_inline"
|
||||||
|
nolabel="1"
|
||||||
|
attrs="{'required':[('is_auto_renew', '=', True)]}"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
<group attrs="{'invisible':[('is_auto_renew', '=', False)]}">
|
<group attrs="{'invisible':[('is_auto_renew', '=', False)]}">
|
||||||
<label for="termination_notice_interval" />
|
<label for="termination_notice_interval" />
|
||||||
<div>
|
<div>
|
||||||
<field name="termination_notice_interval"
|
<field
|
||||||
class="oe_inline" nolabel="1"
|
name="termination_notice_interval"
|
||||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
class="oe_inline"
|
||||||
<field name="termination_notice_rule_type"
|
nolabel="1"
|
||||||
class="oe_inline" nolabel="1"
|
attrs="{'required':[('is_auto_renew', '=', True)]}"
|
||||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
/>
|
||||||
|
<field
|
||||||
|
name="termination_notice_rule_type"
|
||||||
|
class="oe_inline"
|
||||||
|
nolabel="1"
|
||||||
|
attrs="{'required':[('is_auto_renew', '=', True)]}"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
@@ -66,5 +77,4 @@
|
|||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<!-- Copyright 2019 ACSONE SA/NV
|
<!-- Copyright 2019 ACSONE SA/NV
|
||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record model="ir.ui.view" id="res_config_settings_form_view">
|
<record model="ir.ui.view" id="res_config_settings_form_view">
|
||||||
<field name="name">res.config.settings.form (in product_contract)
|
<field name="name">res.config.settings.form (in product_contract)
|
||||||
</field>
|
</field>
|
||||||
<field name="model">res.config.settings</field>
|
<field name="model">res.config.settings</field>
|
||||||
<field name="inherit_id" ref="sale.res_config_settings_view_form" />
|
<field name="inherit_id" ref="sale.res_config_settings_view_form" />
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//div[@id='sales_settings_invoicing_policy']/.."
|
<xpath
|
||||||
position="inside">
|
expr="//div[@id='sales_settings_invoicing_policy']/.."
|
||||||
|
position="inside"
|
||||||
|
>
|
||||||
<div class="col-12 col-lg-6 o_setting_box">
|
<div class="col-12 col-lg-6 o_setting_box">
|
||||||
<div class="o_setting_left_pane">
|
<div class="o_setting_left_pane">
|
||||||
<field name="create_contract_at_sale_order_confirmation" />
|
<field name="create_contract_at_sale_order_confirmation" />
|
||||||
@@ -26,6 +26,4 @@
|
|||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -1,60 +1,69 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Copyright 2018 ACSONE SA/NV.
|
Copyright 2018 ACSONE SA/NV.
|
||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="view_order_form" model="ir.ui.view">
|
<record id="view_order_form" model="ir.ui.view">
|
||||||
<field name="name">sale.order.form (in product_contract)</field>
|
<field name="name">sale.order.form (in product_contract)</field>
|
||||||
<field name="model">sale.order</field>
|
<field name="model">sale.order</field>
|
||||||
<field name="inherit_id" ref="sale.view_order_form" />
|
<field name="inherit_id" ref="sale.view_order_form" />
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//header" position="inside">
|
<xpath expr="//header" position="inside">
|
||||||
<field name="need_contract_creation" invisible="1"/>
|
<field name="need_contract_creation" attrs="{'invisible': []}" />
|
||||||
<button name="action_create_contract"
|
<button
|
||||||
|
name="action_create_contract"
|
||||||
string="Create Contracts"
|
string="Create Contracts"
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_highlight" attrs="{'invisible': [('need_contract_creation', '=', False)]}"/>
|
class="oe_highlight"
|
||||||
|
attrs="{'invisible': [('need_contract_creation', '=', False)]}"
|
||||||
|
/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//div[@name='button_box']" position="inside">
|
<xpath expr="//div[@name='button_box']" position="inside">
|
||||||
<button name="action_show_contracts"
|
<button
|
||||||
type="object" icon="fa-book"
|
name="action_show_contracts"
|
||||||
|
type="object"
|
||||||
|
icon="fa-book"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
attrs="{'invisible': ['|', '|', ('is_contract', '!=', True), ('state', 'not in', ['sale', 'done']), ('contract_count', '=', 0)]}">
|
attrs="{'invisible': ['|', '|', ('is_contract', '!=', True), ('state', 'not in', ['sale', 'done']), ('contract_count', '=', 0)]}"
|
||||||
<field string="Contracts"
|
>
|
||||||
name="contract_count"
|
<field string="Contracts" name="contract_count" widget="statinfo" />
|
||||||
widget="statinfo"/>
|
|
||||||
</button>
|
</button>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='order_line']" position="before">
|
<xpath expr="//field[@name='order_line']" position="before">
|
||||||
<field name="is_contract" invisible="1"/>
|
<field name="is_contract" attrs="{'invisible': []}" />
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='order_line']/form//field[@name='product_id']"
|
<xpath
|
||||||
position="after">
|
expr="//field[@name='order_line']/form//field[@name='product_id']"
|
||||||
<field name="contract_template_id" invisible="1"/>
|
position="after"
|
||||||
<field name="contract_id"
|
>
|
||||||
|
<field name="contract_template_id" attrs="{'invisible': []}" />
|
||||||
|
<field
|
||||||
|
name="contract_id"
|
||||||
options='{"no_create": True}'
|
options='{"no_create": True}'
|
||||||
attrs="{'invisible': [('is_contract', '=', False)]}"
|
attrs="{'invisible': [('is_contract', '=', False)]}"
|
||||||
domain="['|',('contract_template_id','=',contract_template_id),
|
domain="['|',('contract_template_id','=',contract_template_id),
|
||||||
('contract_template_id','=',False),
|
('contract_template_id','=',False),
|
||||||
('partner_id','=',parent.partner_id),
|
('partner_id','=',parent.partner_id),
|
||||||
('is_terminated','=',False),
|
('is_terminated','=',False),
|
||||||
]"/>
|
]"
|
||||||
<field name="contract_line_id"
|
/>
|
||||||
|
<field
|
||||||
|
name="contract_line_id"
|
||||||
attrs="{'invisible': [('is_contract', '=', False)]}"
|
attrs="{'invisible': [('is_contract', '=', False)]}"
|
||||||
domain="[('contract_id','=',contract_id)]"/>
|
domain="[('contract_id','=',contract_id)]"
|
||||||
|
/>
|
||||||
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='order_line']/form//field[@name='tax_id']/parent::group"
|
<xpath
|
||||||
position="after">
|
expr="//field[@name='order_line']/form//field[@name='tax_id']/parent::group"
|
||||||
<field name="is_contract" invisible="1"/>
|
position="after"
|
||||||
<separator colspan="4" string="Recurrence Invoicing"
|
>
|
||||||
attrs="{'invisible': [('is_contract', '=', False)]}"/>
|
<field name="is_contract" attrs="{'invisible': []}" />
|
||||||
|
<separator
|
||||||
|
colspan="4"
|
||||||
|
string="Recurrence Invoicing"
|
||||||
|
attrs="{'invisible': [('is_contract', '=', False)]}"
|
||||||
|
/>
|
||||||
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
||||||
<field name="recurring_rule_type" />
|
<field name="recurring_rule_type" />
|
||||||
</group>
|
</group>
|
||||||
@@ -62,41 +71,54 @@
|
|||||||
<field name="recurring_invoicing_type" />
|
<field name="recurring_invoicing_type" />
|
||||||
</group>
|
</group>
|
||||||
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
||||||
<field name="date_start"
|
<field
|
||||||
attrs="{'required': [('is_contract', '=', True)]}"/>
|
name="date_start"
|
||||||
|
attrs="{'required': [('is_contract', '=', True)]}"
|
||||||
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
||||||
<field name="date_end" attrs="{'required': [('is_contract', '=', True)]}"/>
|
<field
|
||||||
|
name="date_end"
|
||||||
|
attrs="{'required': [('is_contract', '=', True)]}"
|
||||||
|
/>
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group attrs="{'invisible': [('is_contract', '=', False)]}">
|
||||||
attrs="{'invisible': [('is_contract', '=', False)]}">
|
|
||||||
<field name="is_auto_renew" />
|
<field name="is_auto_renew" />
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group attrs="{'invisible': [('is_auto_renew', '=', False)]}">
|
||||||
attrs="{'invisible': [('is_auto_renew', '=', False)]}">
|
|
||||||
<label for="auto_renew_interval" />
|
<label for="auto_renew_interval" />
|
||||||
<div>
|
<div>
|
||||||
<field name="auto_renew_interval"
|
<field
|
||||||
class="oe_inline" nolabel="1"
|
name="auto_renew_interval"
|
||||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
class="oe_inline"
|
||||||
<field name="auto_renew_rule_type"
|
nolabel="1"
|
||||||
class="oe_inline" nolabel="1"
|
attrs="{'required':[('is_auto_renew', '=', True)]}"
|
||||||
attrs="{'required':[('is_auto_renew', '=', True)]}"/>
|
/>
|
||||||
|
<field
|
||||||
|
name="auto_renew_rule_type"
|
||||||
|
class="oe_inline"
|
||||||
|
nolabel="1"
|
||||||
|
attrs="{'required':[('is_auto_renew', '=', True)]}"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='order_line']/tree//field[@name='price_total']"
|
<xpath
|
||||||
position="after">
|
expr="//field[@name='order_line']/tree//field[@name='price_total']"
|
||||||
<field name="date_start"
|
position="after"
|
||||||
attrs="{'column_invisible': [('parent.is_contract', '=', False)]}"/>
|
>
|
||||||
<field name="date_end"
|
<field
|
||||||
attrs="{'column_invisible': [('parent.is_contract', '=', False)]}"/>
|
name="date_start"
|
||||||
|
attrs="{'column_invisible': [('parent.is_contract', '=', False)]}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="date_end"
|
||||||
|
attrs="{'column_invisible': [('parent.is_contract', '=', False)]}"
|
||||||
|
/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='order_line']/tree"
|
<xpath expr="//field[@name='order_line']/tree" position="attributes">
|
||||||
position="attributes">
|
|
||||||
<attribute name="editable" />
|
<attribute name="editable" />
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
Reference in New Issue
Block a user