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({