mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
ADD module mrp_unbuild_tracked_raw_material
This commit is contained in:
5
mrp_unbuild_tracked_raw_material/README.rst
Normal file
5
mrp_unbuild_tracked_raw_material/README.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
1
mrp_unbuild_tracked_raw_material/__init__.py
Normal file
1
mrp_unbuild_tracked_raw_material/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
19
mrp_unbuild_tracked_raw_material/__manifest__.py
Normal file
19
mrp_unbuild_tracked_raw_material/__manifest__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright (C) 2019 Akretion (http://www.akretion.com). All Rights Reserved
|
||||
# @author David BEAL <david.beal@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
"name": "Mrp Unbuild Tracked Raw Material",
|
||||
"summary": "Allow to unbuild tracked purchased products",
|
||||
"author": "Akretion, Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"category": "Manufacturing",
|
||||
"version": "12.0.1.0.0",
|
||||
"license": "AGPL-3",
|
||||
"depends": ["mrp"],
|
||||
"maintainers": ["bealdav"],
|
||||
"data": [
|
||||
"views/product_view.xml",
|
||||
],
|
||||
"installable": True,
|
||||
}
|
||||
2
mrp_unbuild_tracked_raw_material/models/__init__.py
Normal file
2
mrp_unbuild_tracked_raw_material/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import product
|
||||
from . import unbuild
|
||||
14
mrp_unbuild_tracked_raw_material/models/product.py
Normal file
14
mrp_unbuild_tracked_raw_material/models/product.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright (C) 2019 Akretion (http://www.akretion.com). All Rights Reserved
|
||||
# @author David BEAL <david.beal@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
allow_unbuild_purchased = fields.Boolean(
|
||||
help="If checked, unbuild orders doesn't assume a previous "
|
||||
"manufacturing order have built this product.\n"
|
||||
"In this case it's a purchased product and you want unbuild it")
|
||||
117
mrp_unbuild_tracked_raw_material/models/unbuild.py
Normal file
117
mrp_unbuild_tracked_raw_material/models/unbuild.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# Copyright (C) 2019 Akretion (http://www.akretion.com). All Rights Reserved
|
||||
# @author David BEAL <david.beal@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from odoo import _, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MESSAGE = "Some of your components are tracked, you have to specify " \
|
||||
"a manufacturing order in order to retrieve " \
|
||||
"the correct components."
|
||||
ALTER_MESSAGE = "Alternatively, you may unbuild '%s' tracked product " \
|
||||
"if you set it as '%s'.\n" \
|
||||
"In this case lots'll be automatically created for you."
|
||||
|
||||
|
||||
class MrpUnbuild(models.Model):
|
||||
_inherit = "mrp.unbuild"
|
||||
|
||||
def action_unbuild(self):
|
||||
""" We need to catch raise behavior when tracked products
|
||||
are unbuild without an original manufacturing order and
|
||||
go on with another workflow with
|
||||
_bypass_tracked_product_without_mo()
|
||||
"""
|
||||
try:
|
||||
res = super().action_unbuild()
|
||||
except UserError as err:
|
||||
# Unbuild is impossible because of MESSAGE
|
||||
# original condition of this raise is :
|
||||
# any(produce_move.has_tracking != 'none'
|
||||
# and not self.mo_id for produce_move in produce_moves)
|
||||
if hasattr(err, 'name') and err.name == _(MESSAGE):
|
||||
if self.product_id.allow_unbuild_purchased:
|
||||
# In this case it becomes possible to unbuild
|
||||
return self._bypass_tracked_product_without_mo()
|
||||
# Here other option to resolve unbuild conditions
|
||||
new_message = _(ALTER_MESSAGE) % (
|
||||
self.product_id.name, _("Unbuild Purchased"))
|
||||
# We teach user the 2 conditions that make unbuild possible
|
||||
raise UserError(_("%s \n\n%s") % (err.name, new_message))
|
||||
# Here is the Odoo native raise
|
||||
raise err
|
||||
return res
|
||||
|
||||
def _bypass_tracked_product_without_mo(self):
|
||||
# These moves are already generated with call to
|
||||
# super().action_unbuild(). We catch them
|
||||
consume_move = self.env["stock.move"].search(
|
||||
[("consume_unbuild_id", "=", self.id)])
|
||||
produce_moves = self.env["stock.move"].search(
|
||||
[("unbuild_id", "=", self.id)])
|
||||
|
||||
# Comes from
|
||||
# https://github.com/OCA/ocb/blob/12.0/addons/mrp/models/...
|
||||
# mrp_unbuild.py#L117
|
||||
if consume_move.has_tracking != 'none':
|
||||
self.env['stock.move.line'].create({
|
||||
'move_id': consume_move.id,
|
||||
'lot_id': self.lot_id.id,
|
||||
'qty_done': consume_move.product_uom_qty,
|
||||
'product_id': consume_move.product_id.id,
|
||||
'product_uom_id': consume_move.product_uom.id,
|
||||
'location_id': consume_move.location_id.id,
|
||||
'location_dest_id': consume_move.location_dest_id.id,
|
||||
})
|
||||
else:
|
||||
consume_move.quantity_done = consume_move.product_uom_qty
|
||||
consume_move._action_done()
|
||||
|
||||
# Comment from odoo original module:
|
||||
# TODO: Will fail if user do more than one unbuild with lot
|
||||
# on the same MO. Need to check what other unbuild has aready took
|
||||
for produce_move in produce_moves:
|
||||
if produce_move.has_tracking != 'none':
|
||||
if produce_move.product_id.tracking == "serial":
|
||||
# TODO
|
||||
raise UserError(_(
|
||||
"Unbuild of component of tracked as serial "
|
||||
"is not supported for now: contact maintainers of "
|
||||
"'mrp_unbuild_tracked_raw_material' module "
|
||||
"if you want this feature"))
|
||||
lot = self.env['stock.production.lot'].create(
|
||||
self._prepare_lots_for_purchased_unbuild(
|
||||
produce_move.product_id))
|
||||
self.env['stock.move.line'].create({
|
||||
'move_id': produce_move.id,
|
||||
'lot_id': lot.id,
|
||||
'qty_done': produce_move.product_uom_qty,
|
||||
'product_id': produce_move.product_id.id,
|
||||
'product_uom_id': produce_move.product_uom.id,
|
||||
'location_id': produce_move.location_id.id,
|
||||
'location_dest_id': produce_move.location_dest_id.id,
|
||||
})
|
||||
else:
|
||||
produce_move.quantity_done = produce_move.product_uom_qty
|
||||
# comes from native code
|
||||
produce_moves._action_done()
|
||||
produced_move_line_ids = produce_moves.mapped(
|
||||
'move_line_ids').filtered(lambda ml: ml.qty_done > 0)
|
||||
consume_move.move_line_ids.write(
|
||||
{'produce_line_ids': [(6, 0, produced_move_line_ids.ids)]})
|
||||
self.message_post(
|
||||
body=_("Product has been unbuilt without previous "
|
||||
"manufacturing order"))
|
||||
return self.write({'state': 'done'})
|
||||
|
||||
def _prepare_lots_for_purchased_unbuild(self, product):
|
||||
# Customize your data lot with your own code
|
||||
return {
|
||||
"name": datetime.now().strftime(DT_FORMAT),
|
||||
"product_id": product.id,
|
||||
}
|
||||
1
mrp_unbuild_tracked_raw_material/readme/CONFIGURE.rst
Normal file
1
mrp_unbuild_tracked_raw_material/readme/CONFIGURE.rst
Normal file
@@ -0,0 +1 @@
|
||||
Customize method `_prepare_lots_for_purchased_unbuild()` to define your own data lot
|
||||
3
mrp_unbuild_tracked_raw_material/readme/CONTRIBUTORS.rst
Normal file
3
mrp_unbuild_tracked_raw_material/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,3 @@
|
||||
Akretion:
|
||||
|
||||
* David Béal <david.beal@akretion.com>
|
||||
17
mrp_unbuild_tracked_raw_material/readme/DESCRIPTION.rst
Normal file
17
mrp_unbuild_tracked_raw_material/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
Odoo has a limitation on tracked product's components
|
||||
which are not manufactured in the ERP.
|
||||
|
||||
When you try to do it, you get this warning:
|
||||
|
||||
Some of your components are tracked, you have to specify a manufacturing order in order to retrieve the correct components.
|
||||
|
||||
Unfortunately, it doesn't cover all the use cases.
|
||||
|
||||
Example:
|
||||
You receive eggs and you want to unbuild them in 2 parts:
|
||||
|
||||
- yellow part
|
||||
- white part
|
||||
|
||||
Each of the parts are tracked and not linked to a previous manufacturing order
|
||||
because, you don't build the eggs yourself, you subcontract it to a chicken.
|
||||
3
mrp_unbuild_tracked_raw_material/readme/ROADMAP.rst
Normal file
3
mrp_unbuild_tracked_raw_material/readme/ROADMAP.rst
Normal file
@@ -0,0 +1,3 @@
|
||||
This module doesn't take account product `allow_unbuild_purchased` checked
|
||||
which use `serial` tracking
|
||||
|
||||
6
mrp_unbuild_tracked_raw_material/readme/USAGE.rst
Normal file
6
mrp_unbuild_tracked_raw_material/readme/USAGE.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
# Check 'Allow Unbuild Purchased' field in Inventory tab on product form
|
||||
for any product with a bom you didn't manufactured but you want unbuild.
|
||||
|
||||
# Go to Manufacturing > Operations > Unbuild Orders
|
||||
|
||||
# Encode an unbuild order with the product used in first step
|
||||
1
mrp_unbuild_tracked_raw_material/tests/__init__.py
Normal file
1
mrp_unbuild_tracked_raw_material/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_unbuild
|
||||
71
mrp_unbuild_tracked_raw_material/tests/test_unbuild.py
Normal file
71
mrp_unbuild_tracked_raw_material/tests/test_unbuild.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# Copyright (C) 2019 Akretion (http://www.akretion.com). All Rights Reserved
|
||||
# @author David BEAL <david.beal@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
# from odoo.tests.common import TransactionCase
|
||||
from odoo.addons.mrp.tests.common import TestMrpCommon
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class TestUnbuildUnmanufacturedProduct(TestMrpCommon):
|
||||
|
||||
def setUp(self, *args, **kwargs):
|
||||
self.loc = self.env.ref("stock.stock_location_stock")
|
||||
super().setUp(*args, **kwargs)
|
||||
|
||||
def create_data(self):
|
||||
prd_to_build = self.env["product.product"].create({
|
||||
"name": "To unbuild",
|
||||
"type": "product",
|
||||
"allow_unbuild_purchased": True,
|
||||
"tracking": "lot",
|
||||
})
|
||||
prd_to_use1 = self.env["product.product"].create({
|
||||
"name": "component 1",
|
||||
"type": "product",
|
||||
"tracking": "lot",
|
||||
})
|
||||
prd_to_use2 = self.env["product.product"].create({
|
||||
"name": "component 2",
|
||||
"type": "product",
|
||||
"tracking": "none",
|
||||
})
|
||||
bom = self.env["mrp.bom"].create({
|
||||
"product_id": prd_to_build.id,
|
||||
"product_tmpl_id": prd_to_build.product_tmpl_id.id,
|
||||
"product_uom_id": self.uom_unit.id,
|
||||
"product_qty": 1.0,
|
||||
"type": "normal",
|
||||
"bom_line_ids": [
|
||||
(0, 0, {"product_id": prd_to_use2.id,
|
||||
"product_qty": 1}),
|
||||
(0, 0, {"product_id": prd_to_use1.id,
|
||||
"product_qty": 2})
|
||||
]})
|
||||
return (bom, prd_to_build, prd_to_use1, prd_to_use2)
|
||||
|
||||
def test_unbuild(self):
|
||||
""" """
|
||||
bom, prd_to_build, prd_to_use1, prd_to_use2 = self.create_data()
|
||||
lot = self.env['stock.production.lot'].create(
|
||||
{"name": "%s" % datetime.now(),
|
||||
"product_id": prd_to_build.id})
|
||||
self.env["stock.quant"]._update_available_quantity(
|
||||
prd_to_build, self.loc, 10, lot_id=lot)
|
||||
unbuild = self.env["mrp.unbuild"].create({
|
||||
"product_id": prd_to_build.id,
|
||||
"bom_id": bom.id,
|
||||
"product_qty": 1.0,
|
||||
"lot_id": lot.id,
|
||||
"product_uom_id": self.uom_unit.id,
|
||||
})
|
||||
unbuild.action_validate()
|
||||
self._check_qty(9, prd_to_build)
|
||||
self._check_qty(2, prd_to_use1)
|
||||
self._check_qty(1, prd_to_use2)
|
||||
|
||||
def _check_qty(self, qty, product):
|
||||
self.assertEqual(self.env["stock.quant"]._get_available_quantity(
|
||||
product, self.loc), qty,
|
||||
"You should have the %s product '%s' in stock" % (
|
||||
qty, product.name))
|
||||
16
mrp_unbuild_tracked_raw_material/views/product_view.xml
Normal file
16
mrp_unbuild_tracked_raw_material/views/product_view.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_template_property_form" model="ir.ui.view">
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="stock.view_template_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="tracking" position="after">
|
||||
<field name="allow_unbuild_purchased"
|
||||
attrs="{'invisible': [('tracking', '=', 'none')]}"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user