[14.0][ADD] mrp_component_operation

This commit is contained in:
DavidJForgeFlow
2022-12-01 16:47:21 +01:00
parent 7335c0a769
commit 00500a9d47
21 changed files with 774 additions and 0 deletions

View File

View File

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

View File

@@ -0,0 +1,21 @@
# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
{
"name": "MRP Components Operations",
"version": "14.0.1.0.0",
"author": "ForgeFlow, Odoo Community Association (OCA)",
"summary": "Allows to operate the components from a MO",
"website": "https://github.com/OCA/manufacture",
"category": "Manufacturing",
"depends": ["mrp", "stock_move_forced_lot"],
"data": [
"security/ir.model.access.csv",
"views/mrp_component_operation_views.xml",
"views/mrp_production_views.xml",
"views/stock_view.xml",
"wizards/mrp_component_operate_wizard.xml",
],
"license": "LGPL-3",
"installable": True,
}

View File

@@ -0,0 +1,3 @@
from . import mrp_component_operation
from . import mrp_production
from . import stock_location_route

View File

@@ -0,0 +1,60 @@
# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models
class MrpComponentOperation(models.Model):
_name = "mrp.component.operation"
_description = "Component Operation"
name = fields.Char(help="Component Operation Reference", required=True)
source_location_id = fields.Many2one(
"stock.location",
"Source Location",
help="The Location where the components are.",
)
source_route_id = fields.Many2one(
comodel_name="stock.location.route",
string="Source Route",
help="The Route used to pick the components.",
domain=[("mo_component_selectable", "=", True)],
)
destination_location_id = fields.Many2one(
"stock.location",
"Destination Location",
help="The Location where the components are going to be transferred.",
)
destination_route_id = fields.Many2one(
comodel_name="stock.location.route",
string="Destination Route",
help="The Route used to transfer the components to the destination location.",
domain=[("mo_component_selectable", "=", True)],
)
scrap_location_id = fields.Many2one(
"stock.location",
"Scrap Location",
)
incoming_operation = fields.Selection(
selection=[
("no", "No"),
("replace", "Pick Component from Source Route"),
],
default="no",
required=True,
)
outgoing_operation = fields.Selection(
selection=[
("no", "No"),
("move", "Move to Destination Location"),
("scrap", "Make a Scrap"),
],
default="no",
required=True,
)

View File

@@ -0,0 +1,25 @@
# Copyright 2022 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 MrpProduction(models.Model):
_inherit = "mrp.production"
def button_operate_components(self):
return {
"name": _("Operate Component"),
"view_mode": "form",
"res_model": "mrp.component.operate",
"view_id": self.env.ref(
"mrp_component_operation.view_mrp_component_operate_form"
).id,
"type": "ir.actions.act_window",
"context": {
"default_mo_id": self.id,
"product_ids": self.move_raw_ids.move_line_ids.product_id.mapped("id"),
"lot_ids": self.move_raw_ids.move_line_ids.lot_id.mapped("id"),
},
"target": "new",
}

View File

@@ -0,0 +1,9 @@
# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models
class StockLocationRoute(models.Model):
_inherit = "stock.location.route"
mo_component_selectable = fields.Boolean(string="Selectable on MO Components")

View File

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

View File

@@ -0,0 +1,2 @@
This module allows to operate the components from a MO, being able to move it to another location or making an scrap
and take another from a desired location and link it to the current MO.

View File

@@ -0,0 +1,6 @@
Go to Manufacture -> Configuration -> Settings -> Component Operations.
Create a new Operation, select the desired outgoing operation and the desired incoming operation.
Then fill the Locations and Routes (check in the routes that are selectable for mo operations in the route page).
Once saved, go to the Manufacture Order and click on the button "Operate Component".
Select the desired product (lot if needed) and the operation wanted.
When Confirmed, in the MO will be linked the pickings/scraps done.

View File

