mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[14.0][ADD] mrp_unbuild_subcontracting: unbuild created when is returned a product subcontracted
This commit is contained in:
0
mrp_unbuild_subcontracting/README.rst
Normal file
0
mrp_unbuild_subcontracting/README.rst
Normal file
1
mrp_unbuild_subcontracting/__init__.py
Normal file
1
mrp_unbuild_subcontracting/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
15
mrp_unbuild_subcontracting/__manifest__.py
Normal file
15
mrp_unbuild_subcontracting/__manifest__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
{
|
||||
"name": "Unbuild orders with return subcontracting",
|
||||
"version": "14.0.1.0.0",
|
||||
"license": "LGPL-3",
|
||||
"category": "Manufacture",
|
||||
"summary": "Unbuild orders are created automatically "
|
||||
"when is returned a product subcontracted",
|
||||
"author": "ForgeFlow, Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"depends": ["mrp_account", "mrp_subcontracting"],
|
||||
"data": ["views/mrp_unbuild_views.xml"],
|
||||
"installable": True,
|
||||
}
|
||||
3
mrp_unbuild_subcontracting/models/__init__.py
Normal file
3
mrp_unbuild_subcontracting/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import stock_picking
|
||||
from . import mrp_unbuild
|
||||
from . import stock_move
|
||||
8
mrp_unbuild_subcontracting/models/mrp_unbuild.py
Normal file
8
mrp_unbuild_subcontracting/models/mrp_unbuild.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpUnbuild(models.Model):
|
||||
_inherit = "mrp.unbuild"
|
||||
|
||||
picking_id = fields.Many2one("stock.picking", "Transfer", readonly=True)
|
||||
is_subcontracted = fields.Boolean("Is Subcontracted", readonly=True)
|
||||
57
mrp_unbuild_subcontracting/models/stock_move.py
Normal file
57
mrp_unbuild_subcontracting/models/stock_move.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import _, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools.float_utils import float_is_zero
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
def _action_confirm(self, merge=True, merge_into=False):
|
||||
if self.origin_returned_move_id:
|
||||
subcontract_details_per_picking = defaultdict(list)
|
||||
move_to_not_merge = self.env["stock.move"]
|
||||
for move in self:
|
||||
if (
|
||||
move.location_dest_id.usage == "supplier"
|
||||
and move.location_id
|
||||
== self.picking_id.picking_type_id.default_location_src_id
|
||||
):
|
||||
continue
|
||||
if move.move_orig_ids.production_id:
|
||||
continue
|
||||
bom = move._get_subcontract_bom()
|
||||
if not bom:
|
||||
continue
|
||||
if (
|
||||
float_is_zero(
|
||||
move.product_qty, precision_rounding=move.product_uom.rounding
|
||||
)
|
||||
and move.picking_id.immediate_transfer is True
|
||||
):
|
||||
raise UserError(_("To subcontract, use a planned transfer."))
|
||||
subcontract_details_per_picking[move.picking_id].append((move, bom))
|
||||
move.write(
|
||||
{
|
||||
"is_subcontract": True,
|
||||
}
|
||||
)
|
||||
move_to_not_merge |= move
|
||||
for picking, subcontract_details in subcontract_details_per_picking.items():
|
||||
picking._subcontracted_produce_unbuild(subcontract_details)
|
||||
|
||||
# We avoid merging move due to complication with stock.rule.
|
||||
res = super(StockMove, move_to_not_merge)._action_confirm(merge=False)
|
||||
res |= super(StockMove, self - move_to_not_merge)._action_confirm(
|
||||
merge=merge, merge_into=merge_into
|
||||
)
|
||||
if subcontract_details_per_picking:
|
||||
self.env["stock.picking"].concat(
|
||||
*list(subcontract_details_per_picking.keys())
|
||||
).action_assign()
|
||||
return res
|
||||
result = super(StockMove, self)._action_confirm(
|
||||
merge=merge, merge_into=merge_into
|
||||
)
|
||||
return result
|
||||
96
mrp_unbuild_subcontracting/models/stock_picking.py
Normal file
96
mrp_unbuild_subcontracting/models/stock_picking.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo.osv.expression import OR
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = "stock.picking"
|
||||
|
||||
subcontracted_unbuild_ids = fields.One2many(
|
||||
"mrp.unbuild", "picking_id", readonly=True, string="Suncontracted unbuilds"
|
||||
)
|
||||
|
||||
def _prepare_subcontract_unbuild_vals(self, subcontract_move, bom):
|
||||
subcontract_move.ensure_one()
|
||||
product = subcontract_move.product_id
|
||||
vals = {
|
||||
"company_id": subcontract_move.company_id.id,
|
||||
"product_id": product.id,
|
||||
"product_uom_id": subcontract_move.product_uom.id,
|
||||
"bom_id": bom.id,
|
||||
"location_id": subcontract_move.picking_id.partner_id.with_company(
|
||||
subcontract_move.company_id
|
||||
).property_stock_subcontractor.id,
|
||||
"location_dest_id": subcontract_move.picking_id.partner_id.with_company(
|
||||
subcontract_move.company_id
|
||||
).property_stock_subcontractor.id,
|
||||
"product_qty": subcontract_move.product_uom_qty,
|
||||
"picking_id": self.id,
|
||||
"is_subcontracted": True,
|
||||
"mo_id": subcontract_move.move_orig_ids.move_orig_ids.production_id.id,
|
||||
"lot_id": subcontract_move.move_orig_ids.lot_ids.id,
|
||||
}
|
||||
return vals
|
||||
|
||||
def _subcontracted_produce_unbuild(self, subcontract_details):
|
||||
self.ensure_one()
|
||||
for move, bom in subcontract_details:
|
||||
unbuild = (
|
||||
self.env["mrp.unbuild"]
|
||||
.with_company(move.company_id)
|
||||
.create(self._prepare_subcontract_unbuild_vals(move, bom))
|
||||
)
|
||||
self.subcontracted_unbuild_ids |= unbuild
|
||||
|
||||
def _action_done(self):
|
||||
res = super(StockPicking, self)._action_done()
|
||||
for picking in self:
|
||||
unbuilds_to_done = picking.subcontracted_unbuild_ids.filtered(
|
||||
lambda x: x.state == "draft"
|
||||
)
|
||||
if not unbuilds_to_done:
|
||||
continue
|
||||
unbuild_ids_backorder = []
|
||||
if not self.env.context.get("cancel_backorder"):
|
||||
unbuild_ids_backorder = unbuilds_to_done.filtered(
|
||||
lambda u: u.state == "draft"
|
||||
).ids
|
||||
unbuilds_to_done.with_context(
|
||||
subcontract_move_id=True, mo_ids_to_backorder=unbuild_ids_backorder
|
||||
).action_validate()
|
||||
move = self.move_lines.filtered(lambda move: move.is_subcontract)
|
||||
finished_move = unbuilds_to_done.produce_line_ids.filtered(
|
||||
lambda m: m.product_id == move.product_id
|
||||
)
|
||||
finished_move.write({"move_dest_ids": [(4, move.id, False)]})
|
||||
# For concistency, set the date on production move before the date
|
||||
# on picking. (Traceability report + Product Moves menu item)
|
||||
minimum_date = min(picking.move_line_ids.mapped("date"))
|
||||
unbuild_moves = (
|
||||
unbuilds_to_done.produce_line_ids | unbuilds_to_done.consume_line_ids
|
||||
)
|
||||
unbuild_moves.write({"date": minimum_date - timedelta(seconds=1)})
|
||||
unbuild_moves.move_line_ids.write(
|
||||
{"date": minimum_date - timedelta(seconds=1)}
|
||||
)
|
||||
return res
|
||||
|
||||
def action_view_stock_valuation_layers(self):
|
||||
action = super(StockPicking, self).action_view_stock_valuation_layers()
|
||||
subcontracted_unbuilds = self.subcontracted_unbuild_ids
|
||||
if not subcontracted_unbuilds:
|
||||
return action
|
||||
domain = action["domain"]
|
||||
domain_subcontracting = [
|
||||
(
|
||||
"id",
|
||||
"in",
|
||||
(
|
||||
subcontracted_unbuilds.produce_line_ids
|
||||
| subcontracted_unbuilds.consume_line_ids
|
||||
).stock_valuation_layer_ids.ids,
|
||||
)
|
||||
]
|
||||
domain = OR([domain, domain_subcontracting])
|
||||
return dict(action, domain=domain)
|
||||
3
mrp_unbuild_subcontracting/readme/CONTRIBUTORS.rst
Normal file
3
mrp_unbuild_subcontracting/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,3 @@
|
||||
* `ForgeFlow <https://www.forgeflow.com>`_:
|
||||
|
||||
* Thiago Mulero <thiago.mulero@forgeflow.com>
|
||||
2
mrp_unbuild_subcontracting/readme/DESCRIPTION.rst
Normal file
2
mrp_unbuild_subcontracting/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1,2 @@
|
||||
This module automatically creates a unbuild in draft state when a subcontracting picking return is created. In addition, when the picking is validated, the unbuild is also validated.
|
||||
To view the unbuilds created, you have to select the operation Subcontracted Unbuild Orders in debug mode
|
||||
BIN
mrp_unbuild_subcontracting/static/description/icon.png
Normal file
BIN
mrp_unbuild_subcontracting/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
1
mrp_unbuild_subcontracting/tests/__init__.py
Normal file
1
mrp_unbuild_subcontracting/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_mrp_unbuild_subcontracting
|
||||
@@ -0,0 +1,332 @@
|
||||
from odoo.tests import Form, TransactionCase
|
||||
|
||||
|
||||
class TestSubcontractingPurchaseFlows(TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.subcontractor = self.env["res.partner"].create(
|
||||
{"name": "SuperSubcontractor"}
|
||||
)
|
||||
|
||||
self.finished, self.compo = self.env["product.product"].create(
|
||||
[
|
||||
{
|
||||
"name": "SuperProduct",
|
||||
"type": "product",
|
||||
},
|
||||
{
|
||||
"name": "Component",
|
||||
"type": "consu",
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
self.bom = self.env["mrp.bom"].create(
|
||||
{
|
||||
"product_tmpl_id": self.finished.product_tmpl_id.id,
|
||||
"type": "subcontract",
|
||||
"subcontractor_ids": [(6, 0, self.subcontractor.ids)],
|
||||
"bom_line_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"product_id": self.compo.id,
|
||||
"product_qty": 1,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
def test_purchase_and_return(self):
|
||||
"""
|
||||
The user buys 10 x a subcontracted product P. He receives the 10
|
||||
products and then does a return with 3 x P. The test ensures that
|
||||
the unbuild is created with the correct quantities and states
|
||||
"""
|
||||
po = self.env["purchase.order"].create(
|
||||
{
|
||||
"partner_id": self.subcontractor.id,
|
||||
"order_line": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": self.finished.name,
|
||||
"product_id": self.finished.id,
|
||||
"product_uom_qty": 10,
|
||||
"product_uom": self.finished.uom_id.id,
|
||||
"price_unit": 1,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
po.button_confirm()
|
||||
|
||||
mo = self.env["mrp.production"].search([("bom_id", "=", self.bom.id)])
|
||||
self.assertTrue(mo)
|
||||
|
||||
receipt = po.picking_ids
|
||||
receipt.move_lines.quantity_done = 10
|
||||
receipt.button_validate()
|
||||
|
||||
return_form = Form(
|
||||
self.env["stock.return.picking"].with_context(
|
||||
active_id=receipt.id, active_model="stock.picking"
|
||||
)
|
||||
)
|
||||
with return_form.product_return_moves.edit(0) as line:
|
||||
line.quantity = 3
|
||||
line.to_refund = True
|
||||
return_wizard = return_form.save()
|
||||
return_id, _ = return_wizard._create_returns()
|
||||
|
||||
return_picking = self.env["stock.picking"].browse(return_id)
|
||||
return_picking.move_lines.quantity_done = 3
|
||||
subcontractor_location = self.subcontractor.property_stock_subcontractor
|
||||
unbuild = self.env["mrp.unbuild"].search([("bom_id", "=", self.bom.id)])
|
||||
|
||||
self.assertTrue(unbuild)
|
||||
self.assertEqual(
|
||||
unbuild.state, "draft", "The state of the unbuild should be draft"
|
||||
)
|
||||
self.assertEqual(
|
||||
unbuild.product_qty, 3, "The quantity of the unbuild should be 3"
|
||||
)
|
||||
self.assertEqual(
|
||||
unbuild.location_id,
|
||||
subcontractor_location,
|
||||
"The source location of the unbuild should be the property stock "
|
||||
"of the subcontractor",
|
||||
)
|
||||
self.assertEqual(
|
||||
unbuild.location_dest_id,
|
||||
subcontractor_location,
|
||||
"The destination location of the unbuild should be the property "
|
||||
"stock of the subcontractor",
|
||||
)
|
||||
|
||||
return_picking.button_validate()
|
||||
|
||||
self.assertEqual(self.finished.qty_available, 7.0)
|
||||
self.assertEqual(po.order_line.qty_received, 7.0)
|
||||
self.assertEqual(
|
||||
unbuild.state, "done", "The state of the unbuild should be done"
|
||||
)
|
||||
|
||||
move = return_picking.move_lines
|
||||
self.assertEqual(
|
||||
move.location_id,
|
||||
receipt.location_dest_id,
|
||||
"The source location of the stock move should be the same as "
|
||||
"destination location of the original purchase",
|
||||
)
|
||||
self.assertEqual(
|
||||
move.location_dest_id,
|
||||
subcontractor_location,
|
||||
"The destination location of the stock move should be the property "
|
||||
"stock of the subcontractor",
|
||||
)
|
||||
|
||||
# Call the action to view the layers associated to the pickings
|
||||
result1 = return_picking.action_view_stock_valuation_layers()
|
||||
result2 = receipt.action_view_stock_valuation_layers()
|
||||
layers1 = result1["domain"][2][2]
|
||||
layers2 = result2["domain"][2][2]
|
||||
self.assertTrue(
|
||||
layers1,
|
||||
)
|
||||
self.assertTrue(
|
||||
layers2,
|
||||
)
|
||||
|
||||
|
||||
class TestSubcontractingTracking(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestSubcontractingTracking, self).setUp()
|
||||
# 1: Create a subcontracting partner
|
||||
main_company_1 = self.env["res.partner"].create({"name": "main_partner"})
|
||||
self.subcontractor_partner1 = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Subcontractor 1",
|
||||
"parent_id": main_company_1.id,
|
||||
"company_id": self.env.ref("base.main_company").id,
|
||||
}
|
||||
)
|
||||
|
||||
# 2. Create a BOM of subcontracting type
|
||||
# 2.1. Comp1 has tracking by lot
|
||||
self.comp1_sn = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Component1",
|
||||
"type": "product",
|
||||
"categ_id": self.env.ref("product.product_category_all").id,
|
||||
"tracking": "serial",
|
||||
}
|
||||
)
|
||||
self.comp2 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Component2",
|
||||
"type": "product",
|
||||
"categ_id": self.env.ref("product.product_category_all").id,
|
||||
}
|
||||
)
|
||||
|
||||
# 2.2. Finished prodcut has tracking by serial number
|
||||
self.finished_product = self.env["product.product"].create(
|
||||
{
|
||||
"name": "finished",
|
||||
"type": "product",
|
||||
"categ_id": self.env.ref("product.product_category_all").id,
|
||||
"tracking": "lot",
|
||||
}
|
||||
)
|
||||
bom_form = Form(self.env["mrp.bom"])
|
||||
bom_form.type = "subcontract"
|
||||
bom_form.subcontractor_ids.add(self.subcontractor_partner1)
|
||||
bom_form.product_tmpl_id = self.finished_product.product_tmpl_id
|
||||
with bom_form.bom_line_ids.new() as bom_line:
|
||||
bom_line.product_id = self.comp1_sn
|
||||
bom_line.product_qty = 1
|
||||
with bom_form.bom_line_ids.new() as bom_line:
|
||||
bom_line.product_id = self.comp2
|
||||
bom_line.product_qty = 1
|
||||
self.bom_tracked = bom_form.save()
|
||||
|
||||
def test_purchase_and_return_with_serial_numbers(self):
|
||||
"""
|
||||
The user buys one subcontracted product P with serial number.
|
||||
Then does the return . The test ensures that the unbuild is
|
||||
created with the correct quantities, serial number of the product and states
|
||||
"""
|
||||
# Create a receipt picking from the subcontractor
|
||||
picking_form = Form(self.env["stock.picking"])
|
||||
picking_form.picking_type_id = self.env.ref("stock.picking_type_in")
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished_product
|
||||
move.product_uom_qty = 1
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_confirm()
|
||||
|
||||
# We should be able to call the 'record_components' button
|
||||
self.assertTrue(picking_receipt.display_action_record_components)
|
||||
|
||||
# Check the created manufacturing order
|
||||
mo = self.env["mrp.production"].search([("bom_id", "=", self.bom_tracked.id)])
|
||||
self.assertEqual(len(mo), 1)
|
||||
self.assertEqual(len(mo.picking_ids), 0)
|
||||
wh = picking_receipt.picking_type_id.warehouse_id
|
||||
self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
|
||||
self.assertFalse(mo.picking_type_id.active)
|
||||
|
||||
# Create a RR
|
||||
pg1 = self.env["procurement.group"].create({})
|
||||
self.env["stock.warehouse.orderpoint"].create(
|
||||
{
|
||||
"name": "xxx",
|
||||
"product_id": self.comp1_sn.id,
|
||||
"product_min_qty": 0,
|
||||
"product_max_qty": 0,
|
||||
"location_id": self.env.user.company_id.subcontracting_location_id.id,
|
||||
"group_id": pg1.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Run the scheduler and check the created picking
|
||||
self.env["procurement.group"].run_scheduler()
|
||||
picking = self.env["stock.picking"].search([("group_id", "=", pg1.id)])
|
||||
self.assertEqual(len(picking), 1)
|
||||
self.assertEqual(picking.picking_type_id, wh.out_type_id)
|
||||
|
||||
lot_id = self.env["stock.production.lot"].create(
|
||||
{
|
||||
"name": "lot1",
|
||||
"product_id": self.finished_product.id,
|
||||
"company_id": self.env.company.id,
|
||||
}
|
||||
)
|
||||
serial_id = self.env["stock.production.lot"].create(
|
||||
{
|
||||
"name": "lot1",
|
||||
"product_id": self.comp1_sn.id,
|
||||
"company_id": self.env.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
action = picking_receipt.action_record_components()
|
||||
mo = self.env["mrp.production"].browse(action["res_id"])
|
||||
mo_form = Form(mo.with_context(**action["context"]), view=action["view_id"])
|
||||
mo_form.qty_producing = 1
|
||||
mo_form.lot_producing_id = lot_id
|
||||
with mo_form.move_line_raw_ids.edit(0) as ml:
|
||||
ml.lot_id = serial_id
|
||||
mo = mo_form.save()
|
||||
mo.subcontracting_record_component()
|
||||
|
||||
# We should not be able to call the 'record_components' button
|
||||
self.assertFalse(picking_receipt.display_action_record_components)
|
||||
|
||||
picking_receipt.button_validate()
|
||||
self.assertEqual(mo.state, "done")
|
||||
|
||||
return_form = Form(
|
||||
self.env["stock.return.picking"].with_context(
|
||||
active_id=picking_receipt.id, active_model="stock.picking"
|
||||
)
|
||||
)
|
||||
with return_form.product_return_moves.edit(0) as line:
|
||||
line.quantity = 1
|
||||
line.to_refund = True
|
||||
return_wizard = return_form.save()
|
||||
return_id, _ = return_wizard._create_returns()
|
||||
|
||||
return_picking = self.env["stock.picking"].browse(return_id)
|
||||
return_picking.move_lines.quantity_done = 1
|
||||
subcontractor_location = (
|
||||
self.subcontractor_partner1.property_stock_subcontractor
|
||||
)
|
||||
unbuild = self.env["mrp.unbuild"].search([("bom_id", "=", self.bom_tracked.id)])
|
||||
|
||||
self.assertTrue(unbuild)
|
||||
self.assertEqual(
|
||||
unbuild.state, "draft", "The state of the unbuild should be draft"
|
||||
)
|
||||
self.assertEqual(
|
||||
unbuild.product_qty, 1, "The quantity of the unbuild should be 1"
|
||||
)
|
||||
self.assertEqual(
|
||||
unbuild.location_id,
|
||||
subcontractor_location,
|
||||
"The source location of the unbuild should be the property stock "
|
||||
"of the subcontractor",
|
||||
)
|
||||
self.assertEqual(
|
||||
unbuild.location_dest_id,
|
||||
subcontractor_location,
|
||||
"The destination location of the unbuild should be the property "
|
||||
"stock of the subcontractor",
|
||||
)
|
||||
return_picking.move_line_ids_without_package.lot_id = lot_id
|
||||
return_picking.button_validate()
|
||||
|
||||
self.assertEqual(
|
||||
unbuild.state, "done", "The state of the unbuild should be done"
|
||||
)
|
||||
|
||||
move = return_picking.move_lines
|
||||
self.assertEqual(
|
||||
move.location_id,
|
||||
picking_receipt.location_dest_id,
|
||||
"The source location of the stock move should be the same as "
|
||||
"destination location of the original purchase",
|
||||
)
|
||||
self.assertEqual(
|
||||
move.location_dest_id,
|
||||
subcontractor_location,
|
||||
"The destination location of the stock move should be the property "
|
||||
"stock of the subcontractor",
|
||||
)
|
||||
30
mrp_unbuild_subcontracting/views/mrp_unbuild_views.xml
Normal file
30
mrp_unbuild_subcontracting/views/mrp_unbuild_views.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record model="ir.actions.act_window" id="mrp.mrp_unbuild">
|
||||
<field name="domain">[('is_subcontracted', '=', False)]</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="mrp_unbuild_subcontracted">
|
||||
<field name="name">Unbuild Orders - Subcontracted</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">mrp.unbuild</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="domain">[('is_subcontracted', '=', True)]</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No unbuild order found
|
||||
</p><p>
|
||||
An unbuild order is used to break down a finished product into its components.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_mrp_unbuild_subcontracted"
|
||||
name="Subcontracted Unbuild Orders"
|
||||
parent="mrp.menu_mrp_manufacturing"
|
||||
action="mrp_unbuild_subcontracted"
|
||||
sequence="21"
|
||||
groups="base.group_no_one"
|
||||
/>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user