mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
1
mrp_production_operation_injection/README.rst
Normal file
1
mrp_production_operation_injection/README.rst
Normal file
@@ -0,0 +1 @@
|
||||
To be auto generated
|
||||
2
mrp_production_operation_injection/__init__.py
Normal file
2
mrp_production_operation_injection/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import wizard
|
||||
24
mrp_production_operation_injection/__manifest__.py
Normal file
24
mrp_production_operation_injection/__manifest__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
{
|
||||
"name": "MRP Production Inject Operation",
|
||||
"summary": "Adds an existing operation from the Bill of Material",
|
||||
"version": "15.0.1.0.0",
|
||||
"development_status": "Beta",
|
||||
"category": "Manufacturing",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"author": "Camptocamp, Odoo Community Association (OCA)",
|
||||
"maintainers": ["grindtildeath"],
|
||||
"license": "AGPL-3",
|
||||
"application": False,
|
||||
"installable": True,
|
||||
"depends": [
|
||||
"mrp_workorder_sequence",
|
||||
],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"views/mrp_production.xml",
|
||||
"views/mrp_workorder.xml",
|
||||
"wizard/mrp_workorder_injector.xml",
|
||||
],
|
||||
}
|
||||
2
mrp_production_operation_injection/models/__init__.py
Normal file
2
mrp_production_operation_injection/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import mrp_production
|
||||
from . import mrp_workorder
|
||||
72
mrp_production_operation_injection/models/mrp_production.py
Normal file
72
mrp_production_operation_injection/models/mrp_production.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = "mrp.production"
|
||||
|
||||
display_inject_workorder = fields.Boolean(
|
||||
compute="_compute_display_inject_workorder"
|
||||
)
|
||||
|
||||
@api.depends("state", "bom_id", "bom_id.operation_ids", "workorder_ids")
|
||||
def _compute_display_inject_workorder(self):
|
||||
for production in self:
|
||||
production.display_inject_workorder = (
|
||||
production.state in ["confirmed", "progress", "to_close"]
|
||||
and production.bom_id.operation_ids
|
||||
and production.workorder_ids
|
||||
)
|
||||
|
||||
def action_open_workorder_injector(self):
|
||||
self.ensure_one()
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(
|
||||
"mrp_production_operation_injection.mrp_workorder_injector_action"
|
||||
)
|
||||
ctx = self.env.context.copy()
|
||||
ctx["default_production_id"] = self.id
|
||||
action.update({"context": ctx})
|
||||
return action
|
||||
|
||||
def _prepare_injected_workorder_values(self, operation):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"name": operation.name,
|
||||
"production_id": self.id,
|
||||
"workcenter_id": operation.workcenter_id.id,
|
||||
"product_uom_id": self.product_uom_id.id,
|
||||
"operation_id": operation.id,
|
||||
"state": "pending",
|
||||
}
|
||||
|
||||
def _add_workorder(self, operation, previous_workorder):
|
||||
self.ensure_one()
|
||||
following_workorders = self.workorder_ids.filtered(
|
||||
lambda w: w.sequence > previous_workorder.sequence
|
||||
)
|
||||
next_workorder = fields.first(following_workorders)
|
||||
# Prepare creation of new workorder
|
||||
workorder_values = self._prepare_injected_workorder_values(operation)
|
||||
workorder_values["sequence"] = previous_workorder.sequence + 1
|
||||
workorder_values["next_work_order_id"] = next_workorder.id
|
||||
# FIXME: state computation is not good in Odoo anyway so handle
|
||||
# only most 'probable' cases only
|
||||
if next_workorder.state in ["ready", "progress"]:
|
||||
workorder_values["state"] = "ready"
|
||||
# Update following workorders sequence before create to make sure workorders
|
||||
# can be ordered properly for _action_confirm (cf override in mrp_workorder)
|
||||
for wo in following_workorders:
|
||||
wo.sequence += 1
|
||||
new_workorder = self.env["mrp.workorder"].create(workorder_values)
|
||||
# Update next workorder
|
||||
# FIXME: state computation is not good in Odoo anyway so handle
|
||||
# only most 'probable' cases only
|
||||
if next_workorder.state == "ready":
|
||||
next_workorder.state = "pending"
|
||||
new_workorder.duration_expected = new_workorder._get_duration_expected()
|
||||
# Replan if needed after cache invalidation to make sure all workorders are considered
|
||||
self.invalidate_cache()
|
||||
if self.is_planned:
|
||||
self._plan_workorders(replan=True)
|
||||
return True
|
||||
17
mrp_production_operation_injection/models/mrp_workorder.py
Normal file
17
mrp_production_operation_injection/models/mrp_workorder.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpWorkorder(models.Model):
|
||||
|
||||
_inherit = "mrp.workorder"
|
||||
|
||||
sequence = fields.Integer(readonly=True)
|
||||
|
||||
def _action_confirm(self):
|
||||
# HACK: Ensure self is ordered according to redefined _order attribute
|
||||
# in mrp_sequence module as _action_confirm needs to loop in this
|
||||
# order to redefine next_work_order_id properly
|
||||
self = self.sorted()
|
||||
return super()._action_confirm()
|
||||
@@ -0,0 +1 @@
|
||||
* Akim Juillerat <akim.juillerat@camptocamp.com>
|
||||
@@ -0,0 +1,2 @@
|
||||
This module provides a wizard to add extra workorders based on existing BOM
|
||||
operations, and to select where such workorder should be added.
|
||||
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
mrp_production_operation_injection.access_mrp_workorder_injector,access_mrp_workorder_injector,mrp_production_operation_injection.model_mrp_workorder_injector,mrp.group_mrp_user,1,1,1,1
|
||||
|
1
mrp_production_operation_injection/tests/__init__.py
Normal file
1
mrp_production_operation_injection/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_mrp_production_inject_operation
|
||||
@@ -0,0 +1,262 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
from odoo import fields
|
||||
from odoo.tests.common import Form, TransactionCase
|
||||
|
||||
|
||||
class TestMrpProductionInjectOperation(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
||||
cls.product_template_drawer = cls.env.ref(
|
||||
"product.product_product_27_product_template"
|
||||
)
|
||||
cls.product_drawer = cls.env.ref("product.product_product_27")
|
||||
cls.bom_drawer = cls.env.ref("mrp.mrp_bom_drawer_rout")
|
||||
cls.packing_operation = cls.env.ref("mrp.mrp_routing_workcenter_4")
|
||||
cls.testing_operation = cls.env.ref("mrp.mrp_routing_workcenter_3")
|
||||
cls.assembly_operation = cls.env.ref("mrp.mrp_routing_workcenter_1")
|
||||
cls.assembly_line_workcenter = cls.env.ref("mrp.mrp_workcenter_3")
|
||||
cls.productive_time = cls.env.ref("mrp.block_reason7")
|
||||
|
||||
cls.color_attribute = cls.env.ref("product.product_attribute_2")
|
||||
cls.color_att_value_white = cls.env.ref("product.product_attribute_value_3")
|
||||
cls.color_att_value_black = cls.env.ref("product.product_attribute_value_4")
|
||||
|
||||
@classmethod
|
||||
def _define_color_attribute_on_drawer(cls):
|
||||
"""
|
||||
Redefine Drawer product to manage attributes by:
|
||||
- Creating white product
|
||||
- Defining color as attribute of drawer product with white and black values
|
||||
- Adding white component to the BOM drawer applying only on selected variant
|
||||
- Adding a painting operation to the BOM drawer consuming white product only
|
||||
on selected variant
|
||||
|
||||
"""
|
||||
white_color_product_tmpl_form = Form(cls.env["product.template"])
|
||||
white_color_product_tmpl_form.name = "White color"
|
||||
white_color_product_tmpl = white_color_product_tmpl_form.save()
|
||||
|
||||
cls.env["product.template.attribute.line"].create(
|
||||
{
|
||||
"product_tmpl_id": cls.product_template_drawer.id,
|
||||
"attribute_id": cls.color_attribute.id,
|
||||
"value_ids": [
|
||||
fields.Command.set(
|
||||
[cls.color_att_value_white.id, cls.color_att_value_black.id]
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
tmpl_attr_value_white = cls.env["product.template.attribute.value"].search(
|
||||
[
|
||||
("product_tmpl_id", "=", cls.product_template_drawer.id),
|
||||
("product_attribute_value_id", "=", cls.color_att_value_white.id),
|
||||
]
|
||||
)
|
||||
tmpl_attr_value_black = cls.env["product.template.attribute.value"].search(
|
||||
[
|
||||
("product_tmpl_id", "=", cls.product_template_drawer.id),
|
||||
("product_attribute_value_id", "=", cls.color_att_value_black.id),
|
||||
]
|
||||
)
|
||||
|
||||
cls.env["mrp.bom.line"].create(
|
||||
{
|
||||
"bom_id": cls.bom_drawer.id,
|
||||
"product_id": white_color_product_tmpl.product_variant_id.id,
|
||||
"bom_product_template_attribute_value_ids": [
|
||||
fields.Command.link(tmpl_attr_value_white.id)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
cls.env["mrp.routing.workcenter"].create(
|
||||
{
|
||||
"name": "Painting",
|
||||
"bom_id": cls.bom_drawer.id,
|
||||
"workcenter_id": cls.assembly_line_workcenter.id,
|
||||
"bom_product_template_attribute_value_ids": [
|
||||
fields.Command.link(tmpl_attr_value_white.id)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
white_drawer = cls.product_template_drawer.product_variant_ids.filtered(
|
||||
lambda p: p.product_template_variant_value_ids == tmpl_attr_value_white
|
||||
)
|
||||
black_drawer = cls.product_template_drawer.product_variant_ids.filtered(
|
||||
lambda p: p.product_template_variant_value_ids == tmpl_attr_value_black
|
||||
)
|
||||
|
||||
return white_drawer, black_drawer
|
||||
|
||||
@classmethod
|
||||
def _create_manufacturing_order(cls, product, bom=None):
|
||||
mo_form = Form(cls.env["mrp.production"])
|
||||
mo_form.product_id = product
|
||||
if bom is not None:
|
||||
mo_form.bom_id = bom
|
||||
mo = mo_form.save()
|
||||
return mo
|
||||
|
||||
@classmethod
|
||||
def _get_injector_wizard_form(cls, production):
|
||||
action = production.action_open_workorder_injector()
|
||||
injector_form = Form(
|
||||
cls.env[action["res_model"]].with_context(**action.get("context", {}))
|
||||
)
|
||||
return injector_form
|
||||
|
||||
@classmethod
|
||||
def _inject_operation(cls, production, new_operation, previous_workorder):
|
||||
injector_form = cls._get_injector_wizard_form(production)
|
||||
injector_form.operation_id = new_operation
|
||||
injector_form.workorder_id = previous_workorder
|
||||
injector_wiz = injector_form.save()
|
||||
injector_wiz.action_add_operation()
|
||||
|
||||
@classmethod
|
||||
def _get_new_workorder(cls, previously_existing_wos, existing_wos):
|
||||
return previously_existing_wos - existing_wos
|
||||
|
||||
@classmethod
|
||||
def _record_time_tracking(cls, workorder, duration, productivity):
|
||||
workorder_form = Form(workorder)
|
||||
with workorder_form.time_ids.new() as time_tracking_form:
|
||||
time_tracking_form.date_end = fields.Datetime.add(
|
||||
time_tracking_form.date_start, seconds=duration
|
||||
)
|
||||
time_tracking_form.loss_id = productivity
|
||||
|
||||
def test_injector_allowed_operations_no_variant(self):
|
||||
"""Test only operations from bom are allowed in wizard"""
|
||||
mo = self._create_manufacturing_order(self.product_drawer, self.bom_drawer)
|
||||
mo.action_confirm()
|
||||
injector_form = self._get_injector_wizard_form(mo)
|
||||
non_related_bom_operations = self.env["mrp.routing.workcenter"].search(
|
||||
[("id", "not in", self.bom_drawer.operation_ids.ids)]
|
||||
)
|
||||
self.assertTrue(non_related_bom_operations)
|
||||
for op in non_related_bom_operations:
|
||||
self.assertNotIn(op, injector_form.allowed_bom_operation_ids)
|
||||
for op in mo.bom_id.operation_ids:
|
||||
self.assertIn(op, injector_form.allowed_bom_operation_ids)
|
||||
|
||||
def test_injector_allowed_operations_variant(self):
|
||||
"""Test only operations from bom are allowed in wizard"""
|
||||
white_drawer, black_drawer = self._define_color_attribute_on_drawer()
|
||||
mo = self._create_manufacturing_order(white_drawer, self.bom_drawer)
|
||||
mo.action_confirm()
|
||||
injector_form = self._get_injector_wizard_form(mo)
|
||||
for op in mo.bom_id.operation_ids:
|
||||
self.assertIn(op, injector_form.allowed_bom_operation_ids)
|
||||
mo = self._create_manufacturing_order(black_drawer, self.bom_drawer)
|
||||
mo.action_confirm()
|
||||
injector_form = self._get_injector_wizard_form(mo)
|
||||
for op in mo.bom_id.operation_ids:
|
||||
if op.bom_product_template_attribute_value_ids:
|
||||
self.assertEqual(op.name, "Painting")
|
||||
self.assertNotIn(op, injector_form.allowed_bom_operation_ids)
|
||||
else:
|
||||
self.assertIn(op, injector_form.allowed_bom_operation_ids)
|
||||
|
||||
def test_injector_allowed_workorders_no_variant(self):
|
||||
"""Test only workorders from manufacturing order are allowed in wizard"""
|
||||
mo = self._create_manufacturing_order(self.product_drawer, self.bom_drawer)
|
||||
mo.action_confirm()
|
||||
injector_form = self._get_injector_wizard_form(mo)
|
||||
non_mo_workorders = self.env["mrp.workorder"].search(
|
||||
[("id", "not in", mo.workorder_ids.ids)]
|
||||
)
|
||||
for wo in non_mo_workorders:
|
||||
self.assertNotIn(wo, injector_form.production_workorder_ids)
|
||||
for wo in mo.workorder_ids:
|
||||
self.assertIn(wo, injector_form.production_workorder_ids)
|
||||
# Ensure only last done workorder is selectable as previous operation
|
||||
first_workorder = fields.first(mo.workorder_ids)
|
||||
second_workorder = first_workorder.next_work_order_id
|
||||
third_workorder = second_workorder.next_work_order_id
|
||||
first_workorder.button_start()
|
||||
first_workorder.button_finish()
|
||||
second_workorder.button_start()
|
||||
second_workorder.button_finish()
|
||||
injector_form = self._get_injector_wizard_form(mo)
|
||||
self.assertNotIn(first_workorder, injector_form.production_workorder_ids)
|
||||
self.assertIn(second_workorder, injector_form.production_workorder_ids)
|
||||
self.assertIn(third_workorder, injector_form.production_workorder_ids)
|
||||
|
||||
def test_inject_operation(self):
|
||||
mo = self._create_manufacturing_order(self.product_drawer, self.bom_drawer)
|
||||
mo.action_confirm()
|
||||
mo.button_plan()
|
||||
first_workorder = fields.first(mo.workorder_ids)
|
||||
second_workorder = first_workorder.next_work_order_id
|
||||
third_workorder = second_workorder.next_work_order_id
|
||||
# Inject extra testing operation at the end
|
||||
self._inject_operation(mo, self.testing_operation, third_workorder)
|
||||
self.assertEqual(len(mo.workorder_ids), 4)
|
||||
last_workorder = fields.first(mo.workorder_ids.sorted(reverse=True))
|
||||
self.assertEqual(last_workorder.name, self.testing_operation.name)
|
||||
self.assertEqual(last_workorder.operation_id, self.testing_operation)
|
||||
self.assertEqual(
|
||||
last_workorder.workcenter_id, self.testing_operation.workcenter_id
|
||||
)
|
||||
self.assertEqual(last_workorder.state, "pending")
|
||||
self.assertEqual(last_workorder.sequence, 4)
|
||||
self.assertEqual(
|
||||
last_workorder.date_planned_start, third_workorder.date_planned_finished
|
||||
)
|
||||
self.assertEqual(
|
||||
last_workorder.date_planned_finished,
|
||||
last_workorder.workcenter_id.resource_calendar_id.plan_hours(
|
||||
last_workorder.duration_expected / 60.0,
|
||||
last_workorder.date_planned_start,
|
||||
compute_leaves=True,
|
||||
domain=[("time_type", "in", ["leave", "other"])],
|
||||
),
|
||||
)
|
||||
self.assertEqual(third_workorder.next_work_order_id, last_workorder)
|
||||
# Start first op and register time tracking
|
||||
first_workorder.button_start()
|
||||
self._record_time_tracking(first_workorder, 60, self.productive_time)
|
||||
first_workorder.button_finish()
|
||||
self.assertEqual(first_workorder.state, "done")
|
||||
self.assertEqual(second_workorder.state, "ready")
|
||||
# Inject extra packing operation before second workorder
|
||||
pre_existing_wo_ids = set(mo.workorder_ids.ids)
|
||||
self._inject_operation(mo, self.packing_operation, first_workorder)
|
||||
existing_wo_ids = set(mo.workorder_ids.ids)
|
||||
new_workorder = self.env["mrp.workorder"].browse(
|
||||
existing_wo_ids - pre_existing_wo_ids
|
||||
)
|
||||
self.assertEqual(new_workorder.state, "ready")
|
||||
self.assertEqual(new_workorder.sequence, 2)
|
||||
self.assertEqual(
|
||||
new_workorder.date_planned_start,
|
||||
new_workorder.workcenter_id.resource_calendar_id.plan_hours(
|
||||
-new_workorder.duration_expected / 60.0,
|
||||
new_workorder.date_planned_finished,
|
||||
compute_leaves=True,
|
||||
domain=[("time_type", "in", ["leave", "other"])],
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
new_workorder.date_planned_finished, second_workorder.date_planned_start
|
||||
)
|
||||
self.assertEqual(new_workorder.next_work_order_id, second_workorder)
|
||||
# Second workorder is now the third one
|
||||
self.assertEqual(second_workorder.state, "pending")
|
||||
self.assertEqual(second_workorder.sequence, 3)
|
||||
self.assertEqual(second_workorder.next_work_order_id, third_workorder)
|
||||
# Third workorder is now the fourth one
|
||||
self.assertEqual(third_workorder.state, "pending")
|
||||
self.assertEqual(third_workorder.sequence, 4)
|
||||
self.assertEqual(third_workorder.next_work_order_id, last_workorder)
|
||||
# Last workorder is still the last one
|
||||
self.assertEqual(last_workorder.state, "pending")
|
||||
self.assertEqual(last_workorder.sequence, 5)
|
||||
self.assertFalse(last_workorder.next_work_order_id)
|
||||
19
mrp_production_operation_injection/views/mrp_production.xml
Normal file
19
mrp_production_operation_injection/views/mrp_production.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="mrp_production_form_view_inherit" 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">
|
||||
<header position="inside">
|
||||
<field name="display_inject_workorder" invisible="1" />
|
||||
<button
|
||||
name="action_open_workorder_injector"
|
||||
type="object"
|
||||
string="Inject operation"
|
||||
attrs="{'invisible': [('display_inject_workorder', '=', False)]}"
|
||||
/>
|
||||
</header>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
16
mrp_production_operation_injection/views/mrp_workorder.xml
Normal file
16
mrp_production_operation_injection/views/mrp_workorder.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="mrp_production_workorder_tree_view_inherit" model="ir.ui.view">
|
||||
<field name="name">mrp.workorder.tree</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field
|
||||
name="inherit_id"
|
||||
ref="mrp_workorder_sequence.mrp_production_workorder_tree_view_inherit"
|
||||
/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="sequence" position="attributes">
|
||||
<attribute name="force_save">1</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
1
mrp_production_operation_injection/wizard/__init__.py
Normal file
1
mrp_production_operation_injection/wizard/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import mrp_workorder_injector
|
||||
@@ -0,0 +1,67 @@
|
||||
# Copyright 2022 Camptocamp SA
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MrpWorkorderInjector(models.TransientModel):
|
||||
|
||||
_name = "mrp.workorder.injector"
|
||||
_description = "Inject operation from BOM into workorders"
|
||||
|
||||
production_id = fields.Many2one("mrp.production", required=True)
|
||||
bom_id = fields.Many2one("mrp.bom", related="production_id.bom_id")
|
||||
allowed_bom_operation_ids = fields.Many2many(
|
||||
"mrp.routing.workcenter", compute="_compute_allowed_bom_operation_ids"
|
||||
)
|
||||
production_workorder_ids = fields.Many2many(
|
||||
"mrp.workorder", compute="_compute_production_workorder_ids"
|
||||
)
|
||||
operation_id = fields.Many2one(
|
||||
"mrp.routing.workcenter", "New operation", required=True
|
||||
)
|
||||
workorder_id = fields.Many2one("mrp.workorder", "Previous workorder", required=True)
|
||||
|
||||
@api.depends("bom_id", "bom_id.operation_ids")
|
||||
def _compute_allowed_bom_operation_ids(self):
|
||||
for wiz in self:
|
||||
bom_operations = wiz.bom_id.operation_ids
|
||||
# TODO: Move this check in default_get or somewhere else?
|
||||
if not wiz.bom_id or not bom_operations:
|
||||
wiz.allowed_bom_operation_ids = [fields.Command.clear()]
|
||||
continue
|
||||
# Filter out operations applying only for other variants
|
||||
allowed_operations = bom_operations.filtered(
|
||||
lambda o: not o._skip_operation_line(wiz.production_id.product_id)
|
||||
)
|
||||
# TODO: filter out operations consuming components?
|
||||
# AFAICS the link from bom line to operations will only be used to define
|
||||
# on the stock move in which workorder such component is supposed to be
|
||||
# consumed, and will then be used to compute the state of the workorder
|
||||
# through the reservation_state field of the MO:
|
||||
# - Waiting components if move is not assigned
|
||||
# - Ready if move is assigned
|
||||
wiz.allowed_bom_operation_ids = [fields.Command.set(allowed_operations.ids)]
|
||||
|
||||
@api.depends("production_id")
|
||||
def _compute_production_workorder_ids(self):
|
||||
for wiz in self:
|
||||
prod_workorders = wiz.production_id.workorder_ids
|
||||
if not prod_workorders:
|
||||
wiz.production_workorder_ids = [fields.Command.clear()]
|
||||
continue
|
||||
done_wos = prod_workorders.filtered(lambda w: w.state == "done")
|
||||
if not done_wos or len(done_wos) == 1:
|
||||
wiz.production_workorder_ids = [fields.Command.set(prod_workorders.ids)]
|
||||
continue
|
||||
# Only allow to add new operation after last Done workorder
|
||||
last_done_wo = fields.first(done_wos.sorted(reverse=True))
|
||||
allowed_wos = last_done_wo + prod_workorders.filtered(
|
||||
lambda w: w.state != "done"
|
||||
)
|
||||
wiz.production_workorder_ids = allowed_wos
|
||||
|
||||
def action_add_operation(self):
|
||||
self.ensure_one()
|
||||
self.production_id._add_workorder(self.operation_id, self.workorder_id)
|
||||
return {"type": "ir.actions.act_window_close"}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="mrp_workorder_injector_view_form" model="ir.ui.view">
|
||||
<field name="name">mrp.workorder.injector.form.view</field>
|
||||
<field name="model">mrp.workorder.injector</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Add operation to workorders">
|
||||
<group>
|
||||
<field name="production_id" invisible="1" />
|
||||
<field name="bom_id" invisible="1" />
|
||||
<field name="allowed_bom_operation_ids" invisible="1" />
|
||||
<field name="production_workorder_ids" invisible="1" />
|
||||
<field
|
||||
name="operation_id"
|
||||
domain="[('id', 'in', allowed_bom_operation_ids)]"
|
||||
options="{'no_create': True, 'no_quick_create': True, 'no_create_edit':True}"
|
||||
/>
|
||||
<field
|
||||
name="workorder_id"
|
||||
domain="[('id', 'in', production_workorder_ids)]"
|
||||
options="{'no_create': True, 'no_quick_create': True, 'no_create_edit':True}"
|
||||
/>
|
||||
</group>
|
||||
<footer>
|
||||
<button
|
||||
name="action_add_operation"
|
||||
type="object"
|
||||
string="Add"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
<button special="cancel" string="Cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="mrp_workorder_injector_action" model="ir.actions.act_window">
|
||||
<field name="name">Inject operation</field>
|
||||
<field name="res_model">mrp.workorder.injector</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1 @@
|
||||
../../../../mrp_production_operation_injection
|
||||
6
setup/mrp_production_operation_injection/setup.py
Normal file
6
setup/mrp_production_operation_injection/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