diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py index 443eaaff..4b356f32 100644 --- a/product_contract/models/sale_order.py +++ b/product_contract/models/sale_order.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2017 LasLabs Inc. +# Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import fields, api, models @@ -9,29 +10,61 @@ class SaleOrder(models.Model): _inherit = 'sale.order' 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') @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 action_confirm(self): """ If we have a contract in the order, set it up """ - for rec in self: - order_lines = self.mapped('order_line').filtered( - lambda r: r.product_id.is_contract + contract_env = self.env['account.analytic.account'] + for rec in self.filtered('is_contract'): + line_to_create_contract = rec.order_line.filtered( + lambda r: not r.contract_id ) - for line in order_lines: - contract_tmpl = line.product_id.contract_template_id - contract = self.env['account.analytic.account'].create({ - 'name': '%s Contract' % rec.name, - 'partner_id': rec.partner_id.id, - 'contract_template_id': contract_tmpl.id, - }) - line.contract_id = contract.id - contract.recurring_create_invoice() + for contract_template in line_to_create_contract.mapped( + 'product_id.contract_template_id' + ): + order_lines = line_to_create_contract.filtered( + lambda r: r.product_id.contract_template_id + == contract_template + ) + contract = contract_env.create( + { + 'name': '{template_name}: {sale_name}'.format( + template_name=contract_template.name, + sale_name=rec.name, + ), + 'partner_id': rec.partner_id.id, + 'recurring_invoices': True, + 'contract_template_id': contract_template.id, + } + ) + contract._onchange_contract_template_id() + order_lines.create_contract_line(contract) + order_lines.write({'contract_id': contract.id}) + line_to_update_contract = rec.order_line.filtered('contract_id') + for line in line_to_update_contract: + line.create_contract_line(line.contract_id) 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')) + + @api.multi + def action_show_contracts(self): + self.ensure_one() + action = self.env.ref( + "contract.action_account_analytic_sale_overdue_all" + ).read()[0] + action["domain"] = [ + ("id", "in", self.order_line.mapped('contract_id').ids) + ] + return action diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 23da9c1c..b275e67e 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -3,7 +3,8 @@ # Copyright 2017 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 SaleOrderLine(models.Model): @@ -13,7 +14,13 @@ class SaleOrderLine(models.Model): string='Is a contract', related="product_id.is_contract" ) contract_id = fields.Many2one( - comodel_name='account.analytic.account', string='Contract' + comodel_name='account.analytic.account', string='Contract', copy=False + ) + contract_template_id = fields.Many2one( + comodel_name='account.analytic.contract', + string='Contract Template', + related='product_id.product_tmpl_id.contract_template_id', + readonly=True ) recurring_rule_type = fields.Selection( [ @@ -55,3 +62,54 @@ class SaleOrderLine(models.Model): self.product_id.recurring_invoicing_type ) self.recurring_interval = self.product_id.recurring_interval + + @api.multi + def _prepare_contract_line_values(self, contract): + self.ensure_one() + return { + 'sequence': self.sequence, + 'product_id': self.product_id.id, + 'name': self.name, + 'quantity': self.product_uom_qty, + 'uom_id': self.product_uom.id, + 'price_unit': self.price_unit, + 'discount': self.discount, + 'recurring_next_date': self.recurring_next_date + or fields.Date.today(), + 'date_end': self.date_end, + 'date_start': self.date_start or fields.Date.today(), + 'recurring_interval': self.recurring_interval, + 'recurring_invoicing_type': self.recurring_invoicing_type, + 'recurring_rule_type': self.recurring_rule_type, + 'contract_id': contract.id, + } + + @api.multi + def create_contract_line(self, contract): + contract_line = self.env['account.analytic.invoice.line'] + for rec in self: + contract_line.create(rec._prepare_contract_line_values(contract)) + + @api.constrains('contract_id') + def _check_contract_sale_partner(self): + for rec in self: + if rec.contract_id: + if rec.order_id.partner_id != rec.contract_id.partner_id: + raise ValidationError( + _( + "Sale Order and contract should be " + "linked to the same partner" + ) + ) + + @api.constrains('product_id', 'contract_id') + def _check_contract_sale_contract_template(self): + for rec in self: + if rec.contract_id: + if ( + rec.contract_template_id + != rec.contract_id.contract_template_id + ): + raise ValidationError( + _("Contract product has different contract template") + ) diff --git a/product_contract/tests/__init__.py b/product_contract/tests/__init__.py index 4766d5ea..9d85581f 100644 --- a/product_contract/tests/__init__.py +++ b/product_contract/tests/__init__.py @@ -5,4 +5,3 @@ from . import test_product from . import test_sale_order -from . import test_sale_order_line diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index e8f66602..36e690b1 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -2,39 +2,93 @@ # Copyright 2017 LasLabs Inc. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from mock import MagicMock from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError class TestSaleOrder(TransactionCase): - def setUp(self): super(TestSaleOrder, self).setUp() - self.product = 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.sale = self.env.ref('sale.sale_order_2') - self.contract = self.env['account.analytic.contract'].create({ - 'name': 'Test', - }) - self.product.product_tmpl_id.is_contract = True - self.product.product_tmpl_id.contract_template_id = self.contract.id - - def tearDown(self): - self.env['account.analytic.account']._revert_method( - 'create', + self.contract_template1 = self.env['account.analytic.contract'].create( + {'name': 'Template 1'} + ) + self.contract_template2 = self.env['account.analytic.contract'].create( + {'name': 'Template 2'} + ) + self.product1.write( + { + 'is_contract': True, + 'contract_template_id': self.contract_template1.id, + } + ) + self.product2.write( + { + 'is_contract': True, + 'contract_template_id': self.contract_template2.id, + } + ) + self.order_line1 = self.sale.order_line.filtered( + lambda l: l.product_id == self.product1 ) - super(TestSaleOrder, self).tearDown() - - def test_action_done(self): - """ It should create a contract when the sale for a contract is set - to done for the first time """ + def test_compute_is_contract(self): + """Sale Order should have is_contract true if one of its lines is + contract""" self.assertTrue(self.sale.is_contract) - self.env['account.analytic.account']._patch_method( - 'create', MagicMock() + + def test_action_confirm(self): + """ It should create a contract for each contract template used in + order_line """ + self.sale.action_confirm() + 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, ) + + def test_sale_contract_count(self): + """It should count contracts as many different contract template used + in order_line""" self.sale.action_confirm() - self.env['account.analytic.account'].create.assert_called_once_with({ - 'name': '%s Contract' % self.sale.name, - 'partner_id': self.sale.partner_id.id, - 'contract_template_id': self.contract.id, - }) + self.assertEqual(self.sale.contract_count, 2) + + def test_onchange_product(self): + """ It should get recurrence invoicing info to the sale line from + its product """ + self.assertEqual( + self.order_line1.recurring_rule_type, + self.product1.recurring_rule_type, + ) + self.assertEqual( + self.order_line1.recurring_interval, + self.product1.recurring_interval, + ) + self.assertEqual( + self.order_line1.recurring_invoicing_type, + self.product1.recurring_invoicing_type, + ) + + def test_check_contract_sale_partner(self): + contract2 = self.env['account.analytic.account'].create( + { + 'name': 'Contract', + 'contract_template_id': self.contract_template2.id, + 'partner_id': self.sale.partner_id.id, + } + ) + with self.assertRaises(ValidationError): + self.order_line1.contract_id = contract2 + + def test_check_contract_sale_contract_template(self): + contract1 = self.env['account.analytic.account'].create( + { + 'name': 'Contract', + 'contract_template_id': self.contract_template1.id, + } + ) + with self.assertRaises(ValidationError): + self.order_line1.contract_id = contract1 diff --git a/product_contract/tests/test_sale_order_line.py b/product_contract/tests/test_sale_order_line.py deleted file mode 100644 index 1f109059..00000000 --- a/product_contract/tests/test_sale_order_line.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2018 ACSONE SA/NV. -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from mock import MagicMock -from odoo.tests.common import TransactionCase - - -class TestSaleOrder(TransactionCase): - - def setUp(self): - super(TestSaleOrder, self).setUp() - self.product = self.env.ref('product.product_product_1') - self.sale = self.env.ref('sale.sale_order_2') - self.contract = self.env['account.analytic.contract'].create({ - 'name': 'Test', - }) - self.product.product_tmpl_id.is_contract = True - self.sale_order_line = self.sale.order_line.filtered( - lambda l: l.product_id == self.product - ) - - def test_onchange_product(self): - """ It should get recurrence invoicing info to the sale line from - its product """ - self.assertEqual( - self.sale_order_line.recurring_rule_type, - self.product.recurring_rule_type - ) - self.assertEqual( - self.sale_order_line.recurring_interval, - self.product.recurring_interval - ) - self.assertEqual( - self.sale_order_line.recurring_invoicing_type, - self.product.recurring_invoicing_type - ) diff --git a/product_contract/views/sale_order.xml b/product_contract/views/sale_order.xml index f27c4025..70397c32 100644 --- a/product_contract/views/sale_order.xml +++ b/product_contract/views/sale_order.xml @@ -12,9 +12,27 @@ sale.order + + + + + + +