Merge PR #1083 into 13.0

Signed-off-by LoisRForgeFlow
This commit is contained in:
OCA-git-bot
2023-10-23 08:39:32 +00:00
16 changed files with 394 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from . import models
from . import wizards

View File

@@ -0,0 +1,19 @@
# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
{
"name": "MRP Work Order Update Component",
"summary": "Allows to modify component lines in work orders.",
"version": "13.0.1.0.0",
"category": "Manufacturing",
"author": "ForgeFlow, Odoo Community Association (OCA)",
"maintainers": ["DavidJForgeFlow"],
"website": "https://github.com/OCA/manufacture",
"license": "LGPL-3",
"depends": ["mrp"],
"data": [
"views/mrp_workorder_view.xml",
"wizards/mrp_workorder_new_line_view.xml",
],
"installable": True,
}

View File

@@ -0,0 +1 @@
from . import mrp_workorder

View File

@@ -0,0 +1,27 @@
# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, models
class MrpWorkOrderLine(models.Model):
_inherit = "mrp.workorder.line"
def action_new_line_wizard(self):
return {
"name": _("Create new Component Line"),
"view_mode": "form",
"res_model": "mrp.workorder.new.line",
"view_id": self.env.ref(
"mrp_workorder_update_component.view_mrp_workorder_new_line_form"
).id,
"type": "ir.actions.act_window",
"context": {
"default_workorder_id": self.raw_workorder_id.id,
"default_workorder_line_id": self.id,
"default_product_id": self.product_id.id,
"default_original_line_qty": self.qty_to_consume,
"default_company_id": self.company_id.id,
},
"target": "new",
}

View File

@@ -0,0 +1 @@
* David Jiménez <david.jimenez@forgeflow.com>

View File

@@ -0,0 +1 @@
Allows to update the components in the work orders.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1 @@
from . import test_mrp_workorder

View File