@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mrp_component_operation_user,mrp.component.operation.user,model_mrp_component_operation,mrp.group_mrp_user,1,0,0,0
access_mrp_component_operation_manager,mrp.component.operation.manager,model_mrp_component_operation,mrp.group_mrp_manager,1,1,1,1
access_mrp_component_operate_user,mrp.component.operate.user,model_mrp_component_operate,mrp.group_mrp_user,1,0,0,0
access_mrp_component_operate_manager,mrp.component.operate.manager,model_mrp_component_operate,mrp.group_mrp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mrp_component_operation_user mrp.component.operation.user model_mrp_component_operation mrp.group_mrp_user 1 0 0 0
3 access_mrp_component_operation_manager mrp.component.operation.manager model_mrp_component_operation mrp.group_mrp_manager 1 1 1 1
4 access_mrp_component_operate_user mrp.component.operate.user model_mrp_component_operate mrp.group_mrp_user 1 0 0 0
5 access_mrp_component_operate_manager mrp.component.operate.manager model_mrp_component_operate mrp.group_mrp_manager 1 1 1 1

View File

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

View File

@@ -0,0 +1,322 @@
from odoo import api
from odoo.tests import common
class TestComponentOperation(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestComponentOperation, cls).setUpClass()
cls.user_admin = cls.env.ref("base.user_admin")
cls.env = api.Environment(cls.cr, cls.user_admin.id, {})
cls.ProcurementGroup = cls.env["procurement.group"]
cls.MrpProduction = cls.env["mrp.production"]
cls.env.user.company_id.manufacturing_lead = 0
cls.env.user.tz = False # Make sure there's no timezone in user
cls.picking_type = cls.env["stock.picking.type"].search(
[
("code", "=", "mrp_operation"),
("sequence_id.company_id", "=", cls.env.user.company_id.id),
],
limit=1,
)
cls.product1 = cls.env["product.product"].create(
{
"name": "TEST Parent",
"route_ids": [
(6, 0, [cls.env.ref("mrp.route_warehouse0_manufacture").id])
],
"type": "product",
"produce_delay": 0,
}
)
cls.product2 = cls.env["product.product"].create(
{"name": "TEST Child", "type": "product"}
)
cls.product3 = cls.env["product.product"].create(
{"name": "TEST Child Serial", "type": "product", "tracking": "serial"}
)
cls.bom = cls.env["mrp.bom"].create(
{
"product_id": cls.product1.id,
"product_tmpl_id": cls.product1.product_tmpl_id.id,
"type": "normal",
"bom_line_ids": [
(0, 0, {"product_id": cls.product2.id, "product_qty": 2}),
(0, 0, {"product_id": cls.product3.id, "product_qty": 1}),
],
}
)
cls.stock_picking_type = cls.env.ref("stock.picking_type_out")
cls.warehouse = cls.env["stock.warehouse"].search(
[("company_id", "=", cls.env.user.company_id.id)], limit=1
)
cls.warehouse.manufacture_steps = "pbm"
cls.ressuply_loc1 = cls.warehouse.lot_stock_id
cls.source_location = cls.env.ref("stock.stock_location_stock")
cls.destination_location = cls.env.ref("stock.stock_location_output")
stock_location_locations_virtual = cls.env["stock.location"].create(
{"name": "Virtual Locations", "usage": "view", "posz": 1}
)
cls.scrapped_location = cls.env["stock.location"].create(
{
"name": "Scrapped",
"location_id": stock_location_locations_virtual.id,
"scrap_location": True,
"usage": "inventory",
}
)
cls.source_route = cls.env["stock.location.route"].create(
{
"name": "Source Route",
"mo_component_selectable": True,
"sequence": 10,
}
)
cls.destination_route = cls.env["stock.location.route"].create(
{
"name": "Destination Route",
"mo_component_selectable": True,
"sequence": 10,
}
)
cls.env["stock.rule"].create(
{
"name": "Transfer",
"route_id": cls.source_route.id,
"location_src_id": cls.ressuply_loc1.id,
"location_id": cls.source_location.id,
"action": "pull",
"picking_type_id": cls.warehouse.int_type_id.id,
"procure_method": "make_to_stock",
"warehouse_id": cls.warehouse.id,
}
)
cls.env["stock.rule"].create(
{
"name": "Transfer 2",
"route_id": cls.destination_route.id,
"location_src_id": cls.source_location.id,
"location_id": cls.destination_location.id,
"action": "pull",
"picking_type_id": cls.warehouse.int_type_id.id,
"procure_method": "make_to_stock",
"warehouse_id": cls.warehouse.id,
"propagate_warehouse_id": cls.warehouse.id,
}
)
cls.operation_scrap_replace = cls.env["mrp.component.operation"].create(
{
"name": "Operation Scrap and Replace",
"incoming_operation": "replace",
"outgoing_operation": "scrap",
"source_location_id": cls.source_location.id,
"source_route_id": cls.source_route.id,
"scrap_location_id": cls.scrapped_location.id,
}
)
cls.operation_no = cls.env["mrp.component.operation"].create(
{
"name": "Operation Scrap and Replace",
"incoming_operation": "no",
"outgoing_operation": "no",
"source_location_id": cls.source_location.id,
}
)
cls.operation_move_replace = cls.env["mrp.component.operation"].create(
{
"name": "Operation Move",
"incoming_operation": "replace",
"outgoing_operation": "move",
"source_location_id": cls.source_location.id,
"source_route_id": cls.source_route.id,
"destination_location_id": cls.destination_location.id,
"destination_route_id": cls.destination_route.id,
}
)
def test_01_scrap_and_replace(self):
nb_product_todo = 5
serials_p2 = []
for i in range(nb_product_todo):
serials_p2.append(
self.env["stock.production.lot"].create(
{
"name": f"lot_consumed_2_{i}",
"product_id": self.product3.id,
"company_id": self.env.company.id,
}
)
)
self.env["stock.quant"]._update_available_quantity(
self.product3, self.ressuply_loc1, 1, lot_id=serials_p2[-1]
)
self.env["stock.quant"]._update_available_quantity(
self.product2, self.ressuply_loc1, 10
)
mo = self.MrpProduction.create(
{
"bom_id": self.bom.id,
"product_id": self.product1.id,
"product_qty": 2,
"product_uom_id": self.product1.uom_id.id,
"date_deadline": "2023-01-01 15:00:00",
"date_planned_start": "2023-01-01 15:00:00",
}
)
mo._onchange_move_raw()
mo._onchange_move_finished()
mo.action_confirm()
mo.action_assign()
self.assertEqual(mo.move_raw_ids[0].move_line_ids.product_uom_qty, 4)
self.assertEqual(len(mo.move_raw_ids[1].move_line_ids), 2)
wizard = self.env["mrp.component.operate"].create(
{
"product_id": mo.move_raw_ids[1].product_id.id,
"lot_id": mo.move_raw_ids[1].move_line_ids[0].lot_id.id,
"operation_id": self.operation_scrap_replace.id,
"mo_id": mo.id,
}
)
self.assertEqual(wizard.product_qty, 1)
self.assertEqual(wizard.product_id, self.product3)
lot = wizard.lot_id
wizard.action_operate_component()
self.assertEqual(len(mo.picking_ids), 1)
self.assertEqual(mo.scrap_ids.product_id, self.product3)
self.assertEqual(mo.scrap_ids.lot_id, lot)
self.assertEqual(mo.scrap_ids.state, "done")
self.assertEqual(len(mo.move_raw_ids[1].move_line_ids), 1)
self.assertEqual(len(mo.move_raw_ids[1].move_orig_ids.move_line_ids), 0)
self.assertEqual(mo.picking_ids.product_id, self.product3)
mo.picking_ids.action_assign()
mo.picking_ids.button_validate()
self.assertEqual(len(mo.move_raw_ids[1].move_line_ids), 1)
self.assertEqual(len(mo.move_raw_ids[1].move_orig_ids.move_line_ids), 1)
def test_02_move_and_replace(self):
nb_product_todo = 5
serials_p2 = []
for i in range(nb_product_todo):
serials_p2.append(
self.env["stock.production.lot"].create(
{
"name": f"lot_consumed_2_{i}",
"product_id": self.product3.id,
"company_id": self.env.company.id,
}
)
)
self.env["stock.quant"]._update_available_quantity(
self.product3, self.ressuply_loc1, 1, lot_id=serials_p2[-1]
)
self.env["stock.quant"]._update_available_quantity(
self.product2, self.ressuply_loc1, 10
)
mo = self.MrpProduction.create(
{
"bom_id": self.bom.id,
"product_id": self.product1.id,
"product_qty": 1,
"product_uom_id": self.product1.uom_id.id,
"date_deadline": "2023-01-01 15:00:00",
"date_planned_start": "2023-01-01 15:00:00",
}
)
mo._onchange_move_raw()
mo._onchange_move_finished()
mo.action_confirm()
mo.action_assign()
self.assertEqual(mo.move_raw_ids[0].move_line_ids.product_uom_qty, 2)
self.assertEqual(len(mo.move_raw_ids[1].move_line_ids), 1)
wizard = self.env["mrp.component.operate"].create(
{
"product_id": mo.move_raw_ids[1].product_id.id,
"lot_id": mo.move_raw_ids[1].move_line_ids[0].lot_id.id,
"operation_id": self.operation_move_replace.id,
"mo_id": mo.id,
}
)
self.assertEqual(wizard.product_qty, 1)
self.assertEqual(wizard.product_id, self.product3)
lot = wizard.lot_id
wizard.action_operate_component()
self.assertEqual(len(mo.picking_ids), 2)
self.assertEqual(mo.picking_ids[1].location_dest_id, self.destination_location)
self.assertEqual(mo.picking_ids[1].move_lines.product_id, self.product3)
self.assertEqual(mo.picking_ids[0].location_dest_id, self.source_location)
self.assertEqual(mo.picking_ids[0].move_lines.product_id, self.product3)
self.assertEqual(len(mo.move_raw_ids[1].move_line_ids), 0)
self.assertEqual(len(mo.move_raw_ids[1].move_orig_ids.move_line_ids), 0)
mo.picking_ids[1].action_assign()
mo.picking_ids[1].button_validate()
self.assertEqual(
mo.picking_ids[1].move_line_ids.location_dest_id, self.destination_location
)
self.assertEqual(mo.picking_ids[1].move_line_ids.lot_id, lot)
self.assertEqual(len(mo.move_raw_ids[1].move_line_ids), 0)
self.assertEqual(len(mo.move_raw_ids[1].move_orig_ids.move_line_ids), 0)
mo.picking_ids[0].action_assign()
mo.picking_ids[0].button_validate()
self.assertEqual(
mo.picking_ids[0].move_line_ids.location_dest_id, self.source_location
)
self.assertEqual(mo.picking_ids[0].move_line_ids.product_id, self.product3)
self.assertEqual(len(mo.move_raw_ids[1].move_line_ids), 0)
self.assertEqual(len(mo.move_raw_ids[1].move_orig_ids.move_line_ids), 1)
def test_03_nothing_and_nothing(self):
nb_product_todo = 5
serials_p2 = []
for i in range(nb_product_todo):
serials_p2.append(
self.env["stock.production.lot"].create(
{
"name": f"lot_consumed_2_{i}",
"product_id": self.product3.id,
"company_id": self.env.company.id,
}
)
)
self.env["stock.quant"]._update_available_quantity(
self.product3, self.ressuply_loc1, 1, lot_id=serials_p2[-1]
)
self.env["stock.quant"]._update_available_quantity(
self.product2, self.ressuply_loc1, 10
)
mo = self.MrpProduction.create(
{
"bom_id": self.bom.id,
"product_id": self.product1.id,
"product_qty": 2,
"product_uom_id": self.product1.uom_id.id,
"date_deadline": "2023-01-01 15:00:00",
"date_planned_start": "2023-01-01 15:00:00",
}
)
mo._onchange_move_raw()
mo._onchange_move_finished()
mo.action_confirm()
mo.action_assign()
self.assertEqual(mo.move_raw_ids[0].move_line_ids.product_uom_qty, 4)
self.assertEqual(len(mo.move_raw_ids[1].move_line_ids), 2)
wizard = self.env["mrp.component.operate"].create(
{
"product_id": mo.move_raw_ids[1].product_id.id,
"lot_id": mo.move_raw_ids[1].move_line_ids[0].lot_id.id,
"operation_id": self.operation_no.id,
"mo_id": mo.id,
}
)
self.assertEqual(wizard.product_qty, 1)
self.assertEqual(wizard.product_id, self.product3)
wizard.action_operate_component()
self.assertEqual(len(mo.picking_ids), 0)
self.assertEqual(mo.move_raw_ids[0].move_line_ids.product_uom_qty, 4)
self.assertEqual(len(mo.move_raw_ids[1].move_line_ids), 2)

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_mrp_component_operation_form" model="ir.ui.view">
<field name="name">view_mrp_component_operation_form</field>
<field name="model">mrp.component.operation</field>
<field name="arch" type="xml">
<form string="Component Operation">
<sheet>
<h1>
<field name="name" string="Reference" />
</h1>
<group name="operations" string="Operations">
<field name="outgoing_operation" />
<field name="incoming_operation" />
</group>
<group
name="locations"
string="Locations/Routes"
attrs="{'invisible': [('outgoing_operation', '=', 'no'),('incoming_operation', '=', 'no')]}"
>
<field
name="source_location_id"
attrs="{'invisible': [('incoming_operation', '!=', 'replace'),('outgoing_operation', '!=', 'scrap')], 'required': ['|',('incoming_operation', '=', 'replace'),('outgoing_operation', '=', 'scrap')]}"
/>
<field
name="source_route_id"
attrs="{'invisible': [('incoming_operation', '!=', 'replace')], 'required': [('incoming_operation', '=', 'replace')]}"
/>
<field
name="destination_location_id"
attrs="{'invisible': [('outgoing_operation', '!=', 'move')], 'required': [('outgoing_operation', '=', 'move')]}"
/>
<field
name="destination_route_id"
attrs="{'invisible': [('outgoing_operation', '!=', 'move')], 'required': [('outgoing_operation', '=', 'move')]}"
/>
<field
name="scrap_location_id"
attrs="{'invisible': [('outgoing_operation', '!=', 'scrap')], 'required': [('outgoing_operation', '=', 'scrap')]}"
/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_menu_mrp_component_operation" model="ir.actions.act_window">
<field name="name">Component Operation</field>
<field name="res_model">mrp.component.operation</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem
id="menu_mrp_component_operation"
name="Component Operations"
action="action_menu_mrp_component_operation"
parent="mrp.menu_mrp_configuration"
sequence="6"
/>
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="mrp_production_form_view" model="ir.ui.view">
<field name="name">mrp.production.form</field>
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view" />
<field name="arch" type="xml">
<button name="button_scrap" position="after">
<button
name="button_operate_components"
type="object"
string="Operate Components"
attrs="{'invisible': [('state', 'in', ('cancel', 'draft', 'done'))]}"
/>
</button>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record
id="stock_location_route_form_view_inherit_mrp_component"
model="ir.ui.view"
>
<field name="name">stock.location.route.form - mrp.component</field>
<field name="inherit_id" ref="stock.stock_location_route_form_view" />
<field name="model">stock.location.route</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='warehouse_selectable']" position="before">
<field name="mo_component_selectable" string="Component Operations" />
</xpath>
</field>
</record>
</odoo>

