mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
0
mrp_workorder_update_component/README.rst
Normal file
0
mrp_workorder_update_component/README.rst
Normal file
4
mrp_workorder_update_component/__init__.py
Normal file
4
mrp_workorder_update_component/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from . import models
|
||||
from . import wizards
|
||||
19
mrp_workorder_update_component/__manifest__.py
Normal file
19
mrp_workorder_update_component/__manifest__.py
Normal 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,
|
||||
}
|
||||
1
mrp_workorder_update_component/models/__init__.py
Normal file
1
mrp_workorder_update_component/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import mrp_workorder
|
||||
27
mrp_workorder_update_component/models/mrp_workorder.py
Normal file
27
mrp_workorder_update_component/models/mrp_workorder.py
Normal 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",
|
||||
}
|
||||
1
mrp_workorder_update_component/readme/CONTRIBUTORS.rst
Normal file
1
mrp_workorder_update_component/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1 @@
|
||||
* David Jiménez <david.jimenez@forgeflow.com>
|
||||
1
mrp_workorder_update_component/readme/DESCRIPTION.rst
Normal file
1
mrp_workorder_update_component/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1 @@
|
||||
Allows to update the components in the work orders.
|
||||
BIN
mrp_workorder_update_component/static/description/icon.png
Normal file
BIN
mrp_workorder_update_component/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
1
mrp_workorder_update_component/tests/__init__.py
Normal file
1
mrp_workorder_update_component/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_mrp_workorder
|
||||
171
mrp_workorder_update_component/tests/test_mrp_workorder.py
Normal file
171
mrp_workorder_update_component/tests/test_mrp_workorder.py
Normal 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)
|
||||
25
mrp_workorder_update_component/views/mrp_workorder_view.xml
Normal file
25
mrp_workorder_update_component/views/mrp_workorder_view.xml
Normal 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>
|
||||
1
mrp_workorder_update_component/wizards/__init__.py
Normal file
1
mrp_workorder_update_component/wizards/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import mrp_workorder_new_line
|
||||
108
mrp_workorder_update_component/wizards/mrp_workorder_new_line.py
Normal file
108
mrp_workorder_update_component/wizards/mrp_workorder_new_line.py
Normal 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)
|
||||
)
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
../../../../mrp_workorder_update_component
|
||||
6
setup/mrp_workorder_update_component/setup.py
Normal file
6
setup/mrp_workorder_update_component/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
Reference in New Issue
Block a user