@@ -0,0 +1,171 @@
# Copyright 2023 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import odoo.tests.common as common
from odoo.exceptions import ValidationError
from odoo.tests import tagged
from odoo.tests.common import Form
@tagged("-at_install", "post_install")
class TestMrpWorkorderUpdateComponent(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestMrpWorkorderUpdateComponent, cls).setUpClass()
cls.categ_obj = cls.env["product.category"]
cls.product_obj = cls.env["product.product"]
cls.bom_obj = cls.env["mrp.bom"]
cls.bom_line_obj = cls.env["mrp.bom.line"]
cls.production_obj = cls.env["mrp.production"]
cls.produce_wiz = cls.env["mrp.product.produce"]
cls.wc_obj = cls.env["mrp.workcenter"]
cls.routing_obj = cls.env["mrp.routing"]
cls.sm_obj = cls.env["stock.move"]
cls.picking_obj = cls.env["stock.picking"]
cls.lot_obj = cls.env["stock.production.lot"]
cls.partner_obj = cls.env["res.partner"]
cls.company = cls.env.ref("base.main_company")
cls.src_location = cls.env.ref("stock.stock_location_stock")
manufacture_route = cls.env.ref("mrp.route_warehouse0_manufacture")
mto_route = cls.env.ref("stock.route_warehouse0_mto")
# Create Partner:
cls.partner = cls.partner_obj.create(
{"name": "Test Customer", "email": "example@custemer-test.com"}
)
# Create products and lots:
cls.product_1 = cls.product_obj.create(
{
"name": "TEST 01",
"type": "product",
"tracking": "lot",
"route_ids": [(6, 0, [mto_route.id, manufacture_route.id])],
}
)
cls.product_1_lot = cls.lot_obj.create(
{
"name": "LotABC",
"product_id": cls.product_1.id,
"company_id": cls.company.id,
}
)
cls.component_1 = cls.product_obj.create(
{
"name": "RM 01",
"type": "product",
"standard_price": 100.0,
"tracking": "lot",
}
)
cls.component_1_lot = cls.lot_obj.create(
{
"name": "Lot1",
"product_id": cls.component_1.id,
"company_id": cls.company.id,
}
)
cls.component_1_lot_2 = cls.lot_obj.create(
{
"name": "Lot2",
"product_id": cls.component_1.id,
"company_id": cls.company.id,
}
)
# Create Bills of Materials:
test_wc = cls.wc_obj.create({"name": "Test WC", "company_id": cls.company.id})
routing = cls.routing_obj.create(
{
"name": "Test Routing",
"company_id": cls.company.id,
"operation_ids": [
(0, 0, {"name": "Single operation", "workcenter_id": test_wc.id})
],
}
)
cls.bom_1 = cls.bom_obj.create(
{
"product_tmpl_id": cls.product_1.product_tmpl_id.id,
"routing_id": routing.id,
"company_id": cls.company.id,
}
)
cls.bom_line_obj.create(
{
"product_id": cls.component_1.id,
"bom_id": cls.bom_1.id,
"product_qty": 5.0,
}
)
def test_01_change_lot(self):
self.env["stock.quant"]._update_available_quantity(
self.component_1, self.src_location, 5, lot_id=self.component_1_lot
)
self.env["stock.quant"]._update_available_quantity(
self.component_1, self.src_location, 2, lot_id=self.component_1_lot_2
)
mo = self.production_obj.create(
{
"product_id": self.product_1.id,
"bom_id": self.bom_1.id,
"location_src_id": self.src_location.id,
"location_dest_id": self.src_location.id,
"product_qty": 1,
"product_uom_id": self.product_1.uom_id.id,
}
)
mo._onchange_move_raw()
mo.action_confirm()
mo.action_assign()
mo.button_plan()
workorder = mo.workorder_ids
self.assertTrue(workorder)
self.assertEqual(len(mo.move_raw_ids.move_line_ids), 1)
self.assertEqual(len(workorder.raw_workorder_line_ids), 1)
old_ml = mo.move_raw_ids.move_line_ids
old_wol = workorder.raw_workorder_line_ids
self.assertEqual(old_ml.product_uom_qty, 5)
self.assertEqual(old_ml.lot_id, self.component_1_lot)
self.assertEqual(old_wol.qty_to_consume, 5)
self.assertEqual(old_wol.qty_reserved, 5)
self.assertEqual(old_wol.lot_id, self.component_1_lot)
wiz_dict = workorder.raw_workorder_line_ids.action_new_line_wizard()
mrp_wo_nl = Form(
self.env["mrp.workorder.new.line"].with_context(wiz_dict["context"])
)
mrp_wo_nl.lot_id = self.component_1_lot_2
mrp_wo_nl.product_qty = 6
with self.assertRaises(
ValidationError,
msg="The quantity must be lower than the original line quantity: 5.",
):
mrp_wo_nl.save()
mrp_wo_nl.product_qty = 3
mrp_wo = mrp_wo_nl.save()
with self.assertRaises(
ValidationError,
msg="The quantity must be lower or equal than the available quantity: 2.",
):
mrp_wo.action_validate()
mrp_wo_nl.product_qty = 2
mrp_wo = mrp_wo_nl.save()
mrp_wo.action_validate()
self.assertEqual(len(mo.move_raw_ids.move_line_ids), 2)
self.assertEqual(len(workorder.raw_workorder_line_ids), 2)
new_ml = mo.move_raw_ids.move_line_ids.filtered(lambda ml: ml.id != old_ml.id)
new_wol = workorder.raw_workorder_line_ids.filtered(
lambda wol: wol.id != old_wol.id
)
self.assertEqual(old_ml.product_uom_qty, 3)
self.assertEqual(old_ml.lot_id, self.component_1_lot)
self.assertEqual(old_wol.qty_to_consume, 3)
self.assertEqual(old_wol.qty_reserved, 3)
self.assertEqual(old_wol.lot_id, self.component_1_lot)
self.assertEqual(new_ml.product_uom_qty, 2)
self.assertEqual(new_ml.lot_id, self.component_1_lot_2)
self.assertEqual(new_wol.qty_to_consume, 2)
self.assertEqual(new_wol.qty_reserved, 2)
self.assertEqual(new_wol.lot_id, self.component_1_lot_2)

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record
id="mrp_production_workorder_form_view_inherit_update_components"
model="ir.ui.view"
>
<field name="name">mrp.workorder.form</field>
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit" />
<field name="arch" type="xml">
<xpath
expr="//field[@name='raw_workorder_line_ids']/tree/field[@name='qty_done']"
position="after"
>
<button
name="action_new_line_wizard"
title="Create New Line"
type="object"
icon="fa-chain-broken"
attrs="{'invisible': ['|', '|', ('qty_to_consume', 'in', [0, 1]), ('parent.state', 'not in', ['ready','progress']), ('lot_id', '=', False)]}"
/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
from . import mrp_workorder_new_line

