ADD module mrp_unbuild_tracked_raw_material

This commit is contained in:
David Beal
2019-10-31 15:49:38 +01:00
committed by ps-tubtim
parent 7665d61a40
commit a09730e110
14 changed files with 276 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

View File

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

View 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,
}

View File

@@ -0,0 +1,2 @@
from . import product
from . import unbuild

View 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")

View 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,
}

View File

@@ -0,0 +1 @@
Customize method `_prepare_lots_for_purchased_unbuild()` to define your own data lot

View File

@@ -0,0 +1,3 @@
Akretion:
* David Béal <david.beal@akretion.com>

View 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.

View File

@@ -0,0 +1,3 @@
This module doesn't take account product `allow_unbuild_purchased` checked
which use `serial` tracking

View 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

View File

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

View 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))

View 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>