# Copyright 2022-2025 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields from odoo.tests import Form from odoo.tests.common import users from odoo.tools import mute_logger from .common import TestProjectStockBase class TestProjectStock(TestProjectStockBase): @classmethod def setUpClass(cls): super().setUpClass() cls._create_stock_quant(cls, cls.product_a, cls.location, 2) cls._create_stock_quant(cls, cls.product_b, cls.location, 1) cls._create_stock_quant(cls, cls.product_c, cls.location, 1) cls.task = cls._create_task(cls, [(cls.product_a, 2), (cls.product_b, 1)]) cls.move_product_a = cls.task.move_ids.filtered( lambda x: x.product_id == cls.product_a ) cls.move_product_b = cls.task.move_ids.filtered( lambda x: x.product_id == cls.product_b ) cls.env.ref("base.user_admin").write( { "groups_id": [ (4, cls.env.ref("analytic.group_analytic_accounting").id), ], } ) def _create_stock_quant(self, product, location, qty): self.env["stock.quant"].create( {"product_id": product.id, "location_id": location.id, "quantity": qty} ) def test_project_task_misc(self): self.assertTrue(self.task.group_id) self.assertEqual(self.task.picking_type_id, self.picking_type) self.assertEqual(self.task.location_id, self.location) self.assertEqual(self.task.location_dest_id, self.location_dest) self.assertEqual(self.move_product_a.name, self.task.name) self.assertEqual(self.move_product_a.group_id, self.task.group_id) self.assertEqual(self.move_product_a.reference, self.task.name) self.assertEqual(self.move_product_a.location_id, self.location) self.assertEqual(self.move_product_a.location_dest_id, self.location_dest) self.assertEqual(self.move_product_a.picking_type_id, self.picking_type) self.assertEqual(self.move_product_a.raw_material_task_id, self.task) self.assertEqual(self.move_product_b.group_id, self.task.group_id) self.assertEqual(self.move_product_b.location_id, self.location) self.assertEqual(self.move_product_b.location_dest_id, self.location_dest) self.assertEqual(self.move_product_b.picking_type_id, self.picking_type) self.assertEqual(self.move_product_b.raw_material_task_id, self.task) def _test_task_analytic_lines_from_task(self, amount): self.task = self.env["project.task"].browse(self.task.id) # Prevent error when hr_timesheet addon is installed. stock_analytic_lines = self.task.sudo().stock_analytic_line_ids self.assertEqual(len(stock_analytic_lines), 2) self.assertEqual(sum(stock_analytic_lines.mapped("unit_amount")), 3) self.assertEqual(sum(stock_analytic_lines.mapped("amount")), amount) self.assertIn( self.analytic_account, stock_analytic_lines.mapped("account_id"), ) # Prevent incoherence when hr_timesheet addon is installed. if "project_id" in self.task.stock_analytic_line_ids._fields: self.assertFalse(self.task.stock_analytic_line_ids.project_id) def test_project_task_without_analytic_account(self): self.task = self.env["project.task"].browse(self.task.id) # Prevent error when hr_timesheet addon is installed. if "allow_timesheets" in self.task.project_id._fields: self.task.project_id.allow_timesheets = False self.task.project_id.analytic_account_id = False self.task.write({"stage_id": self.stage_done.id}) self.task.action_done() self.assertFalse(self.task.stock_analytic_line_ids) def test_project_task_picking_done_analytic_items(self): self.task = self.env["project.task"].browse(self.task.id) self.task.action_assign() picking = self.task.move_ids.picking_id for move in picking.move_ids: move.quantity_done = move.product_uom_qty picking.button_validate() self.assertEqual(picking.state, "done") self._test_task_analytic_lines_from_task(-40) self.assertEqual( fields.first(self.task.stock_analytic_line_ids).date, fields.Date.from_string("1990-01-01"), ) @users("manager-user") def test_project_task_without_analytic_account_manager_user(self): self.test_project_task_without_analytic_account() def test_project_task_user_access_without_stock_group(self): self.basic_user.write( { "groups_id": [(6, 0, [self.env.ref("project.group_project_user").id])], } ) task_form = Form(self.task.with_user(self.basic_user)) self.assertEqual(task_form.project_id, self.project) def test_project_task_analytic_lines_without_tags(self): self.task = self.env["project.task"].browse(self.task.id) self.task.write({"stage_id": self.stage_done.id}) self.task.action_done() self._test_task_analytic_lines_from_task(-40) self.assertEqual( fields.first(self.task.stock_analytic_line_ids).date, fields.Date.from_string("1990-01-01"), ) @users("manager-user") def test_project_task_analytic_lines_without_tags_manager_user(self): self.test_project_task_analytic_lines_without_tags() def test_project_task_analytic_lines_with_tag_1(self): self.task = self.env["project.task"].browse(self.task.id) self.task.write( { "stock_analytic_date": "1991-01-01", } ) self.task.write({"stage_id": self.stage_done.id}) self.task.action_done() self._test_task_analytic_lines_from_task(-40) self.assertEqual( fields.first(self.task.stock_analytic_line_ids).date, fields.Date.from_string("1991-01-01"), ) @users("manager-user") def test_project_task_analytic_lines_with_tag_1_manager_user(self): self.task.stock_analytic_distribution = {self.analytic_account.id: 100} self.test_project_task_analytic_lines_with_tag_1() def test_project_task_analytic_lines_with_tag_2(self): self.task = self.env["project.task"].browse(self.task.id) self.task.project_id.stock_analytic_date = False self.task.write({"stage_id": self.stage_done.id}) self.task.action_done() self._test_task_analytic_lines_from_task(-40) self.assertEqual( fields.first(self.task.stock_analytic_line_ids).date, fields.date.today() ) @users("manager-user") def test_project_task_analytic_lines_with_tag_2_manager_user(self): self.task.stock_analytic_distribution = { self.analytic_account.id: 50, self.analytic_account_2.id: 50, } self.test_project_task_analytic_lines_with_tag_2() def test_project_task_process_done(self): self.task = self.env["project.task"].browse(self.task.id) self.assertEqual(self.move_product_a.state, "draft") self.assertEqual(self.move_product_b.state, "draft") # Change task stage (auto-confirm + auto-assign) self.task.write({"stage_id": self.stage_done.id}) self.assertEqual(self.move_product_a.state, "assigned") self.assertEqual(self.move_product_b.state, "assigned") self.assertEqual(self.move_product_a.reserved_availability, 2) self.assertEqual(self.move_product_b.reserved_availability, 1) self.assertTrue(self.task.stock_moves_is_locked) self.task.action_toggle_stock_moves_is_locked() self.assertFalse(self.task.stock_moves_is_locked) # Add new stock_move self.task.write({"stage_id": self.stage_in_progress.id}) task_form = Form(self.task) with task_form.move_ids.new() as move_form: move_form.product_id = self.product_c move_form.product_uom_qty = 1 task_form.save() move_product_c = self.task.move_ids.filtered( lambda x: x.product_id == self.product_c ) self.assertEqual(move_product_c.group_id, self.task.group_id) self.assertEqual(move_product_c.state, "draft") self.task.action_assign() self.assertEqual(move_product_c.state, "assigned") self.task.write({"stage_id": self.stage_done.id}) # action_done self.task.action_done() self.assertEqual(self.move_product_a.state, "done") self.assertEqual(self.move_product_b.state, "done") self.assertEqual(self.move_product_a.quantity_done, 2) self.assertEqual(self.move_product_b.quantity_done, 1) self.assertEqual(move_product_c.quantity_done, 1) @users("basic-user") def test_project_task_process_done_basic_user(self): self.test_project_task_process_done() @mute_logger("odoo.models.unlink") def test_project_task_process_cancel(self): self.task = self.env["project.task"].browse(self.task.id) self.assertEqual(self.move_product_a.state, "draft") self.assertEqual(self.move_product_b.state, "draft") # Change task stage self.task.write({"stage_id": self.stage_done.id}) self.assertEqual(self.move_product_a.state, "assigned") self.assertEqual(self.move_product_b.state, "assigned") # action_done self.task.action_done() self.assertEqual(self.move_product_a.state, "done") self.assertEqual(self.move_product_b.state, "done") self.assertEqual(self.move_product_a.quantity_done, 2) self.assertEqual(self.move_product_b.quantity_done, 1) self.assertTrue(self.task.sudo().stock_analytic_line_ids) # action_cancel self.task.action_cancel() self.assertEqual(self.move_product_a.state, "done") self.assertEqual(self.move_product_b.state, "done") self.assertEqual(self.move_product_a.quantity_done, 0) self.assertEqual(self.move_product_b.quantity_done, 0) self.assertFalse(self.task.stock_analytic_line_ids) quant_a = self.product_a.stock_quant_ids.filtered( lambda x: x.location_id == self.location ) quant_b = self.product_b.stock_quant_ids.filtered( lambda x: x.location_id == self.location ) quant_c = self.product_c.stock_quant_ids.filtered( lambda x: x.location_id == self.location ) self.assertEqual(quant_a.quantity, 2) self.assertEqual(quant_b.quantity, 1) self.assertEqual(quant_c.quantity, 1) @users("manager-user") def test_project_task_process_cancel_manager_user(self): self.test_project_task_process_cancel() @mute_logger("odoo.models.unlink") def test_project_task_process_unreserve(self): self.task = self.env["project.task"].browse(self.task.id) self.assertEqual(self.move_product_a.state, "draft") self.assertEqual(self.move_product_b.state, "draft") # Change task stage (auto-confirm + auto-assign) self.task.write({"stage_id": self.stage_done.id}) self.assertTrue(self.move_product_a.move_line_ids) self.assertEqual(self.move_product_a.move_line_ids.task_id, self.task) self.assertEqual(self.move_product_a.state, "assigned") self.assertEqual(self.move_product_b.state, "assigned") self.assertEqual(self.move_product_a.reserved_availability, 2) self.assertEqual(self.move_product_b.reserved_availability, 1) self.assertTrue(self.task.unreserve_visible) # button_unreserve self.task.button_unreserve() self.assertEqual(self.move_product_a.state, "confirmed") self.assertEqual(self.move_product_b.state, "confirmed") self.assertEqual(self.move_product_a.reserved_availability, 0) self.assertEqual(self.move_product_b.reserved_availability, 0) self.assertFalse(self.task.unreserve_visible) @mute_logger("odoo.models.unlink") def test_project_task_process_01(self): """Product A move cancel + Product B move OK.""" self.task = self.env["project.task"].browse(self.task.id) self.move_product_b.unlink() self.assertEqual(self.move_product_a.state, "draft") # Confirm + Edit to qty=0 self.task.action_confirm() self.assertEqual(self.move_product_a.state, "assigned") self.move_product_a.product_uom_qty = 0 self.task.action_done() self.assertEqual(self.move_product_a.state, "cancel") # Add extra line task_form = Form(self.task) with task_form.move_ids.new() as move_form: move_form.product_id = self.product_b move_form.product_uom_qty = 1 task_form.save() self.move_product_b = self.task.move_ids.filtered( lambda x: x.product_id == self.product_b ) self.task.action_confirm() self.assertEqual(self.move_product_b.state, "assigned") self.task.action_done() self.assertEqual(self.move_product_b.state, "done") def test_project_task_process_02(self): self.task.action_confirm() self.assertEqual(self.move_product_a.state, "assigned") self.assertEqual(self.move_product_b.state, "assigned") self.task.action_done() self.assertEqual(self.move_product_a.state, "done") self.assertEqual(self.move_product_b.state, "done") self.assertEqual(len(self.task.stock_analytic_line_ids), 2) self.task.action_done() self.assertEqual(len(self.task.stock_analytic_line_ids), 2) @users("basic-user") def test_project_task_process_unreserve_basic_user(self): self.test_project_task_process_unreserve() def test_project_task_action_cancel(self): self.assertTrue(self.env["project.task"].browse(self.task.id).action_cancel()) @users("basic-user") def test_project_task_action_cancel_basic_user(self): self.test_project_task_action_cancel() def test_project_task_action_done(self): self.task = self.env["project.task"].browse(self.task.id) self.task.write({"stage_id": self.stage_done.id}) self.task.action_done() self.assertTrue(self.task.sudo().stock_analytic_line_ids) @users("basic-user") def test_project_task_action_done_basic_user(self): self.test_project_task_action_done() @mute_logger("odoo.models.unlink") def test_project_task_unlink(self): self.assertTrue(self.env["project.task"].browse(self.task.id).unlink()) @users("basic-user") def test_project_task_unlink_basic_user(self): self.test_project_task_unlink() @mute_logger("odoo.models.unlink") def test_project_project_onchange(self): new_type = self.env.ref("stock.picking_type_out") self.project.write({"picking_type_id": new_type.id}) self.project._onchange_picking_type_id() self.assertEqual(self.project.location_id, new_type.default_location_src_id) self.assertEqual( self.project.location_dest_id, new_type.default_location_dest_id ) self.task.do_unreserve() self.task.write({"picking_type_id": new_type.id}) self.task._onchange_picking_type_id() self.assertEqual(self.task.location_id, new_type.default_location_src_id) self.assertEqual(self.task.location_dest_id, new_type.default_location_dest_id) move = fields.first(self.task.move_ids) self.assertEqual(move.location_id, new_type.default_location_src_id) def test_project_task_scrap(self): move = fields.first(self.task.move_ids) scrap = self.env["stock.scrap"].create( { "product_id": move.product_id.id, "product_uom_id": move.product_id.uom_id.id, "scrap_qty": 1, "task_id": self.task.id, } ) scrap.do_scrap() self.assertEqual(scrap.move_id.raw_material_task_id, self.task)