View File

@@ -0,0 +1,108 @@
# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_compare
class MrpWorkOrderNewLine(models.TransientModel):
_name = "mrp.workorder.new.line"
_description = "MRP Work Order New Line"
product_id = fields.Many2one("product.product", required=True, readonly=True)
workorder_id = fields.Many2one(
"mrp.workorder", "Work Order", required=True, check_company=True
)
workorder_line_id = fields.Many2one(
"mrp.workorder.line", "Work Order Line", required=True, check_company=True
)
product_qty = fields.Float(
"Quantity", default=1.0, required=True, digits="Product Unit of Measure"
)
original_line_qty = fields.Float(
"Original Quantity",
readonly=True,
required=True,
digits="Product Unit of Measure",
)
lot_id = fields.Many2one(
"stock.production.lot",
string="Lot/Serial Number",
domain="[('product_id', '=', product_id), ('company_id', '=', company_id)]",
)
company_id = fields.Many2one("res.company", string="Company")
def action_validate(self):
old_available_quantity = self.env["stock.quant"]._get_available_quantity(
self.workorder_line_id.move_id.product_id,
self.workorder_line_id.move_id.location_id,
lot_id=self.workorder_line_id.lot_id,
)
new_available_quantity = self.env["stock.quant"]._get_available_quantity(
self.workorder_line_id.move_id.product_id,
self.workorder_line_id.move_id.location_id,
lot_id=self.lot_id,
)
if (
float_compare(
new_available_quantity,
self.product_qty,
precision_rounding=self.product_id.uom_id.rounding,
)
< 0
):
self.not_enought_qty_to_reserve(new_available_quantity)
self.workorder_line_id.write(
{
"qty_done": self.workorder_line_id.qty_done - self.product_qty,
"qty_to_consume": self.workorder_line_id.qty_to_consume
- self.product_qty,
"qty_reserved": self.workorder_line_id.qty_reserved - self.product_qty,
}
)
self.workorder_line_id.move_id._update_reserved_quantity(
-self.product_qty,
old_available_quantity,
self.workorder_line_id.move_id.location_id,
lot_id=self.workorder_line_id.lot_id,
)
self.workorder_line_id.move_id._update_reserved_quantity(
self.product_qty,
new_available_quantity,
self.workorder_line_id.move_id.location_id,
lot_id=self.lot_id,
)
line_values = self.workorder_id._generate_lines_values(
self.workorder_line_id.move_id, self.product_qty
)
for value in line_values:
value["lot_id"] = self.lot_id.id
self.env["mrp.workorder.line"].create(line_values)
def not_enought_qty_to_reserve(self, new_available_quantity):
raise ValidationError(
_("The quantity must be lower or equal than the available quantity: %s.")
% (new_available_quantity)
)
@api.constrains("product_qty")
def _check_product_qty(self):
if (
float_compare(
self.product_qty,
self.original_line_qty,
precision_rounding=self.product_id.uom_id.rounding,
)
>= 0
):
raise ValidationError(
_("The quantity must be lower than the original line quantity: %s.")
% (self.original_line_qty)
)

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_mrp_workorder_new_line_form" model="ir.ui.view">
<field name="name">view_mrp_workorder_new_line_form</field>
<field name="model">mrp.workorder.new.line</field>
<field name="arch" type="xml">
<form string="Create New Line">
<sheet>
<group>
<field name="product_id" />
<field name="lot_id" />
<field name="product_qty" />
<field name="company_id" invisible="1" />
</group>
</sheet>
<footer>
<button
name="action_validate"
string="Validate"
type="object"
class="btn-primary"
/>
<button string="Cancel" class="btn-secondary" special="cancel" />
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
../../../../mrp_workorder_update_component

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)