[13.0][MIG] - migration product_contract

This commit is contained in:
sbejaoui
2020-10-24 20:37:58 +02:00
committed by Ilyas
parent 8ac91c2101
commit 417702f3d5
17 changed files with 436 additions and 548 deletions

View File

@@ -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"],
} }

View File

@@ -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)

View File

@@ -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'
"""
)

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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;

View File

@@ -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"))

View File

@@ -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",

View File

@@ -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
) )

View File

@@ -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

View File

@@ -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

View File

@@ -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):
""" """

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>