From 33f76c28f5519830fc6e47d9db9e8923bf0dc72b Mon Sep 17 00:00:00 2001 From: Ted Salmon Date: Thu, 12 Jan 2017 13:16:25 -0800 Subject: [PATCH] [ADD] product_contract: Create module * Add contract functionality to `product.templates` * Add logic to create contracts from `sale.order` that contains contract products. --- product_contract/README.rst | 66 +++++++++++++++++++ product_contract/__init__.py | 5 ++ product_contract/__manifest__.py | 23 +++++++ product_contract/models/__init__.py | 7 ++ product_contract/models/product_template.py | 23 +++++++ product_contract/models/sale_order.py | 27 ++++++++ product_contract/models/sale_order_line.py | 14 ++++ product_contract/tests/__init__.py | 6 ++ .../tests/test_product_template.py | 31 +++++++++ product_contract/tests/test_sale_order.py | 34 ++++++++++ .../views/product_template_view.xml | 29 ++++++++ 11 files changed, 265 insertions(+) create mode 100644 product_contract/README.rst create mode 100644 product_contract/__init__.py create mode 100644 product_contract/__manifest__.py create mode 100644 product_contract/models/__init__.py create mode 100644 product_contract/models/product_template.py create mode 100644 product_contract/models/sale_order.py create mode 100644 product_contract/models/sale_order_line.py create mode 100644 product_contract/tests/__init__.py create mode 100644 product_contract/tests/test_product_template.py create mode 100644 product_contract/tests/test_sale_order.py create mode 100644 product_contract/views/product_template_view.xml diff --git a/product_contract/README.rst b/product_contract/README.rst new file mode 100644 index 00000000..6319dcf5 --- /dev/null +++ b/product_contract/README.rst @@ -0,0 +1,66 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================ +Product Contract +================ + +This module adds support for products to be linked to contract templates. +It also adds functionality to automatically create a contract, from the template, +when a ``sale.order`` contains a product that implements a contract. + +Usage +===== + +To use this module, you need to: + +#. Go to Sales -> Products and select or create a product. +#. Check "Is a contract" and select the contract template related to the + product + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/110/10.0 + +Known issues / Roadmap +====================== + +* None + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Ted Salmon + + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/product_contract/__init__.py b/product_contract/__init__.py new file mode 100644 index 00000000..44db863b --- /dev/null +++ b/product_contract/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/product_contract/__manifest__.py b/product_contract/__manifest__.py new file mode 100644 index 00000000..e1d4d8ac --- /dev/null +++ b/product_contract/__manifest__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Product Contract', + 'version': '10.0.1.0.0', + 'category': 'Contract Management', + 'license': 'AGPL-3', + 'author': "LasLabs, " + "Odoo Community Association (OCA)", + 'website': 'https://laslabs.com', + 'depends': [ + 'contract', + 'product', + 'sale', + ], + 'data': [ + 'views/product_template_view.xml', + ], + 'installable': True, + 'application': False, +} diff --git a/product_contract/models/__init__.py b/product_contract/models/__init__.py new file mode 100644 index 00000000..388717d2 --- /dev/null +++ b/product_contract/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import product_template +from . import sale_order +from . import sale_order_line diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py new file mode 100644 index 00000000..fd7e00d4 --- /dev/null +++ b/product_contract/models/product_template.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + is_contract = fields.Boolean('Is a contract') + contract_template_id = fields.Many2one( + comodel_name='account.analytic.contract', + string='Contract Template', + ) + + @api.onchange('is_contract') + def _change_is_contract(self): + """ Clear the relation to contract_template_id when downgrading + product from contract + """ + if not self.is_contract: + self.contract_template_id = False diff --git a/product_contract/models/sale_order.py b/product_contract/models/sale_order.py new file mode 100644 index 00000000..45f2eec7 --- /dev/null +++ b/product_contract/models/sale_order.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + @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 + ) + 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() + return super(SaleOrder, self).action_confirm() diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py new file mode 100644 index 00000000..c76c1059 --- /dev/null +++ b/product_contract/models/sale_order_line.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + contract_id = fields.Many2one( + comodel_name='account.analytic.account', + string='Contract' + ) diff --git a/product_contract/tests/__init__.py b/product_contract/tests/__init__.py new file mode 100644 index 00000000..e5fbe249 --- /dev/null +++ b/product_contract/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_product_template +from . import test_sale_order diff --git a/product_contract/tests/test_product_template.py b/product_contract/tests/test_product_template.py new file mode 100644 index 00000000..2938cc7f --- /dev/null +++ b/product_contract/tests/test_product_template.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + + +class TestProductTemplate(TransactionCase): + + def setUp(self): + super(TestProductTemplate, self).setUp() + self.product = self.env.ref( + 'product.product_product_4_product_template' + ) + self.contract = self.env['account.analytic.contract'].create({ + 'name': 'Test', + 'recurring_rule_type': 'yearly', + 'recurring_interval': 12345, + }) + + def test_change_is_contract(self): + """ It should verify that the contract_template_id is removed + when is_contract is False """ + self.product.is_contract = True + self.product.contract_template_id = self.contract.id + self.product.is_contract = False + self.product._change_is_contract() + self.assertEquals( + len(self.product.contract_template_id), + 0 + ) diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py new file mode 100644 index 00000000..7ca4c099 --- /dev/null +++ b/product_contract/tests/test_sale_order.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# 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 + + +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', + 'recurring_rule_type': 'yearly', + 'recurring_interval': 12345, + }) + self.product.product_tmpl_id.is_contract = True + self.product.product_tmpl_id.contract_template_id = self.contract.id + + def test_action_done(self): + """ It should create a contract when the sale for a contract is set + to done for the first time """ + self.env['account.analytic.account']._patch_method( + 'create', MagicMock() + ) + 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, + }) diff --git a/product_contract/views/product_template_view.xml b/product_contract/views/product_template_view.xml new file mode 100644 index 00000000..46c2e05c --- /dev/null +++ b/product_contract/views/product_template_view.xml @@ -0,0 +1,29 @@ + + + + + + + + account.invoice.select.contract + product.template + + + +
+ +
+
+ + + +
+
+ +