mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[IMP] mrp_unbuild_tracked_raw_material,: black, isort
This commit is contained in:
@@ -8,12 +8,10 @@
|
||||
"author": "Akretion, Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"category": "Manufacturing",
|
||||
"version": "12.0.1.0.0",
|
||||
"version": "13.0.1.0.0",
|
||||
"license": "AGPL-3",
|
||||
"depends": ["mrp"],
|
||||
"maintainers": ["bealdav"],
|
||||
"data": [
|
||||
"views/product_view.xml",
|
||||
],
|
||||
"data": ["views/product_view.xml"],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@ class ProductTemplate(models.Model):
|
||||
|
||||
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")
|
||||
"manufacturing order have built this product.\n"
|
||||
"In this case it's a purchased product and you want unbuild it"
|
||||
)
|
||||
|
||||
@@ -4,18 +4,23 @@
|
||||
|
||||
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."
|
||||
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):
|
||||
@@ -34,13 +39,15 @@ class MrpUnbuild(models.Model):
|
||||
# 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 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"))
|
||||
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
|
||||
@@ -51,23 +58,25 @@ class MrpUnbuild(models.Model):
|
||||
# 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)])
|
||||
[("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,
|
||||
})
|
||||
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()
|
||||
@@ -76,42 +85,46 @@ class MrpUnbuild(models.Model):
|
||||
# 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.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,
|
||||
})
|
||||
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)
|
||||
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)]})
|
||||
{"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'})
|
||||
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,
|
||||
}
|
||||
return {"name": datetime.now().strftime(DT_FORMAT), "product_id": product.id}
|
||||
|
||||
@@ -3,7 +3,7 @@ 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.
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
This module doesn't take account product `allow_unbuild_purchased` checked
|
||||
which use `serial` tracking
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Check 'Allow Unbuild Purchased' field in Inventory tab on product form
|
||||
# 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
|
||||
|
||||
@@ -2,70 +2,73 @@
|
||||
# @author David BEAL <david.beal@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
# 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})
|
||||
]})
|
||||
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})
|
||||
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,
|
||||
})
|
||||
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))
|
||||
self.assertEqual(
|
||||
self.env["stock.quant"]._get_available_quantity(product, self.loc),
|
||||
qty,
|
||||
"You should have the {} product '{}' in stock".format(qty, product.name),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user