From 50ffc339991b136b48525b2d60aa34a3039bb8da Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Tue, 17 May 2022 08:55:26 +0200 Subject: [PATCH] [IMP] sale_{project,timesheet}: remove 'delivered_milestone' in invoice_policy Before this commit, when the `Milestones` feature is disable, the user can always defined a service product with invoice policy equals to 'Based on Milestones' but since the feature is disabled he cannot add this product on a milestone and so this configuratiom is useless without the feature. This commit removes the item into the invoice_policy field Selection when the feature is disabled. Moreover, when the feature is disabled, the existing products with this setting should be updated to be a service product with `invoice_policy='delivered_manual'`. The quantity delivered method of the existing SOLs containing those products should also be updated to allow the user to manually edit the quantity delivered of those SOLs when the `Milestones` feature is disabled. However, when the `Milestones` feature is enabled again, only the products contained in SOL linked to a milestone will be updated because we cannot assume all service products with `invoice_policy='delivered_manual'` were a service products with invoice policy based on Milestones. Also the existing SOLs containing the products updated will not be updated too to not impact the quantity delivered. task-2829542 --- addons/project/data/project_demo.xml | 5 + addons/sale_project/models/__init__.py | 1 + addons/sale_project/models/product.py | 21 ++- .../models/res_config_settings.py | 27 +++ addons/sale_project/tests/__init__.py | 2 + addons/sale_project/tests/common.py | 136 ++++++++++++++++ .../tests/test_res_config_settings.py | 88 ++++++++++ .../sale_timesheet/data/sale_service_demo.xml | 2 +- addons/sale_timesheet/models/product.py | 11 +- addons/sale_timesheet/tests/common.py | 154 +++--------------- 10 files changed, 302 insertions(+), 145 deletions(-) create mode 100644 addons/sale_project/models/res_config_settings.py create mode 100644 addons/sale_project/tests/common.py create mode 100644 addons/sale_project/tests/test_res_config_settings.py diff --git a/addons/project/data/project_demo.xml b/addons/project/data/project_demo.xml index a8fc6c2c027..c8b388a39b7 100644 --- a/addons/project/data/project_demo.xml +++ b/addons/project/data/project_demo.xml @@ -7,6 +7,11 @@ + + + + + Bug diff --git a/addons/sale_project/models/__init__.py b/addons/sale_project/models/__init__.py index c7c5fa4eb1e..2ca122c0b24 100644 --- a/addons/sale_project/models/__init__.py +++ b/addons/sale_project/models/__init__.py @@ -5,3 +5,4 @@ from . import project from . import project_milestone from . import sale_order from . import sale_order_line +from . import res_config_settings diff --git a/addons/sale_project/models/product.py b/addons/sale_project/models/product.py index 8891c2877c8..a7bd6d63bc5 100644 --- a/addons/sale_project/models/product.py +++ b/addons/sale_project/models/product.py @@ -5,17 +5,20 @@ from odoo import api, fields, models, _ from odoo.exceptions import ValidationError -SERVICE_POLICY = [ - # (service_policy, string) - ('ordered_prepaid', 'Prepaid/Fixed Price'), - ('delivered_milestones', 'Based on Milestones'), - ('delivered_manual', 'Based on Delivered Quantity (Manual)'), -] - - class ProductTemplate(models.Model): _inherit = 'product.template' + @api.model + def _selection_service_policy(self): + service_policies = [ + # (service_policy, string) + ('ordered_prepaid', 'Prepaid/Fixed Price'), + ('delivered_manual', 'Based on Delivered Quantity (Manual)'), + ] + if self.user_has_groups('project.group_project_milestone'): + service_policies.insert(1, ('delivered_milestones', 'Based on Milestones')) + return service_policies + service_tracking = fields.Selection( selection=[ ('no', 'Nothing'), @@ -36,7 +39,7 @@ class ProductTemplate(models.Model): 'project.project', 'Project Template', company_dependent=True, copy=True, domain="[('company_id', '=', current_company_id)]", help='Select a billable project to be the skeleton of the new created project when selling the current product. Its stages and tasks will be duplicated.') - service_policy = fields.Selection(SERVICE_POLICY, string="Service Invoicing Policy", compute='_compute_service_policy', inverse='_inverse_service_policy') + service_policy = fields.Selection('_selection_service_policy', string="Service Invoicing Policy", compute='_compute_service_policy', inverse='_inverse_service_policy') service_type = fields.Selection(selection_add=[ ('milestones', 'Project Milestones'), ]) diff --git a/addons/sale_project/models/res_config_settings.py b/addons/sale_project/models/res_config_settings.py new file mode 100644 index 00000000000..b626c12e17f --- /dev/null +++ b/addons/sale_project/models/res_config_settings.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + def set_values(self): + super().set_values() + if self.group_project_milestone: + # Search the milestones containing a SOL and change the qty_delivered_method field of the SOL and the + # service_policy field set on the product to convert from manual to milestones. + milestone_read_group = self.env['project.milestone'].read_group( + [('sale_line_id', '!=', False)], + ['sale_line_ids:array_agg(sale_line_id)'], + [], + ) + sale_line_ids = milestone_read_group[0]['sale_line_ids'] if milestone_read_group else [] + sale_lines = self.env['sale.order.line'].browse(sale_line_ids) + sale_lines.product_id.service_policy = 'delivered_milestones' + else: + product_domain = [('type', '=', 'service'), ('service_type', '=', 'milestones')] + products = self.env['product.product'].search(product_domain) + products.service_policy = 'delivered_manual' + self.env['sale.order.line'].search([('product_id', 'in', products.ids)]).qty_delivered_method = 'manual' diff --git a/addons/sale_project/tests/__init__.py b/addons/sale_project/tests/__init__.py index 89bae4682d8..5ecbcbf1f6b 100644 --- a/addons/sale_project/tests/__init__.py +++ b/addons/sale_project/tests/__init__.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import common from . import test_child_tasks from . import test_project_profitability +from . import test_res_config_settings from . import test_sale_project from . import test_so_line_milestones diff --git a/addons/sale_project/tests/common.py b/addons/sale_project/tests/common.py new file mode 100644 index 00000000000..741a58a29b1 --- /dev/null +++ b/addons/sale_project/tests/common.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.sale.tests.common import TestSaleCommon + + +class TestSaleProjectCommon(TestSaleCommon): + @classmethod + def setUpClass(cls, chart_template_ref=None): + super().setUpClass(chart_template_ref=chart_template_ref) + + cls.env['res.config.settings'] \ + .create({'group_project_milestone': True}) \ + .execute() + + cls.uom_hour = cls.env.ref('uom.product_uom_hour') + cls.account_sale = cls.company_data['default_account_revenue'] + + cls.analytic_account_sale = cls.env['account.analytic.account'].create({ + 'name': 'Project for selling timesheet - AA', + 'code': 'AA-2030', + 'company_id': cls.company_data['company'].id, + }) + Project = cls.env['project.project'].with_context(tracking_disable=True) + cls.project_global = Project.create({ + 'name': 'Project Global', + 'analytic_account_id': cls.analytic_account_sale.id, + 'allow_billable': True, + }) + cls.project_template = Project.create({ + 'name': 'Project TEMPLATE for services', + }) + cls.project_template_state = cls.env['project.task.type'].create({ + 'name': 'Only stage in project template', + 'sequence': 1, + 'project_ids': [(4, cls.project_template.id)] + }) + + # -- manual (delivered, manual) + cls.product_delivery_manual1 = cls.env['product.product'].create({ + 'name': "Service delivered, create no task", + 'standard_price': 11, + 'list_price': 13, + 'type': 'service', + 'invoice_policy': 'delivery', + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, + 'default_code': 'SERV-DELI1', + 'service_type': 'manual', + 'service_tracking': 'no', + 'project_id': False, + 'taxes_id': False, + 'property_account_income_id': cls.account_sale.id, + }) + cls.product_delivery_manual2 = cls.env['product.product'].create({ + 'name': "Service delivered, create task in global project", + 'standard_price': 30, + 'list_price': 90, + 'type': 'service', + 'invoice_policy': 'delivery', + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, + 'default_code': 'SERV-DELI2', + 'service_type': 'manual', + 'service_tracking': 'task_global_project', + 'project_id': cls.project_global.id, + 'taxes_id': False, + 'property_account_income_id': cls.account_sale.id, + }) + cls.product_delivery_manual3 = cls.env['product.product'].create({ + 'name': "Service delivered, create task in new project", + 'standard_price': 10, + 'list_price': 20, + 'type': 'service', + 'invoice_policy': 'delivery', + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, + 'default_code': 'SERV-DELI3', + 'service_type': 'manual', + 'service_tracking': 'task_in_project', + 'project_id': False, # will create a project + 'taxes_id': False, + 'property_account_income_id': cls.account_sale.id, + }) + cls.product_delivery_manual4 = cls.env['product.product'].create({ + 'name': "Service delivered, create project only", + 'standard_price': 15, + 'list_price': 30, + 'type': 'service', + 'invoice_policy': 'delivery', + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, + 'default_code': 'SERV-DELI4', + 'service_type': 'manual', + 'service_tracking': 'project_only', + 'project_id': False, + 'taxes_id': False, + 'property_account_income_id': cls.account_sale.id, + }) + cls.product_delivery_manual5 = cls.env['product.product'].create({ + 'name': "Service delivered, create project only with template", + 'standard_price': 17, + 'list_price': 34, + 'type': 'service', + 'invoice_policy': 'delivery', + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, + 'default_code': 'SERV-DELI4', + 'service_type': 'manual', + 'service_tracking': 'project_only', + 'project_id': False, + 'project_template_id': cls.project_template.id, + 'taxes_id': False, + 'property_account_income_id': cls.account_sale.id, + }) + + # -- devliered_milestones (delivered, milestones) + product_milestone_vals = { + 'type': 'service', + 'invoice_policy': 'delivery', + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, + 'default_code': 'SERV-MILES', + 'service_type': 'milestones', + 'service_tracking': 'no', + 'property_account_income_id': cls.account_sale.id, + } + cls.product_milestone, cls.product_milestone2 = cls.env['product.product'].create([ + {**product_milestone_vals, 'name': 'Milestone Product', 'list_price': 20}, + {**product_milestone_vals, 'name': 'Milestone Product 2', 'list_price': 15}, + ]) + + def set_project_milestone_feature(self, value): + self.env['res.config.settings'] \ + .create({'group_project_milestone': value}) \ + .execute() diff --git a/addons/sale_project/tests/test_res_config_settings.py b/addons/sale_project/tests/test_res_config_settings.py new file mode 100644 index 00000000000..4ef61cc0d93 --- /dev/null +++ b/addons/sale_project/tests/test_res_config_settings.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import Command +from odoo.tests import tagged + +from .common import TestSaleProjectCommon + + +@tagged('post_install', '-at_install') +class TestResConfigSettings(TestSaleProjectCommon): + @classmethod + def setUpClass(cls, chart_template_ref=None): + super().setUpClass(chart_template_ref=chart_template_ref) + + cls.sale_order = cls.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({ + 'partner_id': cls.partner_b.id, + 'partner_invoice_id': cls.partner_b.id, + 'partner_shipping_id': cls.partner_b.id, + 'order_line': [ + Command.create({ + 'product_id': cls.product_milestone.id, + 'product_uom_qty': 3, + }), + Command.create({ + 'product_id': cls.product_delivery_manual1.id, + 'product_uom_qty': 2, + }) + ] + }) + cls.product_milestone_sale_line = cls.sale_order.order_line.filtered(lambda sol: sol.product_id == cls.product_milestone) + cls.product_delivery_manual1_sale_line = cls.sale_order.order_line.filtered(lambda sol: sol.product_id == cls.product_delivery_manual1) + cls.sale_order.action_confirm() + + cls.milestone = cls.env['project.milestone'].create({ + 'name': 'Test Milestone', + 'sale_line_id': cls.product_milestone_sale_line.id, + 'project_id': cls.project_global.id, + }) + + def test_disable_and_enable_project_milestone_feature(self): + self.assertTrue(self.env.user.has_group('project.group_project_milestone'), 'The Project Milestones feature should be enabled.') + + self.set_project_milestone_feature(False) + self.assertFalse(self.env.user.has_group('project.group_project_milestone'), 'The Project Milestones feature should be disabled.') + product_milestones = self.product_milestone + self.product_milestone2 + self.assertEqual( + product_milestones.mapped('service_policy'), + ['delivered_manual'] * 2, + 'Both milestone products should become a manual product when the project milestones feature is disabled') + self.assertEqual( + product_milestones.mapped('service_type'), + ['manual'] * 2, + 'Both milestone products should become a manual product when the project milestones feature is disabled') + self.assertEqual( + self.product_milestone_sale_line.qty_delivered_method, + 'manual', + 'The quantity delivered method of SOL containing milestone product should be changed to manual when the project milestones feature is disabled') + + # Since the quantity delivered manual is manual then the user can manually change the quantity delivered + self.product_milestone_sale_line.qty_delivered = 2 + + # Enable the project milestones feature + self.set_project_milestone_feature(True) + + self.assertEqual( + self.product_milestone.service_policy, + 'delivered_milestones', + 'The product has been updated and considered as milestones product since a SOL containing this product is linked to a milestone.') + self.assertEqual( + self.product_milestone.service_type, + 'milestones', + 'The product has been updated and considered as milestones product since a SOL containing this product is linked to a milestone.') + self.assertEqual( + self.product_milestone2.service_policy, + 'delivered_manual', + 'The product should not be updated since we cannot assume this product was a milestone when the feature' + ' was enabled because no SOL with this product is linked to a milestone.') + self.assertEqual( + self.product_milestone2.service_type, + 'manual', + 'The product should not be updated since we cannot assume this product was a milestone when the feature' + ' was enabled because no SOL with this product is linked to a milestone.') + self.assertEqual( + self.product_milestone_sale_line.qty_delivered_method, + 'manual', + 'The quantity delivered method of SOL containing milestone product should keep the same quantity delivered method even if the project milestones feature is renabled.') + self.assertEqual(self.product_milestone_sale_line.qty_delivered, 2, 'The quantity delivered should be the one set by the user.') diff --git a/addons/sale_timesheet/data/sale_service_demo.xml b/addons/sale_timesheet/data/sale_service_demo.xml index 2644398e9cc..0c73ab9dd57 100644 --- a/addons/sale_timesheet/data/sale_service_demo.xml +++ b/addons/sale_timesheet/data/sale_service_demo.xml @@ -111,7 +111,7 @@ service - delivered_milestones + milestones no diff --git a/addons/sale_timesheet/models/product.py b/addons/sale_timesheet/models/product.py index 3f9d5fc8c91..e8c24be337b 100644 --- a/addons/sale_timesheet/models/product.py +++ b/addons/sale_timesheet/models/product.py @@ -6,16 +6,15 @@ import threading from odoo import api, fields, models, _ from odoo.exceptions import ValidationError -from odoo.addons.sale_project.models.product import SERVICE_POLICY - -SERVICE_POLICY = [policy[:1] for policy in SERVICE_POLICY] -SERVICE_POLICY.insert(1, ('delivered_timesheet', 'Based on Timesheets')) - class ProductTemplate(models.Model): _inherit = 'product.template' - service_policy = fields.Selection(selection_add=SERVICE_POLICY) + def _selection_service_policy(self): + service_policies = super()._selection_service_policy() + service_policies.insert(1, ('delivered_timesheet', 'Based on Timesheets')) + return service_policies + service_type = fields.Selection(selection_add=[ ('timesheet', 'Timesheets on project (one fare per SO/Project)'), ], ondelete={'timesheet': 'set manual'}) diff --git a/addons/sale_timesheet/tests/common.py b/addons/sale_timesheet/tests/common.py index 544518e899f..d8c1971d8fc 100644 --- a/addons/sale_timesheet/tests/common.py +++ b/addons/sale_timesheet/tests/common.py @@ -2,10 +2,10 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.addons.mail.tests.common import mail_new_test_user -from odoo.addons.sale.tests.common import TestSaleCommon +from odoo.addons.sale_project.tests.common import TestSaleProjectCommon -class TestCommonSaleTimesheet(TestSaleCommon): +class TestCommonSaleTimesheet(TestSaleProjectCommon): @classmethod def setUpClass(cls, chart_template_ref=None): @@ -54,12 +54,7 @@ class TestCommonSaleTimesheet(TestSaleCommon): }) # Account and project - cls.account_sale = cls.company_data['default_account_revenue'] - cls.analytic_account_sale = cls.env['account.analytic.account'].create({ - 'name': 'Project for selling timesheet - AA', - 'code': 'AA-2030', - 'company_id': cls.company_data['company'].id, - }) + cls.analytic_account_sale.name = 'Project for selling timesheet - AA' cls.analytic_account_sale_company_B = cls.env['account.analytic.account'].create({ 'name': 'Project for selling timesheet Company B - AA', 'code': 'AA-2030', @@ -68,20 +63,12 @@ class TestCommonSaleTimesheet(TestSaleCommon): # Create projects Project = cls.env['project.project'].with_context(tracking_disable=True) - cls.project_global = Project.create({ + cls.project_global.write({ 'name': 'Project for selling timesheets', 'allow_timesheets': True, - 'analytic_account_id': cls.analytic_account_sale.id, - 'allow_billable': True, }) - cls.project_template = Project.create({ + cls.project_template.write({ 'name': 'Project TEMPLATE for services', - 'allow_timesheets': True, - }) - cls.project_template_state = cls.env['project.task.type'].create({ - 'name': 'Only stage in project template', - 'sequence': 1, - 'project_ids': [(4, cls.project_template.id)] }) # Projects: at least one per billable type cls.project_task_rate = Project.create({ @@ -106,7 +93,6 @@ class TestCommonSaleTimesheet(TestSaleCommon): }) # Create service products - uom_hour = cls.env.ref('uom.product_uom_hour') # -- ordered quantities (ordered, timesheet) cls.product_order_timesheet1 = cls.env['product.product'].create({ @@ -115,8 +101,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 13, 'type': 'service', 'invoice_policy': 'order', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-ORDERED1', 'service_type': 'timesheet', 'service_tracking': 'no', @@ -130,8 +116,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 90, 'type': 'service', 'invoice_policy': 'order', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-ORDERED2', 'service_type': 'timesheet', 'service_tracking': 'task_global_project', @@ -145,8 +131,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 20, 'type': 'service', 'invoice_policy': 'order', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-ORDERED3', 'service_type': 'timesheet', 'service_tracking': 'task_in_project', @@ -160,8 +146,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 30, 'type': 'service', 'invoice_policy': 'order', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-ORDERED4', 'service_type': 'timesheet', 'service_tracking': 'project_only', @@ -175,8 +161,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 34, 'type': 'service', 'invoice_policy': 'order', - 'uom_id': cls.env.ref('uom.product_uom_hour').id, - 'uom_po_id': cls.env.ref('uom.product_uom_hour').id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-ORDERED4', 'service_type': 'timesheet', 'service_tracking': 'project_only', @@ -193,8 +179,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 13, 'type': 'service', 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-DELI1', 'service_type': 'timesheet', 'service_tracking': 'no', @@ -208,8 +194,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 90, 'type': 'service', 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-DELI2', 'service_type': 'timesheet', 'service_tracking': 'task_global_project', @@ -223,8 +209,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 20, 'type': 'service', 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-DELI3', 'service_type': 'timesheet', 'service_tracking': 'task_in_project', @@ -238,8 +224,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 30, 'type': 'service', 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-DELI4', 'service_type': 'timesheet', 'service_tracking': 'project_only', @@ -253,8 +239,8 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'list_price': 34, 'type': 'service', 'invoice_policy': 'delivery', - 'uom_id': cls.env.ref('uom.product_uom_hour').id, - 'uom_po_id': cls.env.ref('uom.product_uom_hour').id, + 'uom_id': cls.uom_hour.id, + 'uom_po_id': cls.uom_hour.id, 'default_code': 'SERV-DELI5', 'service_type': 'timesheet', 'service_tracking': 'project_only', @@ -264,96 +250,6 @@ class TestCommonSaleTimesheet(TestSaleCommon): 'property_account_income_id': cls.account_sale.id, }) - # -- manual (delivered, manual) - cls.product_delivery_manual1 = cls.env['product.product'].create({ - 'name': "Service delivered, create no task", - 'standard_price': 11, - 'list_price': 13, - 'type': 'service', - 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, - 'default_code': 'SERV-DELI1', - 'service_type': 'manual', - 'service_tracking': 'no', - 'project_id': False, - 'taxes_id': False, - 'property_account_income_id': cls.account_sale.id, - }) - cls.product_delivery_manual2 = cls.env['product.product'].create({ - 'name': "Service delivered, create task in global project", - 'standard_price': 30, - 'list_price': 90, - 'type': 'service', - 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, - 'default_code': 'SERV-DELI2', - 'service_type': 'manual', - 'service_tracking': 'task_global_project', - 'project_id': cls.project_global.id, - 'taxes_id': False, - 'property_account_income_id': cls.account_sale.id, - }) - cls.product_delivery_manual3 = cls.env['product.product'].create({ - 'name': "Service delivered, create task in new project", - 'standard_price': 10, - 'list_price': 20, - 'type': 'service', - 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, - 'default_code': 'SERV-DELI3', - 'service_type': 'manual', - 'service_tracking': 'task_in_project', - 'project_id': False, # will create a project - 'taxes_id': False, - 'property_account_income_id': cls.account_sale.id, - }) - cls.product_delivery_manual4 = cls.env['product.product'].create({ - 'name': "Service delivered, create project only", - 'standard_price': 15, - 'list_price': 30, - 'type': 'service', - 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, - 'default_code': 'SERV-DELI4', - 'service_type': 'manual', - 'service_tracking': 'project_only', - 'project_id': False, - 'taxes_id': False, - 'property_account_income_id': cls.account_sale.id, - }) - cls.product_delivery_manual5 = cls.env['product.product'].create({ - 'name': "Service delivered, create project only with template", - 'standard_price': 17, - 'list_price': 34, - 'type': 'service', - 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, - 'default_code': 'SERV-DELI4', - 'service_type': 'manual', - 'service_tracking': 'project_only', - 'project_id': False, - 'project_template_id': cls.project_template.id, - 'taxes_id': False, - 'property_account_income_id': cls.account_sale.id, - }) - cls.product_milestone = cls.env['product.product'].create({ - 'name': 'Milestone Product', - 'list_price': 20, - 'type': 'service', - 'invoice_policy': 'delivery', - 'uom_id': uom_hour.id, - 'uom_po_id': uom_hour.id, - 'default_code': 'SERV-MILES', - 'service_type': 'milestones', - 'service_tracking': 'no', - 'property_account_income_id': cls.account_sale.id, - }) - def setUp(self): super().setUp() self.so = self.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({