View File

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

View File

@@ -0,0 +1,160 @@
# Copyright 2022 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 UserError, ValidationError
class MrpComponentOperate(models.Model):
_name = "mrp.component.operate"
_description = "Component Operate"
product_id = fields.Many2one("product.product", required=True)
tracking = fields.Selection(
string="Product Tracking", readonly=True, related="product_id.tracking"
)
product_qty = fields.Float(
"Quantity", default=1.0, required=True, digits="Product Unit of Measure"
)
lot_id = fields.Many2one("stock.production.lot")
mo_id = fields.Many2one("mrp.production", ondelete="cascade", required=True)
operation_id = fields.Many2one("mrp.component.operation", required=True)
incoming_operation = fields.Selection(
related="operation_id.incoming_operation",
required=True,
)
outgoing_operation = fields.Selection(
related="operation_id.outgoing_operation",
required=True,
)
@api.onchange("operation_id")
def _onchange_operation_id(self):
for rec in self:
rec.incoming_operation = rec.operation_id.incoming_operation
rec.outgoing_operation = rec.operation_id.outgoing_operation
def _run_incoming_operations(self):
res = []
if self.incoming_operation == "replace":
res = self._run_procurement(
self.operation_id.source_route_id, self.operation_id.source_location_id
)
move = self.mo_id.move_raw_ids.filtered(
lambda x: x.product_id == self.product_id
)
filtered_pickings = self.mo_id.picking_ids.filtered(
lambda x: x.location_dest_id == self.operation_id.source_location_id
)
move.move_orig_ids |= filtered_pickings[
len(filtered_pickings) - 1
].move_ids_without_package
elif self.incoming_operation == "no":
res = []
return res
def _run_outgoing_operations(self):
res = []
if self.outgoing_operation == "scrap":
res = self._create_scrap()
elif self.outgoing_operation == "move":
res = self._run_procurement(
self.operation_id.destination_route_id,
self.operation_id.destination_location_id,
)
move = self.mo_id.move_raw_ids.move_line_ids.filtered(
lambda x: x.product_id == self.product_id
and (x.lot_id == self.lot_id or self.lot_id is False)
)
if move.product_uom_qty == self.product_qty:
move.unlink()
else:
move.write(
{
"product_uom_qty": (move.product_uom_qty - self.product_qty),
}
)
move.move_id._recompute_state()
elif self.outgoing_operation == "no":
res = []
return res
def _create_scrap(self):
scrap = self.env["stock.scrap"].create(
{
"origin": self.mo_id.name,
"product_id": self.product_id.id,
"lot_id": self.lot_id.id,
"scrap_qty": self.product_qty,
"product_uom_id": self.product_id.product_tmpl_id.uom_id.id,
"location_id": self.operation_id.source_location_id.id,
"scrap_location_id": self.operation_id.scrap_location_id.id,
"create_date": fields.Datetime.now(),
"company_id": self.env.company.id,
}
)
self.mo_id.scrap_ids |= scrap
scrap.action_validate()
return scrap
def _run_procurement(self, route, dest_location):
"""Method called when the user clicks on create picking"""
procurements = []
errors = []
procurement = self._prepare_procurement(route, dest_location)
procurements.append(procurement)
try:
self.env["procurement.group"].run(procurements)
except UserError as error:
errors.append(error.args[0])
if errors:
raise UserError("\n".join(errors))
return procurements
@api.model
def _get_procurement_data(self, route, dest_location):
if not route:
raise ValidationError(_("No route specified"))
procurement_data = {
"name": self.mo_id and self.mo_id.name,
"group_id": self.mo_id.procurement_group_id,
"origin": self.mo_id.name,
"date_planned": fields.Datetime.now(),
"product_id": self.product_id.id,
"product_qty": self.product_qty,
"product_uom": self.product_id.product_tmpl_id.uom_id.id,
"location_id": dest_location.id,
"route_ids": route,
"company_id": self.env.company.id,
"mrp_production_ids": self.mo_id.id,
}
if self.lot_id and route != self.operation_id.source_route_id:
procurement_data["lot_id"] = self.lot_id.id
return procurement_data
@api.model
def _prepare_procurement(self, route, dest_location):
values = self._get_procurement_data(route, dest_location)
procurement = self.env["procurement.group"].Procurement(
self.product_id,
self.product_qty,
self.product_id.product_tmpl_id.uom_id,
dest_location,
values.get("origin"),
values.get("origin"),
self.env.company,
values,
)
return procurement
def action_operate_component(self):
self._run_outgoing_operations()
self._run_incoming_operations()
return

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_mrp_component_operate_form" model="ir.ui.view">
<field name="name">view_mrp_component_operate_form</field>
<field name="model">mrp.component.operate</field>
<field name="arch" type="xml">
<form string="Component Operation">
<sheet>
<group>
<field
name="product_id"
options="{'no_create': True}"
domain="[('id', 'in', context.get('product_ids', []))]"
/>
<field
name="lot_id"
string="Lot/Serial Number"
attrs="{'required': [('tracking', '!=', 'none')]}"
domain="[('product_id', '=', product_id), ('id', 'in', context.get('lot_ids', []))]"
/>
<field
name="product_qty"
attrs="{'readonly': ['|',('tracking', '=', 'serial'),('product_id', '=', False)]}"
/>
<field name="operation_id" />
<field name="tracking" invisible="1" />
</group>
<group
name="operations"
string="Operations"
attrs="{'invisible': [('operation_id', '=', False)]}"
>
<field name="outgoing_operation" />
<field name="incoming_operation" />
</group>
</sheet>
<footer>
<button
name="action_operate_component"
string="Done"
type="object"
class="btn-primary"
/>
<button string="Cancel" class="btn-secondary" special="cancel" />
</footer>
</form>
</field>
</record>
</odoo>