mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[ADD] mrp_lot_number_propagation
This commit is contained in:
committed by
Thierry Ducrest
parent
2a303f875a
commit
f1e4bfe52c
1
mrp_lot_number_propagation/README.rst
Normal file
1
mrp_lot_number_propagation/README.rst
Normal file
@@ -0,0 +1 @@
|
||||
.
|
||||
1
mrp_lot_number_propagation/__init__.py
Normal file
1
mrp_lot_number_propagation/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
20
mrp_lot_number_propagation/__manifest__.py
Normal file
20
mrp_lot_number_propagation/__manifest__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
{
|
||||
"name": "MRP Serial Number Propagation",
|
||||
"version": "15.0.0.1.0",
|
||||
"development_status": "Alpha",
|
||||
"license": "AGPL-3",
|
||||
"author": "Camptocamp, Odoo Community Association (OCA)",
|
||||
"maintainers": ["sebalix"],
|
||||
"summary": "Propagate a serial number from a component to a finished product",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"category": "Manufacturing",
|
||||
"depends": ["mrp"],
|
||||
"data": [
|
||||
"views/mrp_bom.xml",
|
||||
"views/mrp_production.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"application": False,
|
||||
}
|
||||
4
mrp_lot_number_propagation/models/__init__.py
Normal file
4
mrp_lot_number_propagation/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import mrp_bom
|
||||
from . import mrp_bom_line
|
||||
from . import mrp_production
|
||||
from . import stock_move
|
||||
74
mrp_lot_number_propagation/models/mrp_bom.py
Normal file
74
mrp_lot_number_propagation/models/mrp_bom.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import api, fields, models, tools
|
||||
|
||||
|
||||
class MrpBom(models.Model):
|
||||
_inherit = "mrp.bom"
|
||||
|
||||
lot_number_propagation = fields.Boolean(
|
||||
default=False,
|
||||
help=(
|
||||
"Allow to propagate the lot/serial number "
|
||||
"from a component to the finished product."
|
||||
),
|
||||
)
|
||||
display_lot_number_propagation = fields.Boolean(
|
||||
compute="_compute_display_lot_number_propagation"
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"type",
|
||||
"product_tmpl_id.tracking",
|
||||
"product_qty",
|
||||
"product_uom_id",
|
||||
"bom_line_ids.product_id.tracking",
|
||||
"bom_line_ids.product_qty",
|
||||
"bom_line_ids.product_uom_id",
|
||||
)
|
||||
def _compute_display_lot_number_propagation(self):
|
||||
"""Check if a lot number can be propagated.
|
||||
|
||||
A lot number can be propagated from a component to the finished product if:
|
||||
- the type of the BoM is normal (Manufacture this product)
|
||||
- the finished product is tracked by serial number
|
||||
- the quantity of the finished product is 1 and its UoM is unit
|
||||
- there is at least one bom line, with a component tracked by serial,
|
||||
having a quantity of 1 and its UoM is unit
|
||||
"""
|
||||
uom_unit = self.env.ref("uom.product_uom_unit")
|
||||
for bom in self:
|
||||
bom.display_lot_number_propagation = (
|
||||
bom.type in self._get_lot_number_propagation_bom_types()
|
||||
and bom.product_tmpl_id.tracking == "serial"
|
||||
and tools.float_compare(
|
||||
bom.product_qty, 1, precision_rounding=bom.product_uom_id.rounding
|
||||
)
|
||||
== 0
|
||||
and bom.product_uom_id == uom_unit
|
||||
and bom._has_tracked_product_to_propagate()
|
||||
)
|
||||
|
||||
def _get_lot_number_propagation_bom_types(self):
|
||||
return ["normal"]
|
||||
|
||||
def _has_tracked_product_to_propagate(self):
|
||||
self.ensure_one()
|
||||
uom_unit = self.env.ref("uom.product_uom_unit")
|
||||
for line in self.bom_line_ids:
|
||||
if (
|
||||
line.product_id.tracking == "serial"
|
||||
and tools.float_compare(
|
||||
line.product_qty, 1, precision_rounding=line.product_uom_id.rounding
|
||||
)
|
||||
== 0
|
||||
and line.product_uom_id == uom_unit
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@api.onchange("display_lot_number_propagation")
|
||||
def onchange_display_lot_number_propagation(self):
|
||||
if not self.display_lot_number_propagation:
|
||||
self.lot_number_propagation = False
|
||||
62
mrp_lot_number_propagation/models/mrp_bom_line.py
Normal file
62
mrp_lot_number_propagation/models/mrp_bom_line.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class MrpBomLine(models.Model):
|
||||
_inherit = "mrp.bom.line"
|
||||
|
||||
propagate_lot_number = fields.Boolean(
|
||||
default=False,
|
||||
)
|
||||
display_propagate_lot_number = fields.Boolean(
|
||||
compute="_compute_display_propagate_lot_number"
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"bom_id.display_lot_number_propagation",
|
||||
"bom_id.lot_number_propagation",
|
||||
)
|
||||
def _compute_display_propagate_lot_number(self):
|
||||
for line in self:
|
||||
line.display_propagate_lot_number = (
|
||||
line.bom_id.display_lot_number_propagation
|
||||
and line.bom_id.lot_number_propagation
|
||||
)
|
||||
|
||||
@api.constrains(
|
||||
"propagate_lot_number",
|
||||
"bom_id.lot_number_propagation",
|
||||
"product_id.tracking",
|
||||
"bom_id.product_tmpl_id.tracking",
|
||||
)
|
||||
def _check_propagate_lot_number(self):
|
||||
"""
|
||||
This function should check:
|
||||
|
||||
- if the bom has lot_number_propagation marked, there is one and
|
||||
only one line of this bom with propagate_lot_number marked.
|
||||
- the bom line being marked with lot_number_propagation is of the same
|
||||
tracking type as the finished product
|
||||
"""
|
||||
for line in self:
|
||||
lines_to_propagate = line.bom_id.bom_line_ids.filtered(
|
||||
lambda o: o.propagate_lot_number
|
||||
)
|
||||
if line.bom_id.lot_number_propagation:
|
||||
if len(lines_to_propagate) > 1:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Only one BoM line can propagate its lot/serial number "
|
||||
"to the finished product."
|
||||
)
|
||||
)
|
||||
if line.propagate_lot_number and line.product_id.tracking != "serial":
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Only components tracked by serial number can propagate "
|
||||
"its lot/serial number to the finished product."
|
||||
)
|
||||
)
|
||||
152
mrp_lot_number_propagation/models/mrp_production.py
Normal file
152
mrp_lot_number_propagation/models/mrp_production.py
Normal file
@@ -0,0 +1,152 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from odoo import _, api, fields, models, tools
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
from odoo.addons.base.models.ir_ui_view import (
|
||||
transfer_modifiers_to_node,
|
||||
transfer_node_to_modifiers,
|
||||
)
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = "mrp.production"
|
||||
|
||||
is_lot_number_propagated = fields.Boolean(
|
||||
default=False,
|
||||
readonly=True,
|
||||
help=(
|
||||
"Lot/serial number is propagated "
|
||||
"from a component to the finished product."
|
||||
),
|
||||
)
|
||||
propagated_lot_producing = fields.Char(
|
||||
compute="_compute_propagated_lot_producing",
|
||||
help=(
|
||||
"The BoM used on this manufacturing order is set to propagate "
|
||||
"lot number from one of its components. The value will be "
|
||||
"computed once the corresponding component is selected."
|
||||
),
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"move_raw_ids.propagate_lot_number",
|
||||
"move_raw_ids.move_line_ids.qty_done",
|
||||
"move_raw_ids.move_line_ids.lot_id",
|
||||
)
|
||||
def _compute_propagated_lot_producing(self):
|
||||
for order in self:
|
||||
order.propagated_lot_producing = False
|
||||
move_with_lot = order.move_raw_ids.filtered(
|
||||
lambda o: o.propagate_lot_number
|
||||
)
|
||||
line_with_sn = move_with_lot.move_line_ids.filtered(
|
||||
lambda l: (
|
||||
l.lot_id
|
||||
and l.product_id.tracking == "serial"
|
||||
and tools.float_compare(
|
||||
l.qty_done, 1, precision_rounding=l.product_uom_id.rounding
|
||||
)
|
||||
== 0
|
||||
)
|
||||
)
|
||||
if len(line_with_sn) == 1:
|
||||
order.propagated_lot_producing = line_with_sn.lot_id.name
|
||||
|
||||
@api.onchange("bom_id")
|
||||
def _onchange_bom_id_lot_number_propagation(self):
|
||||
self.is_lot_number_propagated = self.bom_id.lot_number_propagation
|
||||
|
||||
def action_confirm(self):
|
||||
res = super().action_confirm()
|
||||
self._set_lot_number_propagation_data_from_bom()
|
||||
return res
|
||||
|
||||
def _set_lot_number_propagation_data_from_bom(self):
|
||||
"""Copy information from BoM to the manufacturing order."""
|
||||
for order in self:
|
||||
order.is_lot_number_propagated = order.bom_id.lot_number_propagation
|
||||
for move in order.move_raw_ids:
|
||||
move.propagate_lot_number = move.bom_line_id.propagate_lot_number
|
||||
|
||||
def _post_inventory(self, cancel_backorder=False):
|
||||
self._create_and_assign_propagated_lot_number()
|
||||
return super()._post_inventory(cancel_backorder=cancel_backorder)
|
||||
|
||||
def _create_and_assign_propagated_lot_number(self):
|
||||
for order in self:
|
||||
if not order.is_lot_number_propagated or order.lot_producing_id:
|
||||
continue
|
||||
finish_moves = order.move_finished_ids.filtered(
|
||||
lambda m: m.product_id == order.product_id
|
||||
and m.state not in ("done", "cancel")
|
||||
)
|
||||
if finish_moves and not finish_moves.quantity_done:
|
||||
lot = self.env["stock.production.lot"].create(
|
||||
{
|
||||
"product_id": order.product_id.id,
|
||||
"company_id": order.company_id.id,
|
||||
"name": order.propagated_lot_producing,
|
||||
}
|
||||
)
|
||||
order.with_context(lot_propagation=True).lot_producing_id = lot
|
||||
|
||||
def write(self, vals):
|
||||
for order in self:
|
||||
if (
|
||||
order.is_lot_number_propagated
|
||||
and "lot_producing_id" in vals
|
||||
and not self.env.context.get("lot_propagation")
|
||||
):
|
||||
raise UserError(
|
||||
_(
|
||||
"Lot/Serial number is propagated from a component, "
|
||||
"you are not allowed to change it."
|
||||
)
|
||||
)
|
||||
return super().write(vals)
|
||||
|
||||
def fields_view_get(
|
||||
self, view_id=None, view_type="form", toolbar=False, submenu=False
|
||||
):
|
||||
# Override to hide the "lot_producing_id" field + "action_generate_serial"
|
||||
# button if the MO is configured to propagate a serial number
|
||||
result = super().fields_view_get(
|
||||
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
|
||||
)
|
||||
if result.get("name") in self._views_to_adapt():
|
||||
result["arch"] = self._fields_view_get_adapt_lot_tags_attrs(result)
|
||||
return result
|
||||
|
||||
def _views_to_adapt(self):
|
||||
"""Return the form view names bound to 'mrp.production' to adapt."""
|
||||
return ["mrp.production.form"]
|
||||
|
||||
def _fields_view_get_adapt_lot_tags_attrs(self, view):
|
||||
"""Hide elements related to lot if it is automatically propagated."""
|
||||
doc = etree.XML(view["arch"])
|
||||
tags = (
|
||||
"//label[@for='lot_producing_id']",
|
||||
"//field[@name='lot_producing_id']/..", # parent <div>
|
||||
)
|
||||
for xpath_expr in tags:
|
||||
attrs_key = "invisible"
|
||||
nodes = doc.xpath(xpath_expr)
|
||||
for field in nodes:
|
||||
attrs = safe_eval(field.attrib.get("attrs", "{}"))
|
||||
if not attrs[attrs_key]:
|
||||
continue
|
||||
invisible_domain = expression.OR(
|
||||
[attrs[attrs_key], [("is_lot_number_propagated", "=", True)]]
|
||||
)
|
||||
attrs[attrs_key] = invisible_domain
|
||||
field.set("attrs", str(attrs))
|
||||
modifiers = {}
|
||||
transfer_node_to_modifiers(field, modifiers, self.env.context)
|
||||
transfer_modifiers_to_node(modifiers, field)
|
||||
return etree.tostring(doc, encoding="unicode")
|
||||
13
mrp_lot_number_propagation/models/stock_move.py
Normal file
13
mrp_lot_number_propagation/models/stock_move.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
propagate_lot_number = fields.Boolean(
|
||||
default=False,
|
||||
readonly=True,
|
||||
)
|
||||
2
mrp_lot_number_propagation/readme/CONTRIBUTORS.rst
Normal file
2
mrp_lot_number_propagation/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,2 @@
|
||||
* Akim Juillerat <akim.juillerat@camptocamp.com>
|
||||
* Sébastien Alix <sebastien.alix@camptocamp.com>
|
||||
1
mrp_lot_number_propagation/readme/DESCRIPTION.rst
Normal file
1
mrp_lot_number_propagation/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1 @@
|
||||
Allow to propagate a lot number from a component to a finished product.
|
||||
1
mrp_lot_number_propagation/readme/ROADMAP.rst
Normal file
1
mrp_lot_number_propagation/readme/ROADMAP.rst
Normal file
@@ -0,0 +1 @@
|
||||
* Add compatibility with lot number (in addition to serial number)
|
||||
1
mrp_lot_number_propagation/tests/__init__.py
Normal file
1
mrp_lot_number_propagation/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_mrp_bom
|
||||
92
mrp_lot_number_propagation/tests/common.py
Normal file
92
mrp_lot_number_propagation/tests/common.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
from odoo import fields
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class Common(common.TransactionCase):
|
||||
|
||||
LOT_NAME = "PROPAGATED-LOT"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
||||
cls.bom = cls.env.ref("mrp.mrp_bom_desk")
|
||||
cls.product_tracked_by_lot = cls.env.ref(
|
||||
"mrp.product_product_computer_desk_leg"
|
||||
)
|
||||
cls.product_tracked_by_sn = cls.env.ref(
|
||||
"mrp.product_product_computer_desk_head"
|
||||
)
|
||||
cls.line_tracked_by_lot = cls.bom.bom_line_ids.filtered(
|
||||
lambda o: o.product_id == cls.product_tracked_by_lot
|
||||
)
|
||||
cls.line_tracked_by_sn = cls.bom.bom_line_ids.filtered(
|
||||
lambda o: o.product_id == cls.product_tracked_by_sn
|
||||
)
|
||||
cls.line_no_tracking = fields.first(
|
||||
cls.bom.bom_line_ids.filtered(lambda o: o.product_id.tracking == "none")
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _update_qty_in_location(
|
||||
cls, location, product, quantity, package=None, lot=None, in_date=None
|
||||
):
|
||||
quants = cls.env["stock.quant"]._gather(
|
||||
product, location, lot_id=lot, package_id=package, strict=True
|
||||
)
|
||||
# this method adds the quantity to the current quantity, so remove it
|
||||
quantity -= sum(quants.mapped("quantity"))
|
||||
cls.env["stock.quant"]._update_available_quantity(
|
||||
product,
|
||||
location,
|
||||
quantity,
|
||||
package_id=package,
|
||||
lot_id=lot,
|
||||
in_date=in_date,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _update_stock_component_qty(cls, order=None, bom=None, location=None):
|
||||
if not order and not bom:
|
||||
return
|
||||
if order:
|
||||
bom = order.bom_id
|
||||
if not location:
|
||||
location = cls.env.ref("stock.stock_location_stock")
|
||||
for line in bom.bom_line_ids:
|
||||
if line.product_id.type != "product":
|
||||
continue
|
||||
lot = None
|
||||
if line.product_id.tracking != "none":
|
||||
lot_name = "".join(
|
||||
random.choice(string.ascii_lowercase) for i in range(10)
|
||||
)
|
||||
if line.propagate_lot_number:
|
||||
lot_name = cls.LOT_NAME
|
||||
vals = {
|
||||
"product_id": line.product_id.id,
|
||||
"company_id": line.company_id.id,
|
||||
"name": lot_name,
|
||||
}
|
||||
lot = cls.env["stock.production.lot"].create(vals)
|
||||
cls._update_qty_in_location(
|
||||
location,
|
||||
line.product_id,
|
||||
line.product_qty,
|
||||
lot=lot,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_lot_quants(cls, lot, location=None):
|
||||
quants = lot.quant_ids.filtered(lambda q: q.quantity > 0)
|
||||
if location:
|
||||
quants = quants.filtered(
|
||||
lambda q: q.location_id.parent_path in location.parent_path
|
||||
)
|
||||
return quants
|
||||
36
mrp_lot_number_propagation/tests/test_mrp_bom.py
Normal file
36
mrp_lot_number_propagation/tests/test_mrp_bom.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from .common import Common
|
||||
|
||||
|
||||
class TestMrpBom(Common):
|
||||
def test_bom_display_lot_number_propagation(self):
|
||||
self.assertTrue(self.bom.display_lot_number_propagation)
|
||||
self.bom.product_tmpl_id.tracking = "none"
|
||||
self.assertFalse(self.bom.display_lot_number_propagation)
|
||||
|
||||
def test_bom_line_check_propagate_lot_number_multi(self):
|
||||
self.bom.lot_number_propagation = True
|
||||
# Flag more than one line to propagate
|
||||
with self.assertRaisesRegex(ValidationError, "Only one BoM"):
|
||||
self.bom.bom_line_ids.write({"propagate_lot_number": True})
|
||||
|
||||
def test_bom_line_check_propagate_lot_number_not_tracked(self):
|
||||
self.bom.lot_number_propagation = True
|
||||
# Flag a line that can't be propagated
|
||||
with self.assertRaisesRegex(ValidationError, "Only components tracked"):
|
||||
self.line_no_tracking.propagate_lot_number = True
|
||||
|
||||
def test_bom_line_check_propagate_lot_number_tracked_by_lot(self):
|
||||
self.bom.lot_number_propagation = True
|
||||
# Flag a line tracked by lot (not SN) which is not supported
|
||||
with self.assertRaisesRegex(ValidationError, "Only components tracked"):
|
||||
self.line_tracked_by_lot.propagate_lot_number = True
|
||||
|
||||
def test_bom_line_check_propagate_lot_number_same_tracking(self):
|
||||
self.bom.lot_number_propagation = True
|
||||
# Flag a line whose tracking type is the same than the finished product
|
||||
self.line_tracked_by_sn.propagate_lot_number = True
|
||||
44
mrp_lot_number_propagation/tests/test_mrp_production.py
Normal file
44
mrp_lot_number_propagation/tests/test_mrp_production.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import Form
|
||||
|
||||
from .common import Common
|
||||
|
||||
|
||||
class TestMrpProduction(Common):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# Configure the BoM to propagate lot number
|
||||
cls.bom.lot_number_propagation = True
|
||||
cls.line_tracked_by_sn.propagate_lot_number = True
|
||||
with Form(cls.env["mrp.production"]) as form:
|
||||
form.bom_id = cls.bom
|
||||
cls.order = form.save()
|
||||
|
||||
def _set_qty_done(self, order):
|
||||
for line in order.move_raw_ids.move_line_ids:
|
||||
line.qty_done = line.product_uom_qty
|
||||
order.qty_producing = order.product_qty
|
||||
|
||||
def test_order_propagated_lot_producing(self):
|
||||
self.assertTrue(self.order.is_lot_number_propagated) # set by onchange
|
||||
self._update_stock_component_qty(self.order)
|
||||
self.order.action_confirm()
|
||||
self.assertTrue(self.order.is_lot_number_propagated) # set by action_confirm
|
||||
self.assertTrue(any(self.order.move_raw_ids.mapped("propagate_lot_number")))
|
||||
self._set_qty_done(self.order)
|
||||
self.assertEqual(self.order.propagated_lot_producing, self.LOT_NAME)
|
||||
|
||||
def test_order_write_lot_producing_id_not_allowed(self):
|
||||
with self.assertRaisesRegex(UserError, "not allowed"):
|
||||
self.order.write({"lot_producing_id": False})
|
||||
|
||||
def test_order_post_inventory(self):
|
||||
self._update_stock_component_qty(self.order)
|
||||
self.order.action_confirm()
|
||||
self._set_qty_done(self.order)
|
||||
self.order.button_mark_done()
|
||||
self.assertEqual(self.order.lot_producing_id.name, self.LOT_NAME)
|
||||
28
mrp_lot_number_propagation/views/mrp_bom.xml
Normal file
28
mrp_lot_number_propagation/views/mrp_bom.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2022 Camptocamp SA
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<odoo>
|
||||
|
||||
<record id="mrp_bom_form_view" model="ir.ui.view">
|
||||
<field name="name">mrp.bom.form.inherit</field>
|
||||
<field name="model">mrp.bom</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_bom_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="company_id" position="after">
|
||||
<field name="display_lot_number_propagation" invisible="1" />
|
||||
<field
|
||||
name="lot_number_propagation"
|
||||
attrs="{'invisible': [('display_lot_number_propagation', '=', False)]}"
|
||||
/>
|
||||
</field>
|
||||
<xpath expr="//field[@name='bom_line_ids']/tree" position="inside">
|
||||
<field name="display_propagate_lot_number" invisible="1" />
|
||||
<field
|
||||
name="propagate_lot_number"
|
||||
attrs="{'column_invisible': ['|', ('parent.display_lot_number_propagation', '=', False), ('parent.lot_number_propagation', '=', False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
25
mrp_lot_number_propagation/views/mrp_production.xml
Normal file
25
mrp_lot_number_propagation/views/mrp_production.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2022 Camptocamp SA
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<odoo>
|
||||
|
||||
<record id="mrp_production_form_view" model="ir.ui.view">
|
||||
<field name="name">mrp.production.form.inherit</field>
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<!-- Place new fields in the first group while being compatible with OE -->
|
||||
<xpath expr="//field[@name='id']/.." position="inside">
|
||||
<field name="is_lot_number_propagated" force_save="1" />
|
||||
</xpath>
|
||||
<label for="lot_producing_id" position="before">
|
||||
<field
|
||||
name="propagated_lot_producing"
|
||||
string="Lot/Serial Number"
|
||||
attrs="{'invisible': [('is_lot_number_propagated', '=', False)]}"
|
||||
/>
|
||||
</label>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user