Move 124 sale modules to oca-sale, create oca-project with 56 project modules from oca-workflow-process

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ernad Husremovic 2025-08-30 18:04:10 +02:00
parent 9eb7ae5807
commit 6094c218b2
2332 changed files with 125826 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# Copyright 2022 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import test_project_stock

View file

@ -0,0 +1,88 @@
# Copyright 2022 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests import Form, common
from odoo.tests.common import new_test_user
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
class TestProjectStockBase(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT))
cls.product_a = cls.env["product.product"].create(
{"name": "Test product A", "detailed_type": "product", "standard_price": 10}
)
cls.product_b = cls.env["product.product"].create(
{"name": "Test product B", "detailed_type": "product", "standard_price": 20}
)
cls.product_c = cls.env["product.product"].create(
{"name": "Test product C", "detailed_type": "product", "standard_price": 0}
)
cls.picking_type = cls.env.ref("project_stock.stock_picking_type_tm_test")
cls.location = cls.picking_type.default_location_src_id
cls.location_dest = cls.picking_type.default_location_dest_id
cls.plan = cls.env["account.analytic.plan"].create(
{
"name": "Projects Plan",
"company_id": False,
}
)
cls.analytic_account = cls.env["account.analytic.account"].create(
{
"name": "Test account",
"plan_id": cls.plan.id,
},
)
cls.analytic_account_2 = cls.analytic_account.copy(
{
"name": "Test account 2",
"plan_id": cls.plan.id,
}
)
cls.project = cls.env.ref("project_stock.project_project_tm_test")
cls.project.analytic_account_id = cls.analytic_account
cls.stage_in_progress = cls.env.ref("project.project_stage_1")
cls.stage_done = cls.env.ref("project.project_stage_2")
group_stock_user = "stock.group_stock_user"
cls.basic_user = new_test_user(
cls.env,
login="basic-user",
groups="project.group_project_user,%s" % group_stock_user,
)
new_test_user(
cls.env,
login="manager-user",
groups="project.group_project_manager,%s,analytic.group_analytic_accounting"
% group_stock_user,
)
new_test_user(
cls.env,
login="project-task-user",
groups="project.group_project_user,stock.group_stock_user",
)
def _prepare_context_task(self):
return {
"default_project_id": self.project.id,
"default_stage_id": self.stage_in_progress.id,
# We need to set default values according to compute store fields
"default_location_id": self.project.location_id.id,
"default_location_dest_id": self.project.location_dest_id.id,
"default_picking_type_id": self.project.picking_type_id.id,
}
def _create_task(self, products):
task_form = Form(
self.env["project.task"].with_context(**self._prepare_context_task(self))
)
task_form.name = "Test task"
# Save task to use default_get() correctlly in stock.moves
task_form.save()
for product in products:
with task_form.move_ids.new() as move_form:
move_form.product_id = product[0]
move_form.product_uom_qty = product[1]
return task_form.save()

View file

@ -0,0 +1,356 @@
# 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)