diff --git a/contract_sale_generation/__init__.py b/contract_sale_generation/__init__.py index a9e337226..0650744f6 100644 --- a/contract_sale_generation/__init__.py +++ b/contract_sale_generation/__init__.py @@ -1,2 +1 @@ - from . import models diff --git a/contract_sale_generation/__manifest__.py b/contract_sale_generation/__manifest__.py index 2dd931b40..053a25239 100644 --- a/contract_sale_generation/__manifest__.py +++ b/contract_sale_generation/__manifest__.py @@ -4,18 +4,17 @@ { - 'name': 'Contracts Management - Recurring Sales', - 'version': '12.0.1.0.2', - 'category': 'Contract Management', - 'license': 'AGPL-3', - 'author': "PESOL, " - "Odoo Community Association (OCA)", - 'website': 'https://github.com/oca/contract', - 'depends': ['contract', 'sale'], - 'data': [ - 'data/contract_cron.xml', - 'views/contract.xml', - 'views/contract_template.xml', + "name": "Contracts Management - Recurring Sales", + "version": "14.0.1.0.0", + "category": "Contract Management", + "license": "AGPL-3", + "author": "PESOL, " "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/contract", + "depends": ["contract", "sale"], + "data": [ + "data/contract_cron.xml", + "views/contract.xml", + "views/contract_template.xml", ], - 'installable': True, + "installable": True, } diff --git a/contract_sale_generation/data/contract_cron.xml b/contract_sale_generation/data/contract_cron.xml index 841f52971..c2f206dc1 100644 --- a/contract_sale_generation/data/contract_cron.xml +++ b/contract_sale_generation/data/contract_cron.xml @@ -1,8 +1,7 @@ - - + Generate Recurring sales from Contracts - + code model.cron_recurring_create_sale() @@ -11,5 +10,4 @@ -1 - diff --git a/contract_sale_generation/models/abstract_contract.py b/contract_sale_generation/models/abstract_contract.py index bda9742c1..e5c4a60c3 100644 --- a/contract_sale_generation/models/abstract_contract.py +++ b/contract_sale_generation/models/abstract_contract.py @@ -6,14 +6,12 @@ from odoo import fields, models class ContractAbstractContract(models.AbstractModel): - _inherit = 'contract.abstract.contract' + _inherit = "contract.abstract.contract" type = fields.Selection( - [('invoice', 'Invoice'), - ('sale', 'Sale')], - string='Type', - default='invoice', + [("invoice", "Invoice"), ("sale", "Sale")], + string="Type", + default="invoice", required=True, ) - sale_autoconfirm = fields.Boolean( - string='Sale Autoconfirm') + sale_autoconfirm = fields.Boolean(string="Sale Autoconfirm") diff --git a/contract_sale_generation/models/contract.py b/contract_sale_generation/models/contract.py index 17c622d12..d8d966e5d 100644 --- a/contract_sale_generation/models/contract.py +++ b/contract_sale_generation/models/contract.py @@ -8,24 +8,26 @@ # Copyright 2018 Therp BV . # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import api, fields, models, _ +from odoo import _, api, fields, models class ContractContract(models.Model): - _inherit = 'contract.contract' + _inherit = "contract.contract" sale_count = fields.Integer(compute="_compute_sale_count") @api.multi def _prepare_sale(self, date_ref): self.ensure_one() - sale = self.env['sale.order'].new({ - 'partner_id': self.partner_id, - 'date_order': fields.Date.to_string(date_ref), - 'origin': self.name, - 'company_id': self.company_id.id, - 'user_id': self.partner_id.user_id.id, - }) + sale = self.env["sale.order"].new( + { + "partner_id": self.partner_id, + "date_order": fields.Date.to_string(date_ref), + "origin": self.name, + "company_id": self.company_id.id, + "user_id": self.partner_id.user_id.id, + } + ) if self.payment_term_id: sale.payment_term_id = self.payment_term_id.id if self.fiscal_position_id: @@ -37,10 +39,11 @@ class ContractContract(models.Model): @api.multi def _get_related_sales(self): self.ensure_one() - sales = (self.env['sale.order.line'] - .search([('contract_line_id', 'in', - self.contract_line_ids.ids) - ]).mapped('order_id')) + sales = ( + self.env["sale.order.line"] + .search([("contract_line_id", "in", self.contract_line_ids.ids)]) + .mapped("order_id") + ) return sales @api.multi @@ -51,20 +54,18 @@ class ContractContract(models.Model): @api.multi def action_show_sales(self): self.ensure_one() - tree_view = self.env.ref('sale.view_order_tree', - raise_if_not_found=False) - form_view = self.env.ref('sale.view_order_form', - raise_if_not_found=False) + tree_view = self.env.ref("sale.view_order_tree", raise_if_not_found=False) + form_view = self.env.ref("sale.view_order_form", raise_if_not_found=False) action = { - 'type': 'ir.actions.act_window', - 'name': 'Sales Orders', - 'res_model': 'sale.order', - 'view_type': 'form', - 'view_mode': 'tree,kanban,form,calendar,pivot,graph,activity', - 'domain': [('id', 'in', self._get_related_sales().ids)], + "type": "ir.actions.act_window", + "name": "Sales Orders", + "res_model": "sale.order", + "view_type": "form", + "view_mode": "tree,kanban,form,calendar,pivot,graph,activity", + "domain": [("id", "in", self._get_related_sales().ids)], } if tree_view and form_view: - action['views'] = [(tree_view.id, 'tree'), (form_view.id, 'form')] + action["views"] = [(tree_view.id, "tree"), (form_view.id, "form")] return action @api.multi @@ -77,10 +78,10 @@ class ContractContract(models.Model): for sale_rec in sales: self.message_post( body=_( - 'Contract manually sale order: ' + "Contract manually sale order: " '' - 'Sale Order' - '' + "Sale Order" + "" ) % (sale_rec._name, sale_rec.id) ) @@ -107,14 +108,12 @@ class ContractContract(models.Model): continue sale_values = contract._prepare_sale(date_ref) for line in contract_lines: - sale_values.setdefault('order_line', []) + sale_values.setdefault("order_line", []) invoice_line_values = line._prepare_sale_line( sale_values=sale_values, ) if invoice_line_values: - sale_values['order_line'].append( - (0, 0, invoice_line_values) - ) + sale_values["order_line"].append((0, 0, invoice_line_values)) sales_values.append(sale_values) contract_lines._update_recurring_next_date() return sales_values @@ -123,7 +122,7 @@ class ContractContract(models.Model): def _recurring_create_sale(self, date_ref=False): sales_values = self._prepare_recurring_sales_values(date_ref) so_rec = self.env["sale.order"].create(sales_values) - for rec in self.filtered(lambda c: c.sale_autoconfirm): + for _rec in self.filtered(lambda c: c.sale_autoconfirm): so_rec.action_confirm() return so_rec @@ -132,11 +131,10 @@ class ContractContract(models.Model): if not date_ref: date_ref = fields.Date.context_today(self) domain = self._get_contracts_to_invoice_domain(date_ref) - domain.extend([('type', '=', 'sale')]) + domain.extend([("type", "=", "sale")]) sales = self.env["sale.order"] # Sales by companies, so assignation emails get correct context - companies_to_sale = self.read_group( - domain, ["company_id"], ["company_id"]) + companies_to_sale = self.read_group(domain, ["company_id"], ["company_id"]) for row in companies_to_sale: contracts_to_sale = self.search(row["__domain"]).with_context( allowed_company_ids=[row["company_id"][0]] @@ -149,15 +147,13 @@ class ContractContract(models.Model): if not date_ref: date_ref = fields.Date.context_today(self) domain = self._get_contracts_to_invoice_domain(date_ref) - domain.extend([('type', '=', 'invoice')]) + domain.extend([("type", "=", "invoice")]) invoices = self.env["account.invoice"] # Invoice by companies, so assignation emails get correct context - companies_to_invoice = self.read_group( - domain, ["company_id"], ["company_id"]) + companies_to_invoice = self.read_group(domain, ["company_id"], ["company_id"]) for row in companies_to_invoice: contracts_to_invoice = self.search(row["__domain"]).with_context( allowed_company_ids=[row["company_id"][0]] ) - invoices |= contracts_to_invoice._recurring_create_invoice( - date_ref) + invoices |= contracts_to_invoice._recurring_create_invoice(date_ref) return invoices diff --git a/contract_sale_generation/models/contract_line.py b/contract_sale_generation/models/contract_line.py index bdb571fe0..bc9df45af 100644 --- a/contract_sale_generation/models/contract_line.py +++ b/contract_sale_generation/models/contract_line.py @@ -5,7 +5,7 @@ from odoo import api, models class ContractLine(models.Model): - _inherit = 'contract.line' + _inherit = "contract.line" @api.multi def _prepare_sale_line(self, order_id=False, sale_values=False): @@ -14,22 +14,30 @@ class ContractLine(models.Model): self.last_date_invoiced, self.recurring_next_date ) sale_line_vals = { - 'product_id': self.product_id.id, - 'product_uom_qty': self._get_quantity_to_invoice(*dates), - 'uom_id': self.uom_id.id, - 'discount': self.discount, - 'contract_line_id': self.id, - 'display_type': self.display_type, + "product_id": self.product_id.id, + "product_uom_qty": self._get_quantity_to_invoice(*dates), + "uom_id": self.uom_id.id, + "discount": self.discount, + "contract_line_id": self.id, + "display_type": self.display_type, } if order_id: - sale_line_vals['order_id'] = order_id.id - order_line = self.env['sale.order.line'].with_context( - force_company=self.contract_id.company_id.id, - ).new(sale_line_vals) - if sale_values and not order_id: - sale = self.env['sale.order'].with_context( + sale_line_vals["order_id"] = order_id.id + order_line = ( + self.env["sale.order.line"] + .with_context( force_company=self.contract_id.company_id.id, - ).new(sale_values) + ) + .new(sale_line_vals) + ) + if sale_values and not order_id: + sale = ( + self.env["sale.order"] + .with_context( + force_company=self.contract_id.company_id.id, + ) + .new(sale_values) + ) order_line.order_id = sale # Get other order line values from product onchange order_line.product_id_change() @@ -38,10 +46,10 @@ class ContractLine(models.Model): name = self._insert_markers(dates[0], dates[1]) sale_line_vals.update( { - 'sequence': self.sequence, - 'name': name, - 'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)], - 'price_unit': self.price_unit, + "sequence": self.sequence, + "name": name, + "analytic_tag_ids": [(6, 0, self.analytic_tag_ids.ids)], + "price_unit": self.price_unit, } ) return sale_line_vals diff --git a/contract_sale_generation/models/sale_order_line.py b/contract_sale_generation/models/sale_order_line.py index 08abb1332..cb9451ce8 100644 --- a/contract_sale_generation/models/sale_order_line.py +++ b/contract_sale_generation/models/sale_order_line.py @@ -5,8 +5,8 @@ from odoo import fields, models class SaleOrderLine(models.Model): - _inherit = 'sale.order.line' + _inherit = "sale.order.line" contract_line_id = fields.Many2one( - 'contract.line', string='Contract Line', index=True + "contract.line", string="Contract Line", index=True ) diff --git a/contract_sale_generation/readme/DESCRIPTION.rst b/contract_sale_generation/readme/DESCRIPTION.rst index 2ea92d9eb..f8ff4d193 100644 --- a/contract_sale_generation/readme/DESCRIPTION.rst +++ b/contract_sale_generation/readme/DESCRIPTION.rst @@ -1,2 +1,2 @@ -This module extends functionality of contracts to be able to generate sales +This module extends functionality of contracts to be able to generate sales orders instead of invoices. diff --git a/contract_sale_generation/tests/test_contract_sale.py b/contract_sale_generation/tests/test_contract_sale.py index 1f83a4e93..14f8aa5ae 100644 --- a/contract_sale_generation/tests/test_contract_sale.py +++ b/contract_sale_generation/tests/test_contract_sale.py @@ -3,8 +3,8 @@ # Copyright 2017 Angel Moya # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.exceptions import ValidationError from odoo import fields +from odoo.exceptions import ValidationError from odoo.tests.common import TransactionCase @@ -17,95 +17,95 @@ class TestContractSale(TransactionCase): def setUp(self): super(TestContractSale, self).setUp() - self.pricelist = self.env['product.pricelist'].create({ - 'name': 'pricelist for contract test', - }) - self.partner = self.env['res.partner'].create({ - 'name': 'partner test contract', - 'property_product_pricelist': self.pricelist.id, - }) - self.product_1 = self.env.ref('product.product_product_1') - self.product_1.taxes_id += self.env['account.tax'].search( - [('type_tax_use', '=', 'sale')], limit=1 + self.pricelist = self.env["product.pricelist"].create( + { + "name": "pricelist for contract test", + } ) - self.product_1.description_sale = 'Test description sale' + self.partner = self.env["res.partner"].create( + { + "name": "partner test contract", + "property_product_pricelist": self.pricelist.id, + } + ) + self.product_1 = self.env.ref("product.product_product_1") + self.product_1.taxes_id += self.env["account.tax"].search( + [("type_tax_use", "=", "sale")], limit=1 + ) + self.product_1.description_sale = "Test description sale" self.line_template_vals = { - 'product_id': self.product_1.id, - 'name': 'Test Contract Template', - 'quantity': 1, - 'uom_id': self.product_1.uom_id.id, - 'price_unit': 100, - 'discount': 50, - 'recurring_rule_type': 'yearly', - 'recurring_interval': 1, + "product_id": self.product_1.id, + "name": "Test Contract Template", + "quantity": 1, + "uom_id": self.product_1.uom_id.id, + "price_unit": 100, + "discount": 50, + "recurring_rule_type": "yearly", + "recurring_interval": 1, } self.template_vals = { - 'name': 'Test Contract Template', - 'contract_line_ids': [ + "name": "Test Contract Template", + "contract_line_ids": [ (0, 0, self.line_template_vals), ], } - self.template = self.env['contract.template'].create( - self.template_vals - ) + self.template = self.env["contract.template"].create(self.template_vals) # For being sure of the applied price - self.env['product.pricelist.item'].create( + self.env["product.pricelist.item"].create( { - 'pricelist_id': self.partner.property_product_pricelist.id, - 'product_id': self.product_1.id, - 'compute_price': 'formula', - 'base': 'list_price', + "pricelist_id": self.partner.property_product_pricelist.id, + "product_id": self.product_1.id, + "compute_price": "formula", + "base": "list_price", } ) - self.contract = self.env['contract.contract'].create( + self.contract = self.env["contract.contract"].create( { - 'name': 'Test Contract', - 'partner_id': self.partner.id, - 'pricelist_id': self.partner.property_product_pricelist.id, - 'type': 'sale', - 'sale_autoconfirm': False + "name": "Test Contract", + "partner_id": self.partner.id, + "pricelist_id": self.partner.property_product_pricelist.id, + "type": "sale", + "sale_autoconfirm": False, } ) self.line_vals = { - 'contract_id': self.contract.id, - 'product_id': self.product_1.id, - 'name': 'Services from #START# to #END#', - 'quantity': 1, - 'uom_id': self.product_1.uom_id.id, - 'price_unit': 100, - 'discount': 50, - 'recurring_rule_type': 'monthly', - 'recurring_interval': 1, - 'date_start': '2020-01-01', - 'recurring_next_date': '2020-01-15', + "contract_id": self.contract.id, + "product_id": self.product_1.id, + "name": "Services from #START# to #END#", + "quantity": 1, + "uom_id": self.product_1.uom_id.id, + "price_unit": 100, + "discount": 50, + "recurring_rule_type": "monthly", + "recurring_interval": 1, + "date_start": "2020-01-01", + "recurring_next_date": "2020-01-15", } self.contract.contract_template_id = self.template self.contract._onchange_contract_template_id() - self.contract_line = self.env['contract.line'].create( - self.line_vals - ) - self.contract2 = self.env['contract.contract'].create( + self.contract_line = self.env["contract.line"].create(self.line_vals) + self.contract2 = self.env["contract.contract"].create( { - 'name': 'Test Contract 2', - 'type': 'sale', - 'partner_id': self.partner.id, - 'pricelist_id': self.partner.property_product_pricelist.id, - 'contract_type': 'purchase', - 'contract_line_ids': [ + "name": "Test Contract 2", + "type": "sale", + "partner_id": self.partner.id, + "pricelist_id": self.partner.property_product_pricelist.id, + "contract_type": "purchase", + "contract_line_ids": [ ( 0, 0, { - 'product_id': self.product_1.id, - 'name': 'Services from #START# to #END#', - 'quantity': 1, - 'uom_id': self.product_1.uom_id.id, - 'price_unit': 100, - 'discount': 50, - 'recurring_rule_type': 'monthly', - 'recurring_interval': 1, - 'date_start': '2018-02-15', - 'recurring_next_date': '2018-02-22', + "product_id": self.product_1.id, + "name": "Services from #START# to #END#", + "quantity": 1, + "uom_id": self.product_1.uom_id.id, + "price_unit": 100, + "discount": 50, + "recurring_rule_type": "monthly", + "recurring_interval": 1, + "date_start": "2018-02-15", + "recurring_next_date": "2018-02-22", }, ) ], @@ -114,40 +114,36 @@ class TestContractSale(TransactionCase): def test_check_discount(self): with self.assertRaises(ValidationError): - self.contract_line.write({'discount': 120}) + self.contract_line.write({"discount": 120}) def test_contract(self): - recurring_next_date = to_date('2020-02-15') + recurring_next_date = to_date("2020-02-15") self.assertAlmostEqual(self.contract_line.price_subtotal, 50.0) res = self.contract_line._onchange_product_id() - self.assertIn('uom_id', res['domain']) + self.assertIn("uom_id", res["domain"]) self.contract_line.price_unit = 100.0 self.contract.partner_id = self.partner.id self.contract.recurring_create_sale() self.sale_monthly = self.contract._get_related_sales() self.assertTrue(self.sale_monthly) - self.assertEqual( - self.contract_line.recurring_next_date, recurring_next_date - ) + self.assertEqual(self.contract_line.recurring_next_date, recurring_next_date) self.order_line = self.sale_monthly.order_line[0] self.assertTrue(self.order_line.tax_id) self.assertAlmostEqual(self.order_line.price_subtotal, 50.0) self.assertEqual(self.contract.user_id, self.sale_monthly.user_id) def test_contract_autoconfirm(self): - recurring_next_date = to_date('2020-02-15') + recurring_next_date = to_date("2020-02-15") self.contract.sale_autoconfirm = True self.assertAlmostEqual(self.contract_line.price_subtotal, 50.0) res = self.contract_line._onchange_product_id() - self.assertIn('uom_id', res['domain']) + self.assertIn("uom_id", res["domain"]) self.contract_line.price_unit = 100.0 self.contract.partner_id = self.partner.id self.contract.recurring_create_sale() self.sale_monthly = self.contract._get_related_sales() self.assertTrue(self.sale_monthly) - self.assertEqual( - self.contract_line.recurring_next_date, recurring_next_date - ) + self.assertEqual(self.contract_line.recurring_next_date, recurring_next_date) self.order_line = self.sale_monthly.order_line[0] self.assertTrue(self.order_line.tax_id) self.assertAlmostEqual(self.order_line.price_subtotal, 50.0) @@ -160,19 +156,24 @@ class TestContractSale(TransactionCase): self.contract.contract_template_id = self.template self.contract._onchange_contract_template_id() res = { - 'contract_line_ids': - [(0, 0, { - 'product_id': self.product_1.id, - 'name': 'Test Contract Template', - 'quantity': 1, - 'uom_id': self.product_1.uom_id.id, - 'price_unit': 100, - 'discount': 50, - 'recurring_rule_type': 'yearly', - 'recurring_interval': 1, - })] + "contract_line_ids": [ + ( + 0, + 0, + { + "product_id": self.product_1.id, + "name": "Test Contract Template", + "quantity": 1, + "uom_id": self.product_1.uom_id.id, + "price_unit": 100, + "discount": 50, + "recurring_rule_type": "yearly", + "recurring_interval": 1, + }, + ) + ] } - del self.template_vals['name'] + del self.template_vals["name"] self.assertDictEqual(res, self.template_vals) def test_contract_count_sale(self): @@ -183,27 +184,26 @@ class TestContractSale(TransactionCase): self.assertEqual(self.contract.sale_count, 3) def test_contract_count_sale_2(self): - orders = self.env['sale.order'] + orders = self.env["sale.order"] orders |= self.contract.recurring_create_sale() orders |= self.contract.recurring_create_sale() orders |= self.contract.recurring_create_sale() action = self.contract.action_show_sales() - self.assertEqual(set(action['domain'][0][2]), set(orders.ids)) + self.assertEqual(set(action["domain"][0][2]), set(orders.ids)) def test_cron_recurring_create_sale(self): - self.contract_line.date_start = '2020-01-01' - self.contract_line.recurring_invoicing_type = 'post-paid' - self.contract_line.date_end = '2020-03-15' + self.contract_line.date_start = "2020-01-01" + self.contract_line.recurring_invoicing_type = "post-paid" + self.contract_line.date_end = "2020-03-15" self.contract_line._onchange_date_start() contracts = self.contract2 for _i in range(10): - contracts |= self.contract.copy({'type': 'sale'}) - self.env['contract.contract'].cron_recurring_create_sale() - order_lines = self.env['sale.order.line'].search( - [('contract_line_id', 'in', - contracts.mapped('contract_line_ids').ids)] + contracts |= self.contract.copy({"type": "sale"}) + self.env["contract.contract"].cron_recurring_create_sale() + order_lines = self.env["sale.order.line"].search( + [("contract_line_id", "in", contracts.mapped("contract_line_ids").ids)] ) self.assertEqual( - len(contracts.mapped('contract_line_ids')), + len(contracts.mapped("contract_line_ids")), len(order_lines), ) diff --git a/contract_sale_generation/views/contract.xml b/contract_sale_generation/views/contract.xml index 647a8906c..a22babd61 100644 --- a/contract_sale_generation/views/contract.xml +++ b/contract_sale_generation/views/contract.xml @@ -3,38 +3,58 @@ contract.contract.form.recurring.sale.form contract.contract - + - - - + + + - - {'invisible': ['|', ('create_invoice_visibility', '=', False),('type','!=','invoice')]} - - - - + diff --git a/contract_sale_generation/views/contract_template.xml b/contract_sale_generation/views/contract_template.xml index 9a4707c5a..129da21c7 100644 --- a/contract_sale_generation/views/contract_template.xml +++ b/contract_sale_generation/views/contract_template.xml @@ -3,10 +3,10 @@ contract.template form view (in contract) contract.template - + - + diff --git a/setup/contract_sale_generation/odoo/addons/contract_sale_generation b/setup/contract_sale_generation/odoo/addons/contract_sale_generation new file mode 120000 index 000000000..b26b97f4c --- /dev/null +++ b/setup/contract_sale_generation/odoo/addons/contract_sale_generation @@ -0,0 +1 @@ +../../../../contract_sale_generation \ No newline at end of file diff --git a/setup/contract_sale_generation/setup.py b/setup/contract_sale_generation/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/contract_sale_generation/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)