From 417702f3d566ae689caff00c31e2672b45d69ed0 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Sat, 24 Oct 2020 20:37:58 +0200 Subject: [PATCH] [13.0][MIG] - migration product_contract --- product_contract/__manifest__.py | 31 ++- .../migrations/12.0.2.0.0/pre-migration.py | 18 -- .../migrations/12.0.3.0.0/pre-migration.py | 14 -- .../migrations/12.0.4.0.0/pre-migration.py | 36 ---- product_contract/models/contract.py | 15 +- product_contract/models/contract_line.py | 24 +-- product_contract/models/product_template.py | 70 +++--- product_contract/models/res_company.py | 2 +- .../models/res_config_settings.py | 5 +- product_contract/models/sale_order.py | 110 ++++------ product_contract/models/sale_order_line.py | 187 ++++++++-------- product_contract/tests/test_product.py | 13 +- product_contract/tests/test_sale_order.py | 199 ++++++++---------- product_contract/views/contract.xml | 22 +- product_contract/views/product_template.xml | 70 +++--- .../views/res_config_settings.xml | 18 +- product_contract/views/sale_order.xml | 150 +++++++------ 17 files changed, 436 insertions(+), 548 deletions(-) delete mode 100644 product_contract/migrations/12.0.2.0.0/pre-migration.py delete mode 100644 product_contract/migrations/12.0.3.0.0/pre-migration.py delete mode 100644 product_contract/migrations/12.0.4.0.0/pre-migration.py diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py index 8298115d4..ba99e8d6b 100644 --- a/product_contract/__manifest__.py +++ b/product_contract/__manifest__.py @@ -3,22 +3,21 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { - 'name': 'Recurring - Product Contract', - 'version': '12.0.5.1.0', - 'category': 'Contract Management', - 'license': 'AGPL-3', - 'author': "LasLabs, " - "ACSONE SA/NV, " - "Odoo Community Association (OCA)", - 'website': 'https://github.com/oca/contract', - 'depends': ['product', 'contract_sale'], - 'data': [ - 'views/res_config_settings.xml', - 'views/contract.xml', - 'views/product_template.xml', - 'views/sale_order.xml' + "name": "Recurring - Product Contract", + "version": "13.0.1.0.0", + "category": "Contract Management", + "license": "AGPL-3", + "author": "LasLabs, " "ACSONE SA/NV, " "Odoo Community Association (OCA)", + "website": "https://github.com/oca/contract", + "depends": ["product", "contract", "sale"], + "data": [ + "views/res_config_settings.xml", + "views/contract.xml", + "views/product_template.xml", + "views/sale_order.xml", ], - 'installable': True, - 'application': False, + "installable": True, + "application": False, "external_dependencies": {"python": ["dateutil"]}, + "maintainers": ["sbejaoui"], } diff --git a/product_contract/migrations/12.0.2.0.0/pre-migration.py b/product_contract/migrations/12.0.2.0.0/pre-migration.py deleted file mode 100644 index ca2542a4c..000000000 --- a/product_contract/migrations/12.0.2.0.0/pre-migration.py +++ /dev/null @@ -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) diff --git a/product_contract/migrations/12.0.3.0.0/pre-migration.py b/product_contract/migrations/12.0.3.0.0/pre-migration.py deleted file mode 100644 index 69a771aea..000000000 --- a/product_contract/migrations/12.0.3.0.0/pre-migration.py +++ /dev/null @@ -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' - """ - ) diff --git a/product_contract/migrations/12.0.4.0.0/pre-migration.py b/product_contract/migrations/12.0.4.0.0/pre-migration.py deleted file mode 100644 index f6e2351c0..000000000 --- a/product_contract/migrations/12.0.4.0.0/pre-migration.py +++ /dev/null @@ -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, - ) diff --git a/product_contract/models/contract.py b/product_contract/models/contract.py index df5d87601..d37752ef2 100644 --- a/product_contract/models/contract.py +++ b/product_contract/models/contract.py @@ -7,29 +7,24 @@ from odoo.tools.translate import _ class ContractContract(models.Model): - _inherit = 'contract.contract' + _inherit = "contract.contract" 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): for rec in self: try: order_count = len( - rec.contract_line_ids.mapped( - 'sale_order_line_id.order_id' - ) + rec.contract_line_ids.mapped("sale_order_line_id.order_id") ) except AccessError: order_count = 0 rec.sale_order_count = order_count - @api.multi def action_view_sales_orders(self): self.ensure_one() - orders = self.contract_line_ids.mapped( - 'sale_order_line_id.order_id' - ) + orders = self.contract_line_ids.mapped("sale_order_line_id.order_id") action = { "name": _("Sales Orders"), "view_mode": "tree,form", @@ -39,5 +34,5 @@ class ContractContract(models.Model): } if len(orders) == 1: # 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 diff --git a/product_contract/models/contract_line.py b/product_contract/models/contract_line.py index 1ab326ef8..9615cbee4 100644 --- a/product_contract/models/contract_line.py +++ b/product_contract/models/contract_line.py @@ -7,8 +7,8 @@ from odoo import api, fields, models class ContractLine(models.Model): - _inherit = 'contract.line' - _rec_name = 'display_name' + _inherit = "contract.line" + _rec_name = "display_name" sale_order_line_id = fields.Many2one( comodel_name="sale.order.line", @@ -16,18 +16,14 @@ class ContractLine(models.Model): required=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, invoice_id=False, invoice_values=False): - res = super(ContractLine, self)._prepare_invoice_line( - invoice_id=invoice_id, invoice_values=invoice_values, - ) + def _prepare_invoice_line(self, move_form): + res = super(ContractLine, self)._prepare_invoice_line(move_form) 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 - @api.multi def _get_auto_renew_rule_type(self): """monthly last day don't make sense for auto_renew_rule_type""" self.ensure_one() @@ -35,15 +31,13 @@ class ContractLine(models.Model): return "monthly" return self.recurring_rule_type - @api.onchange('product_id') + @api.onchange("product_id") def _onchange_product_id_recurring_info(self): for rec in self: rec.date_start = fields.Date.today() if rec.product_id.is_contract: rec.recurring_rule_type = rec.product_id.recurring_rule_type - rec.recurring_invoicing_type = ( - rec.product_id.recurring_invoicing_type - ) + rec.recurring_invoicing_type = rec.product_id.recurring_invoicing_type rec.recurring_interval = 1 rec.is_auto_renew = rec.product_id.is_auto_renew 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 ) - @api.depends('name', 'date_start') + @api.depends("name", "date_start") def _compute_display_name_2(self): # FIXME: _compute_display_name depends on rec_name (display_name) # and this trigger a WARNING : display_name depends on itself; diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index 0e784d993..875ff6f3e 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -2,78 +2,76 @@ # Copyright 2018 ACSONE SA/NV. # 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 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( - comodel_name='contract.template', - string='Contract Template', + comodel_name="contract.template", + string="Contract Template", company_dependent=True, ) default_qty = fields.Integer(string="Default Quantity", default=1) recurring_rule_type = fields.Selection( [ - ('daily', 'Day(s)'), - ('weekly', 'Week(s)'), - ('monthly', 'Month(s)'), - ('monthlylastday', 'Month(s) last day'), - ('quarterly', 'Quarter(s)'), - ('semesterly', 'Semester(s)'), - ('yearly', 'Year(s)'), + ("daily", "Day(s)"), + ("weekly", "Week(s)"), + ("monthly", "Month(s)"), + ("monthlylastday", "Month(s) last day"), + ("quarterly", "Quarter(s)"), + ("semesterly", "Semester(s)"), + ("yearly", "Year(s)"), ], - default='monthly', - string='Invoice Every', + default="monthly", + string="Invoice Every", help="Specify Interval for automatic invoice generation.", ) recurring_invoicing_type = fields.Selection( - [('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')], - default='pre-paid', - string='Invoicing type', + [("pre-paid", "Pre-paid"), ("post-paid", "Post-paid")], + default="pre-paid", + string="Invoicing type", help="Specify if process date is 'from' or 'to' invoicing date", ) is_auto_renew = fields.Boolean(string="Auto Renew", default=False) termination_notice_interval = fields.Integer( - default=1, string='Termination Notice Before' + default=1, string="Termination Notice Before" ) termination_notice_rule_type = fields.Selection( - [('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)')], - default='monthly', - string='Termination Notice type', + [("daily", "Day(s)"), ("weekly", "Week(s)"), ("monthly", "Month(s)")], + default="monthly", + string="Termination Notice type", ) auto_renew_interval = fields.Integer( - default=1, - string='Renew Every', - help="Renew every (Days/Week/Month/Year)", + default=1, string="Renew Every", help="Renew every (Days/Week/Month/Year)", ) auto_renew_rule_type = fields.Selection( [ - ('daily', 'Day(s)'), - ('weekly', 'Week(s)'), - ('monthly', 'Month(s)'), - ('yearly', 'Year(s)'), + ("daily", "Day(s)"), + ("weekly", "Week(s)"), + ("monthly", "Month(s)"), + ("yearly", "Year(s)"), ], - default='yearly', - string='Renewal type', + default="yearly", + string="Renewal type", help="Specify Interval for automatic renewal.", ) - @api.multi def write(self, vals): - if 'is_contract' in vals and vals['is_contract'] is False: - for company in self.env['res.company'].search([]): + if "is_contract" in vals and vals["is_contract"] is False: + for company in self.env["res.company"].search([]): self.with_context(force_company=company.id).write( - {'property_contract_template_id': False}) + {"property_contract_template_id": False} + ) super().write(vals) - @api.constrains('is_contract', 'type') + @api.constrains("is_contract", "type") def _check_contract_product_type(self): """ 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")) diff --git a/product_contract/models/res_company.py b/product_contract/models/res_company.py index ab0c760c2..099137c43 100644 --- a/product_contract/models/res_company.py +++ b/product_contract/models/res_company.py @@ -6,7 +6,7 @@ from odoo import fields, models class ResCompany(models.Model): - _inherit = 'res.company' + _inherit = "res.company" create_contract_at_sale_order_confirmation = fields.Boolean( string="Automatically Create Contracts At Sale Order Confirmation", diff --git a/product_contract/models/res_config_settings.py b/product_contract/models/res_config_settings.py index 803b095a5..cd399a197 100644 --- a/product_contract/models/res_config_settings.py +++ b/product_contract/models/res_config_settings.py @@ -6,9 +6,8 @@ from odoo import fields, models class ResConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' + _inherit = "res.config.settings" create_contract_at_sale_order_confirmation = fields.Boolean( - related="company_id.create_contract_at_sale_order_confirmation", - readonly=False + related="company_id.create_contract_at_sale_order_confirmation", readonly=False ) diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 98b4b948d..3255b9d2a 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -2,37 +2,32 @@ # Copyright 2018 ACSONE SA/NV. # 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 class SaleOrder(models.Model): - _inherit = 'sale.order' + _inherit = "sale.order" - is_contract = fields.Boolean( - 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' - ) + is_contract = fields.Boolean(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") - @api.constrains('state') + @api.constrains("state") def check_contact_is_not_terminated(self): for rec in self: - if rec.state not in ( - 'sale', - 'done', - 'cancel', - ) and rec.order_line.filtered('contract_id.is_terminated'): + if rec.state not in ("sale", "done", "cancel",) and rec.order_line.filtered( + "contract_id.is_terminated" + ): raise ValidationError( _("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): 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( 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 and r.product_id.is_contract and r - not in r.contract_id.contract_line_ids.mapped( - 'sale_order_line_id' - ) + not in r.contract_id.contract_line_ids.mapped("sale_order_line_id") ) if line_to_create_contract or line_to_update_contract: rec.need_contract_creation = True - @api.depends('order_line') + @api.depends("order_line") 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): self.ensure_one() return { - 'name': '{template_name}: {sale_name}'.format( + "name": "{template_name}: {sale_name}".format( template_name=contract_template.name, sale_name=self.name ), - 'partner_id': self.partner_id.id, - 'company_id': self.company_id.id, - 'contract_template_id': contract_template.id, - 'user_id': self.user_id.id, - 'payment_term_id': self.payment_term_id.id, - 'fiscal_position_id': self.fiscal_position_id.id, - 'invoice_partner_id': self.partner_invoice_id.id, + "partner_id": self.partner_id.id, + "company_id": self.company_id.id, + "contract_template_id": contract_template.id, + "user_id": self.user_id.id, + "payment_term_id": self.payment_term_id.id, + "fiscal_position_id": self.fiscal_position_id.id, + "invoice_partner_id": self.partner_invoice_id.id, + "line_recurrence": self.partner_invoice_id.id, } - @api.multi def action_create_contract(self): - contract_model = self.env['contract.contract'] - contracts = self.env['contract.contract'] - for rec in self.filtered('is_contract'): + contract_model = self.env["contract.contract"] + contracts = self.env["contract.contract"] + for rec in self.filtered("is_contract"): line_to_create_contract = rec.order_line.filtered( 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 and r.product_id.is_contract and r - not in r.contract_id.contract_line_ids.mapped( - 'sale_order_line_id' - ) + not in r.contract_id.contract_line_ids.mapped("sale_order_line_id") ) contract_templates = self.env["contract.template"] for order_line in line_to_create_contract: @@ -90,19 +80,18 @@ class SaleOrder(models.Model): ).property_contract_template_id if not contract_template: raise ValidationError( - _("You must specify a contract " - "template for '{}' product in '{}' company.").format( - order_line.product_id.name, - rec.company_id.name - ) + _( + "You must specify a contract " + "template for '{}' product in '{}' company." + ).format(order_line.product_id.name, rec.company_id.name) ) contract_templates |= contract_template for contract_template in contract_templates: order_lines = line_to_create_contract.filtered( - lambda r, template=contract_template: - r.product_id.with_context( - force_company=r.order_id.company_id.id - ).property_contract_template_id == template + lambda r, template=contract_template: r.product_id.with_context( + force_company=r.order_id.company_id.id + ).property_contract_template_id + == template ) contract = contract_model.create( rec._prepare_contract_value(contract_template) @@ -111,39 +100,32 @@ class SaleOrder(models.Model): contract._onchange_contract_template_id() contract._onchange_contract_type() 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: line.create_contract_line(line.contract_id) return contracts - @api.multi def action_confirm(self): """ If we have a contract in the order, set it up """ self.filtered( - lambda order: ( - order.company_id.create_contract_at_sale_order_confirmation - ) + lambda order: (order.company_id.create_contract_at_sale_order_confirmation) ).action_create_contract() return super(SaleOrder, self).action_confirm() - @api.multi @api.depends("order_line") def _compute_contract_count(self): for rec in self: rec.contract_count = len( - rec.order_line.mapped('contract_id').filtered( - lambda r: r.active)) + rec.order_line.mapped("contract_id").filtered(lambda r: r.active) + ) - @api.multi def action_show_contracts(self): self.ensure_one() - action = self.env.ref( - "contract.action_customer_contract" - ).read()[0] + action = self.env.ref("contract.action_customer_contract").read()[0] contracts = ( - self.env['contract.line'] - .search([('sale_order_line_id', 'in', self.order_line.ids)]) - .mapped('contract_id') + self.env["contract.line"] + .search([("sale_order_line_id", "in", self.order_line.ids)]) + .mapped("contract_id") ) action["domain"] = [("id", "in", contracts.ids)] if len(contracts) == 1: @@ -152,9 +134,7 @@ class SaleOrder(models.Model): { "res_id": contracts.id, "view_mode": "form", - "views": filter( - lambda view: view[1] == 'form', action['views'] - ), + "views": filter(lambda view: view[1] == "form", action["views"]), } ) return action diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 4883306db..fbe27f263 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -3,47 +3,48 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from dateutil.relativedelta import relativedelta -from odoo import api, fields, models, _ + +from odoo import _, api, fields, models from odoo.exceptions import ValidationError class SaleOrderLine(models.Model): - _inherit = 'sale.order.line' + _inherit = "sale.order.line" 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( - comodel_name='contract.contract', string='Contract', copy=False + comodel_name="contract.contract", string="Contract", copy=False ) contract_template_id = fields.Many2one( - comodel_name='contract.template', - string='Contract Template', - compute='_compute_contract_template_id', + comodel_name="contract.template", + string="Contract Template", + compute="_compute_contract_template_id", ) recurring_rule_type = fields.Selection( [ - ('daily', 'Day(s)'), - ('weekly', 'Week(s)'), - ('monthly', 'Month(s)'), - ('monthlylastday', 'Month(s) last day'), - ('quarterly', 'Quarter(s)'), - ('semesterly', 'Semester(s)'), - ('yearly', 'Year(s)'), + ("daily", "Day(s)"), + ("weekly", "Week(s)"), + ("monthly", "Month(s)"), + ("monthlylastday", "Month(s) last day"), + ("quarterly", "Quarter(s)"), + ("semesterly", "Semester(s)"), + ("yearly", "Year(s)"), ], - default='monthly', - string='Invoice Every', + default="monthly", + string="Invoice Every", copy=False, ) recurring_invoicing_type = fields.Selection( - [('pre-paid', 'Pre-paid'), ('post-paid', 'Post-paid')], - default='pre-paid', - string='Invoicing type', + [("pre-paid", "Pre-paid"), ("post-paid", "Post-paid")], + default="pre-paid", + string="Invoicing type", help="Specify if process date is 'from' or 'to' invoicing date", copy=False, ) - date_start = fields.Date(string='Date Start') - date_end = fields.Date(string='Date End') + date_start = fields.Date(string="Date Start") + date_end = fields.Date(string="Date End") contract_line_id = fields.Many2one( comodel_name="contract.line", @@ -53,42 +54,38 @@ class SaleOrderLine(models.Model): ) is_auto_renew = fields.Boolean(string="Auto Renew", default=False) auto_renew_interval = fields.Integer( - default=1, - string='Renew Every', - help="Renew every (Days/Week/Month/Year)", + default=1, string="Renew Every", help="Renew every (Days/Week/Month/Year)", ) auto_renew_rule_type = fields.Selection( [ - ('daily', 'Day(s)'), - ('weekly', 'Week(s)'), - ('monthly', 'Month(s)'), - ('yearly', 'Year(s)'), + ("daily", "Day(s)"), + ("weekly", "Week(s)"), + ("monthly", "Month(s)"), + ("yearly", "Year(s)"), ], - default='yearly', - string='Renewal type', + default="yearly", + string="Renewal type", help="Specify Interval for automatic renewal.", ) - @api.constrains('contract_id') + @api.constrains("contract_id") def check_contact_is_not_terminated(self): for rec in self: 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 ): raise ValidationError( _("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): for rec in self: rec.contract_template_id = rec.product_id.with_context( force_company=rec.order_id.company_id.id ).property_contract_template_id - @api.multi def _get_auto_renew_rule_type(self): """monthly last day don't make sense for auto_renew_rule_type""" self.ensure_one() @@ -96,52 +93,43 @@ class SaleOrderLine(models.Model): return "monthly" return self.recurring_rule_type - @api.onchange('product_id') + @api.onchange("product_id") def onchange_product(self): - contract_line_model = self.env['contract.line'] + contract_line_model = self.env["contract.line"] for rec in self: if rec.product_id.is_contract: rec.product_uom_qty = rec.product_id.default_qty rec.recurring_rule_type = rec.product_id.recurring_rule_type - rec.recurring_invoicing_type = ( - rec.product_id.recurring_invoicing_type - ) + rec.recurring_invoicing_type = rec.product_id.recurring_invoicing_type rec.date_start = rec.date_start or fields.Date.today() rec.date_end = ( rec.date_start + contract_line_model.get_relative_delta( - rec._get_auto_renew_rule_type(), - int(rec.product_uom_qty), + rec._get_auto_renew_rule_type(), int(rec.product_uom_qty), ) - relativedelta(days=1) ) rec.is_auto_renew = rec.product_id.is_auto_renew if rec.is_auto_renew: - rec.auto_renew_interval = ( - rec.product_id.auto_renew_interval - ) - rec.auto_renew_rule_type = ( - rec.product_id.auto_renew_rule_type - ) + rec.auto_renew_interval = rec.product_id.auto_renew_interval + 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): - contract_line_model = self.env['contract.line'] - for rec in self.filtered('product_id.is_contract'): + contract_line_model = self.env["contract.line"] + for rec in self.filtered("product_id.is_contract"): if not rec.date_start: rec.date_end = False else: rec.date_end = ( rec.date_start + contract_line_model.get_relative_delta( - rec._get_auto_renew_rule_type(), - int(rec.product_uom_qty), + rec._get_auto_renew_rule_type(), int(rec.product_uom_qty), ) - relativedelta(days=1) ) - @api.multi def _prepare_contract_line_values( self, contract, predecessor_contract_line_id=False ): @@ -152,23 +140,19 @@ class SaleOrderLine(models.Model): """ self.ensure_one() recurring_next_date = self.env[ - 'contract.line' + "contract.line" ]._compute_first_recurring_next_date( self.date_start or fields.Date.today(), self.recurring_invoicing_type, self.recurring_rule_type, 1, ) - termination_notice_interval = ( - self.product_id.termination_notice_interval - ) - termination_notice_rule_type = ( - self.product_id.termination_notice_rule_type - ) + termination_notice_interval = self.product_id.termination_notice_interval + termination_notice_rule_type = self.product_id.termination_notice_rule_type return { - 'sequence': self.sequence, - 'product_id': self.product_id.id, - 'name': self.name, + "sequence": self.sequence, + "product_id": self.product_id.id, + "name": self.name, # The quantity on the generated contract line is 1, as it # correspond to the most common use cases: # - 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 # line is not used # Other use cases are easy to implement by overriding this method. - 'quantity': 1.0, - 'uom_id': self.product_uom.id, - 'price_unit': self.price_unit, - 'discount': self.discount, - 'date_end': self.date_end, - 'date_start': self.date_start or fields.Date.today(), - 'recurring_next_date': recurring_next_date, - 'recurring_interval': 1, - 'recurring_invoicing_type': self.recurring_invoicing_type, - 'recurring_rule_type': self.recurring_rule_type, - 'is_auto_renew': self.is_auto_renew, - 'auto_renew_interval': self.auto_renew_interval, - 'auto_renew_rule_type': self.auto_renew_rule_type, - 'termination_notice_interval': termination_notice_interval, - 'termination_notice_rule_type': termination_notice_rule_type, - 'contract_id': contract.id, - 'sale_order_line_id': self.id, - 'predecessor_contract_line_id': predecessor_contract_line_id, - 'analytic_account_id': self.order_id.analytic_account_id.id, + "quantity": 1.0, + "uom_id": self.product_uom.id, + "price_unit": self.price_unit, + "discount": self.discount, + "date_end": self.date_end, + "date_start": self.date_start or fields.Date.today(), + "recurring_next_date": recurring_next_date, + "recurring_interval": 1, + "recurring_invoicing_type": self.recurring_invoicing_type, + "recurring_rule_type": self.recurring_rule_type, + "is_auto_renew": self.is_auto_renew, + "auto_renew_interval": self.auto_renew_interval, + "auto_renew_rule_type": self.auto_renew_rule_type, + "termination_notice_interval": termination_notice_interval, + "termination_notice_rule_type": termination_notice_rule_type, + "contract_id": contract.id, + "sale_order_line_id": self.id, + "predecessor_contract_line_id": predecessor_contract_line_id, + "analytic_account_id": self.order_id.analytic_account_id.id, } - @api.multi def create_contract_line(self, contract): - contract_line_model = self.env['contract.line'] - contract_line = self.env['contract.line'] + contract_line_model = self.env["contract.line"] + contract_line = self.env["contract.line"] predecessor_contract_line = False for rec in self: if rec.contract_line_id: @@ -221,9 +204,7 @@ class SaleOrderLine(models.Model): not rec.contract_line_id.date_end or rec.date_start <= rec.contract_line_id.date_end ): - rec.contract_line_id.stop( - rec.date_start - relativedelta(days=1) - ) + rec.contract_line_id.stop(rec.date_start - relativedelta(days=1)) predecessor_contract_line = rec.contract_line_id if predecessor_contract_line: new_contract_line = contract_line_model.create( @@ -231,9 +212,7 @@ class SaleOrderLine(models.Model): contract, predecessor_contract_line.id ) ) - predecessor_contract_line.successor_contract_line_id = ( - new_contract_line - ) + predecessor_contract_line.successor_contract_line_id = new_contract_line else: new_contract_line = contract_line_model.create( rec._prepare_contract_line_values(contract) @@ -241,7 +220,7 @@ class SaleOrderLine(models.Model): contract_line |= new_contract_line return contract_line - @api.constrains('contract_id') + @api.constrains("contract_id") def _check_contract_sale_partner(self): for rec in self: 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): for rec in self: if rec.contract_id: if ( rec.contract_id.contract_template_id - and rec.contract_template_id - != rec.contract_id.contract_template_id + and rec.contract_template_id != rec.contract_id.contract_template_id ): raise ValidationError( _("Contract product has different contract template") @@ -268,27 +246,26 @@ class SaleOrderLine(models.Model): def _compute_invoice_status(self): res = super(SaleOrderLine, self)._compute_invoice_status() - for line in self.filtered('contract_id'): - line.invoice_status = 'no' + for line in self.filtered("contract_id"): + line.invoice_status = "no" return res - @api.multi def invoice_line_create(self, invoice_id, qty): return super( SaleOrderLine, self.filtered(lambda l: not l.contract_id) ).invoice_line_create(invoice_id, qty) @api.depends( - 'qty_invoiced', - 'qty_delivered', - 'product_uom_qty', - 'order_id.state', - 'product_id.is_contract', + "qty_invoiced", + "qty_delivered", + "product_uom_qty", + "order_id.state", + "product_id.is_contract", ) def _get_to_invoice_qty(self): """ sale line linked to contracts must not be invoiced from sale order """ 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 diff --git a/product_contract/tests/test_product.py b/product_contract/tests/test_product.py index e5ef52c8a..cc6afedae 100644 --- a/product_contract/tests/test_product.py +++ b/product_contract/tests/test_product.py @@ -2,18 +2,16 @@ # Copyright 2018 ACSONE SA/NV. # 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.tests.common import TransactionCase class TestProductTemplate(TransactionCase): def setUp(self): super(TestProductTemplate, self).setUp() - self.service_product = self.env.ref('product.product_product_1') - self.consu_product = self.env.ref('product.product_product_5') - self.contract = self.env['contract.template'].create( - {'name': 'Test'} - ) + self.service_product = self.env.ref("product.product_product_1") + self.consu_product = self.env.ref("product.product_product_5") + self.contract = self.env["contract.template"].create({"name": "Test"}) def test_change_is_contract(self): """ 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.property_contract_template_id = self.contract.id self.service_product.is_contract = False - self.assertEquals(len( - self.service_product.property_contract_template_id), 0) + self.assertEquals(len(self.service_product.property_contract_template_id), 0) def test_check_contract_product_type(self): """ diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index b007f6dfd..728126512 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -3,62 +3,61 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from dateutil.relativedelta import relativedelta -from odoo.tests.common import TransactionCase + from odoo.exceptions import ValidationError from odoo.fields import Date +from odoo.tests.common import TransactionCase class TestSaleOrder(TransactionCase): def setUp(self): super(TestSaleOrder, self).setUp() - self.product1 = self.env.ref('product.product_product_1') - self.product2 = self.env.ref('product.product_product_2') - self.sale = self.env.ref('sale.sale_order_2') - self.contract_template1 = self.env['contract.template'].create( - {'name': 'Template 1'} + self.product1 = self.env.ref("product.product_product_1") + self.product2 = self.env.ref("product.product_product_2") + self.sale = self.env.ref("sale.sale_order_2") + self.contract_template1 = self.env["contract.template"].create( + {"name": "Template 1"} ) - self.contract_template2 = self.env['contract.template'].create( + self.contract_template2 = self.env["contract.template"].create( { - 'name': 'Template 2', - 'contract_line_ids': [ + "name": "Template 2", + "contract_line_ids": [ ( 0, 0, { - 'product_id': self.product2.id, - 'name': 'Services from #START# to #END#', - 'quantity': 1, - 'uom_id': self.product2.uom_id.id, - 'price_unit': 100, - 'discount': 50, - 'recurring_rule_type': 'yearly', - 'recurring_interval': 1, + "product_id": self.product2.id, + "name": "Services from #START# to #END#", + "quantity": 1, + "uom_id": self.product2.uom_id.id, + "price_unit": 100, + "discount": 50, + "recurring_rule_type": "yearly", + "recurring_interval": 1, }, ) ], } ) - self.product1.with_context( - force_company=self.sale.company_id.id).write( + self.product1.with_context(force_company=self.sale.company_id.id).write( { - 'is_contract': True, - 'default_qty': 12, - 'recurring_rule_type': "monthlylastday", - 'recurring_invoicing_type': "post-paid", - 'property_contract_template_id': self.contract_template1.id, + "is_contract": True, + "default_qty": 12, + "recurring_rule_type": "monthlylastday", + "recurring_invoicing_type": "post-paid", + "property_contract_template_id": self.contract_template1.id, } ) - self.product2.with_context( - force_company=self.sale.company_id.id).write( + self.product2.with_context(force_company=self.sale.company_id.id).write( { - 'is_contract': True, - 'property_contract_template_id': self.contract_template2.id, + "is_contract": True, + "property_contract_template_id": self.contract_template2.id, } ) self.order_line1 = self.sale.order_line.filtered( 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 pricelist = self.sale.partner_id.property_product_pricelist.id self.contract = self.env["contract.contract"].create( @@ -67,6 +66,7 @@ class TestSaleOrder(TransactionCase): "partner_id": self.sale.partner_id.id, "pricelist_id": pricelist, "contract_type": "sale", + "line_recurrence": True, "contract_template_id": self.contract_template1.id, "contract_line_ids": [ ( @@ -100,23 +100,20 @@ class TestSaleOrder(TransactionCase): order_line """ self.order_line1.onchange_product() 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( - self.order_line1.contract_id.contract_template_id, - self.contract_template1, + self.order_line1.contract_id.contract_template_id, self.contract_template1, ) 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_end, Date.to_date('2018-12-31')) - self.assertEqual( - contract_line.recurring_next_date, Date.to_date('2018-01-31') - ) + 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.recurring_next_date, Date.to_date("2018-01-31")) def test_change_sale_company(self): self.assertTrue(self.sale.company_id) - other_company = self.env['res.company'].create( - {'name': 'other company', 'parent_id': self.sale.company_id.id} + other_company = self.env["res.company"].create( + {"name": "other company", "parent_id": self.sale.company_id.id} ) self.sale.company_id = other_company with self.assertRaises(ValidationError): @@ -125,8 +122,8 @@ class TestSaleOrder(TransactionCase): def test_change_sale_company_2(self): """Contract company must be the sale order company.""" self.assertTrue(self.sale.company_id) - other_company = self.env['res.company'].create( - {'name': 'other company', 'parent_id': self.sale.company_id.id} + other_company = self.env["res.company"].create( + {"name": "other company", "parent_id": self.sale.company_id.id} ) self.product1.with_context( force_company=other_company.id @@ -136,15 +133,15 @@ class TestSaleOrder(TransactionCase): ).property_contract_template_id = self.contract_template2 self.sale.company_id = other_company self.sale.action_confirm() - contracts = self.sale.order_line.mapped('contract_id') - self.assertEqual(contracts.mapped('company_id'), other_company) + contracts = self.sale.order_line.mapped("contract_id") + self.assertEqual(contracts.mapped("company_id"), other_company) def test_sale_order_invoice_status(self): """ sale line linked to contracts must not be invoiced from sale order """ 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() self.assertTrue(invoice) 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.order_line1.onchange_product() 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.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.assertEqual( - self.order_line1.contract_id.contract_template_id, - self.contract_template1, + self.order_line1.contract_id.contract_template_id, self.contract_template1, ) 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_end, Date.to_date('2018-12-31')) - self.assertEqual( - contract_line.recurring_next_date, Date.to_date('2018-01-31') - ) + 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.recurring_next_date, Date.to_date("2018-01-31")) def test_sale_contract_count(self): """It should count contracts as many different contract template used @@ -184,23 +178,23 @@ class TestSaleOrder(TransactionCase): its product """ self.order_line1.onchange_product() self.assertEqual( - self.order_line1.recurring_rule_type, - self.product1.recurring_rule_type, + self.order_line1.recurring_rule_type, self.product1.recurring_rule_type, ) self.assertEqual( self.order_line1.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): """Can't link order line to a partner contract different then the order one""" - contract2 = self.env['contract.contract'].create( + contract2 = self.env["contract.contract"].create( { - 'name': 'Contract', - 'contract_template_id': self.contract_template2.id, - 'partner_id': self.sale.partner_id.id, + "name": "Contract", + "contract_template_id": self.contract_template2.id, + "partner_id": self.sale.partner_id.id, + "line_recurrence": True, } ) with self.assertRaises(ValidationError): @@ -209,11 +203,12 @@ class TestSaleOrder(TransactionCase): def test_check_contract_sale_contract_template(self): """Can't link order line to a contract with different contract template then the product one""" - contract1 = self.env['contract.contract'].create( + contract1 = self.env["contract.contract"].create( { - 'name': 'Contract', - 'partner_id': self.env.user.partner_id.id, - 'contract_template_id': self.contract_template1.id, + "name": "Contract", + "partner_id": self.env.user.partner_id.id, + "contract_template_id": self.contract_template1.id, + "line_recurrence": True, } ) with self.assertRaises(ValidationError): @@ -230,7 +225,7 @@ class TestSaleOrder(TransactionCase): invoice as status""" self.order_line1.onchange_product() 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): """Sale order with only contract product should have nothing to @@ -240,15 +235,15 @@ class TestSaleOrder(TransactionCase): ).unlink() self.order_line1.onchange_product() 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): """Should not invoice contract product on sale order create invoice""" self.product2.is_contract = False - self.product2.invoice_policy = 'order' + self.product2.invoice_policy = "order" self.order_line1.onchange_product() self.sale.action_confirm() - self.sale.action_invoice_create() + self.sale._create_invoices() self.assertEqual(len(self.sale.invoice_ids), 1) invoice_line = self.sale.invoice_ids.invoice_line_ids.filtered( 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.onchange_product() self.sale.action_confirm() - self.assertEqual( - self.contract_line.date_end, Date.to_date("2018-05-31") - ) + self.assertEqual(self.contract_line.date_end, Date.to_date("2018-05-31")) self.assertFalse(self.contract_line.is_auto_renew) - new_contract_line = self.env['contract.line'].search( - [('sale_order_line_id', '=', self.order_line1.id)] + new_contract_line = self.env["contract.line"].search( + [("sale_order_line_id", "=", self.order_line1.id)] ) self.assertEqual( 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.contract_line.write( { - 'date_start': "2018-06-01", - 'recurring_next_date': "2018-06-01", - 'date_end': False, + "date_start": "2018-06-01", + "recurring_next_date": "2018-06-01", + "date_end": False, } ) self.order_line1.date_start = "2018-06-01" @@ -305,42 +298,38 @@ class TestSaleOrder(TransactionCase): def test_onchange_product_id_recurring_info(self): self.product2.write( { - 'recurring_rule_type': 'monthly', - 'recurring_invoicing_type': 'pre-paid', - 'is_auto_renew': True, - 'default_qty': 12, - 'termination_notice_interval': '6', - 'termination_notice_rule_type': 'weekly', + "recurring_rule_type": "monthly", + "recurring_invoicing_type": "pre-paid", + "is_auto_renew": True, + "default_qty": 12, + "termination_notice_interval": "6", + "termination_notice_rule_type": "weekly", } ) self.contract_line.write( { - 'date_start': Date.today(), - 'date_end': Date.today() + relativedelta(years=1), - 'recurring_next_date': Date.today(), - 'product_id': self.product2.id, + "date_start": Date.today(), + "date_end": Date.today() + relativedelta(years=1), + "recurring_next_date": Date.today(), + "product_id": self.product2.id, } ) self.contract_line._onchange_product_id_recurring_info() - self.assertEqual(self.contract_line.recurring_rule_type, 'monthly') - self.assertEqual( - self.contract_line.recurring_invoicing_type, 'pre-paid' - ) + self.assertEqual(self.contract_line.recurring_rule_type, "monthly") + self.assertEqual(self.contract_line.recurring_invoicing_type, "pre-paid") self.assertEqual(self.contract_line.recurring_interval, 1) 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_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_rule_type, 'weekly' - ) + self.assertEqual(self.contract_line.termination_notice_rule_type, "weekly") def test_action_show_contracts(self): self.sale.action_confirm() action = self.sale.action_show_contracts() self.assertEqual( - self.env['contract.contract'].search(action['domain']), - self.sale.order_line.mapped('contract_id'), + self.env["contract.contract"].search(action["domain"]), + self.sale.order_line.mapped("contract_id"), ) def test_check_contact_is_not_terminated(self): @@ -348,7 +337,7 @@ class TestSaleOrder(TransactionCase): with self.assertRaises(ValidationError): 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.sale.action_confirm() self.contract.is_terminated = True @@ -361,22 +350,20 @@ class TestSaleOrder(TransactionCase): def test_order_lines_with_the_same_contract_template(self): """ It should create one contract with two lines grouped by contract template""" - self.product2.with_context( - force_company=self.sale.company_id.id - ).write( + self.product2.with_context(force_company=self.sale.company_id.id).write( { - 'is_contract': True, - 'property_contract_template_id': self.contract_template1.id, + "is_contract": True, + "property_contract_template_id": self.contract_template1.id, } ) self.sale.order_line.onchange_product() 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.contract_line_ids), 2) contracts = ( - self.env['contract.line'] - .search([('sale_order_line_id', 'in', self.sale.order_line.ids)]) - .mapped('contract_id') + self.env["contract.line"] + .search([("sale_order_line_id", "in", self.sale.order_line.ids)]) + .mapped("contract_id") ) self.assertEqual(len(contracts), 1) diff --git a/product_contract/views/contract.xml b/product_contract/views/contract.xml index 1762136ac..0b67d521b 100644 --- a/product_contract/views/contract.xml +++ b/product_contract/views/contract.xml @@ -1,23 +1,24 @@ - + - - + contract.contract - + - - + - - - + + - - - + ]" + /> + - - - - + + + - + - + - + - + - - + + - - - - - + + + - - + + -