test_so_line_milestones.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from odoo.addons.sale.tests.common import TestSaleCommon
  4. from odoo.exceptions import ValidationError
  5. from odoo.tests.common import tagged
  6. from psycopg2.errors import NotNullViolation
  7. @tagged('post_install', '-at_install')
  8. class TestSoLineMilestones(TestSaleCommon):
  9. @classmethod
  10. def setUpClass(cls, chart_template_ref=None):
  11. super().setUpClass(chart_template_ref=chart_template_ref)
  12. cls.env['res.config.settings'].create({'group_project_milestone': True}).execute()
  13. uom_hour = cls.env.ref('uom.product_uom_hour')
  14. cls.product_delivery_milestones1 = cls.env['product.product'].create({
  15. 'name': "Milestones 1, create project only",
  16. 'standard_price': 15,
  17. 'list_price': 30,
  18. 'type': 'service',
  19. 'invoice_policy': 'delivery',
  20. 'uom_id': uom_hour.id,
  21. 'uom_po_id': uom_hour.id,
  22. 'default_code': 'MILE-DELI4',
  23. 'service_type': 'milestones',
  24. 'service_tracking': 'project_only',
  25. })
  26. cls.product_delivery_milestones2 = cls.env['product.product'].create({
  27. 'name': "Milestones 2, create project only",
  28. 'standard_price':20,
  29. 'list_price': 35,
  30. 'type': 'service',
  31. 'invoice_policy': 'delivery',
  32. 'uom_id': uom_hour.id,
  33. 'uom_po_id': uom_hour.id,
  34. 'default_code': 'MILE-DELI4',
  35. 'service_type': 'milestones',
  36. 'service_tracking': 'project_only',
  37. })
  38. cls.product_delivery_milestones3 = cls.env['product.product'].create({
  39. 'name': "Milestones 3, create project & task",
  40. 'standard_price': 20,
  41. 'list_price': 35,
  42. 'type': 'service',
  43. 'invoice_policy': 'delivery',
  44. 'uom_id': uom_hour.id,
  45. 'uom_po_id': uom_hour.id,
  46. 'default_code': 'MILE-DELI4',
  47. 'service_type': 'milestones',
  48. 'service_tracking': 'task_in_project',
  49. })
  50. cls.sale_order = cls.env['sale.order'].create({
  51. 'partner_id': cls.partner_a.id,
  52. 'partner_invoice_id': cls.partner_a.id,
  53. 'partner_shipping_id': cls.partner_a.id,
  54. })
  55. cls.sol1 = cls.env['sale.order.line'].create({
  56. 'product_id': cls.product_delivery_milestones1.id,
  57. 'product_uom_qty': 20,
  58. 'order_id': cls.sale_order.id,
  59. })
  60. cls.sol2 = cls.env['sale.order.line'].create({
  61. 'product_id': cls.product_delivery_milestones2.id,
  62. 'product_uom_qty': 30,
  63. 'order_id': cls.sale_order.id,
  64. })
  65. cls.sale_order.action_confirm()
  66. cls.project = cls.sol1.project_id
  67. cls.milestone1 = cls.env['project.milestone'].create({
  68. 'name': 'Milestone 1',
  69. 'project_id': cls.project.id,
  70. 'is_reached': False,
  71. 'sale_line_id': cls.sol1.id,
  72. 'quantity_percentage': 0.5,
  73. })
  74. def test_reached_milestones_delivered_quantity(self):
  75. self.milestone2 = self.env['project.milestone'].create({
  76. 'name': 'Milestone 2',
  77. 'project_id': self.project.id,
  78. 'is_reached': False,
  79. 'sale_line_id': self.sol2.id,
  80. 'quantity_percentage': 0.2,
  81. })
  82. self.milestone3 = self.env['project.milestone'].create({
  83. 'name': 'Milestone 3',
  84. 'project_id': self.project.id,
  85. 'is_reached': False,
  86. 'sale_line_id': self.sol2.id,
  87. 'quantity_percentage': 0.4,
  88. })
  89. self.assertEqual(self.sol1.qty_delivered, 0.0, "Delivered quantity should start at 0")
  90. self.assertEqual(self.sol2.qty_delivered, 0.0, "Delivered quantity should start at 0")
  91. self.milestone1.is_reached = True
  92. self.assertEqual(self.sol1.qty_delivered, 10.0, "Delivered quantity should update after a milestone is reached")
  93. self.milestone2.is_reached = True
  94. self.assertEqual(self.sol2.qty_delivered, 6.0, "Delivered quantity should update after a milestone is reached")
  95. self.milestone3.is_reached = True
  96. self.assertEqual(self.sol2.qty_delivered, 18.0, "Delivered quantity should update after a milestone is reached")
  97. def test_update_reached_milestone_quantity(self):
  98. self.milestone1.is_reached = True
  99. self.assertEqual(self.sol1.qty_delivered, 10.0, "Delivered quantity should start at 10")
  100. self.milestone1.quantity_percentage = 0.75
  101. self.assertEqual(self.sol1.qty_delivered, 15.0, "Delivered quantity should update after a milestone's quantity is updated")
  102. def test_remove_reached_milestone(self):
  103. self.milestone1.is_reached = True
  104. self.assertEqual(self.sol1.qty_delivered, 10.0, "Delivered quantity should start at 10")
  105. self.milestone1.unlink()
  106. self.assertEqual(self.sol1.qty_delivered, 0.0, "Delivered quantity should update when a milestone is removed")
  107. def test_compute_sale_line_in_task(self):
  108. task = self.env['project.task'].create({
  109. 'name': 'Test Task',
  110. 'project_id': self.project.id,
  111. })
  112. self.assertEqual(task.sale_line_id, self.sol1, 'The task should have the one of the project linked')
  113. self.project.sale_line_id = False
  114. task.sale_line_id = False
  115. self.assertFalse(task.sale_line_id)
  116. task.write({'milestone_id': self.milestone1.id})
  117. self.assertEqual(task.sale_line_id, self.milestone1.sale_line_id, 'The task should have the SOL from the milestone.')
  118. self.project.sale_line_id = self.sol2
  119. self.assertEqual(task.sale_line_id, self.sol1, 'The task should keep the SOL linked to the milestone.')
  120. def test_create_milestone_on_project_set_on_sales_order(self):
  121. """
  122. Regression Test:
  123. If we confirm an SO with a service with a delivery based on milestones,
  124. that creates both a project & task, and we set a project on the SO,
  125. the project for the milestone should be the one set on the SO,
  126. and no ValidationError or NotNullViolation should be raised.
  127. """
  128. project = self.env['project.project'].create({
  129. 'name': 'Test Project For Milestones',
  130. 'partner_id': self.partner_a.id
  131. })
  132. sale_order = self.env['sale.order'].create({
  133. 'partner_id': self.partner_a.id,
  134. 'partner_invoice_id': self.partner_a.id,
  135. 'partner_shipping_id': self.partner_a.id,
  136. 'project_id': project.id, # the user set a project on the SO
  137. })
  138. self.env['sale.order.line'].create({
  139. 'product_id': self.product_delivery_milestones3.id,
  140. 'product_uom_qty': 20,
  141. 'order_id': sale_order.id,
  142. })
  143. try:
  144. sale_order.action_confirm()
  145. except (ValidationError, NotNullViolation):
  146. self.fail("The sale order should be confirmed, "
  147. "and no ValidationError or NotNullViolation should be raised, "
  148. "for a missing project on the milestone.")