diff --git a/mrp_unbuild_bom_cust_qty/__manifest__.py b/mrp_unbuild_bom_cust_qty/__manifest__.py
index d48cd813..e7c78650 100644
--- a/mrp_unbuild_bom_cust_qty/__manifest__.py
+++ b/mrp_unbuild_bom_cust_qty/__manifest__.py
@@ -7,10 +7,11 @@
""",
"author": "Solvos",
"license": "LGPL-3",
- "version": "13.0.2.2.0",
+ "version": "13.0.3.0.0",
"category": "Manufacturing",
"website": "https://github.com/solvosci/slv-manufacture",
"depends": ["mrp"],
+ "excludes": ["mrp_unbuild_tracked_raw_material"],
"data": [
"security/mrp_unbuild_bom_cust_qty_security.xml",
"security/ir.model.access.csv",
diff --git a/mrp_unbuild_bom_cust_qty/models/mrp_unbuild.py b/mrp_unbuild_bom_cust_qty/models/mrp_unbuild.py
index c0d963e5..b60a97d1 100644
--- a/mrp_unbuild_bom_cust_qty/models/mrp_unbuild.py
+++ b/mrp_unbuild_bom_cust_qty/models/mrp_unbuild.py
@@ -3,6 +3,13 @@
from odoo import _, api, models, fields
from odoo.exceptions import ValidationError
+from odoo.exceptions import UserError
+from odoo.tools import float_round
+
+MESSAGE = (
+ "Some of your components are tracked, you have to specify "
+ "a manufacturing order in order to retrieve the correct components."
+)
class MrpUnbuild(models.Model):
@@ -163,6 +170,95 @@ def action_validate(self):
) % error_totals)
return super().action_validate()
+ 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()
+
+ Copied from https://github.com/OCA/manufacture/blob/13.0/mrp_unbuild_tracked_raw_material/models/unbuild.py
+ """
+ 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):
+ return self._bypass_tracked_product_without_mo()
+ # Here is the Odoo native raise
+ raise err
+ return res
+
+ def _bypass_tracked_product_without_mo(self):
+ consume_moves = self.env["stock.move"].search(
+ [("consume_unbuild_id", "=", self.id)]
+ )
+ produce_moves = self.env["stock.move"].search([("unbuild_id", "=", self.id)])
+ finished_moves = consume_moves.filtered(lambda m: m.product_id == self.product_id)
+ consume_moves -= finished_moves
+
+ """
+ Fully copied from https://github.com/odoo/odoo/blob/4cc086d330b73514fbc64a7fcf22d8a7a9f1b691/addons/mrp/models/mrp_unbuild.py#L150-L199
+ """
+ if any(consume_move.has_tracking != 'none' and not self.mo_id for consume_move in consume_moves):
+ raise UserError(_('Some of your byproducts are tracked, you have to specify a manufacturing order in order to retrieve the correct byproducts.'))
+
+ for finished_move in finished_moves:
+ if finished_move.has_tracking != 'none':
+ self.env['stock.move.line'].create({
+ 'move_id': finished_move.id,
+ 'lot_id': self.lot_id.id,
+ 'qty_done': finished_move.product_uom_qty,
+ 'product_id': finished_move.product_id.id,
+ 'product_uom_id': finished_move.product_uom.id,
+ 'location_id': finished_move.location_id.id,
+ 'location_dest_id': finished_move.location_dest_id.id,
+ })
+ else:
+ finished_move.quantity_done = finished_move.product_uom_qty
+
+ # 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 move in produce_moves | consume_moves:
+ if move.has_tracking != 'none':
+ """ TODO this should be covered in
+ * _generate_move_from_bom_line()
+ * and _generate_move_from_bom_quants_total()
+ * OR _generate_produce_moves()
+ """
+ pass
+ # original_move = move in produce_moves and self.mo_id.move_raw_ids or self.mo_id.move_finished_ids
+ # original_move = original_move.filtered(lambda m: m.product_id == move.product_id)
+ # needed_quantity = move.product_uom_qty
+ # moves_lines = original_move.mapped('move_line_ids')
+ # if move in produce_moves and self.lot_id:
+ # moves_lines = moves_lines.filtered(lambda ml: self.lot_id in ml.lot_produced_ids)
+ # for move_line in moves_lines:
+ # # Iterate over all move_lines until we unbuilded the correct quantity.
+ # taken_quantity = min(needed_quantity, move_line.qty_done)
+ # if taken_quantity:
+ # self.env['stock.move.line'].create({
+ # 'move_id': move.id,
+ # 'lot_id': move_line.lot_id.id,
+ # 'qty_done': taken_quantity,
+ # 'product_id': move.product_id.id,
+ # 'product_uom_id': move_line.product_uom_id.id,
+ # 'location_id': move.location_id.id,
+ # 'location_dest_id': move.location_dest_id.id,
+ # })
+ # needed_quantity -= taken_quantity
+ else:
+ move.quantity_done = float_round(move.product_uom_qty, precision_rounding=move.product_uom.rounding)
+
+ finished_moves._action_done()
+ consume_moves._action_done()
+ produce_moves._action_done()
+ produced_move_line_ids = produce_moves.mapped('move_line_ids').filtered(lambda ml: ml.qty_done > 0)
+ consume_moves.mapped('move_line_ids').write({'produce_line_ids': [(6, 0, produced_move_line_ids.ids)]})
+
+ return self.write({'state': 'done'})
+
def action_add_myself(self):
self.ensure_one()
self.bom_quants_total_ids = [(
@@ -172,13 +268,37 @@ def action_add_myself(self):
)]
self.bom_custom_quants_add_myself = True
+ def _prepare_move_line_with_lot(self, move, bom_quant_id):
+ return {
+ "move_id": move.id,
+ "lot_id": bom_quant_id.lot_id.id,
+ "qty_done": bom_quant_id.custom_qty,
+ "product_id": move.product_id.id,
+ "product_uom_id": move.product_uom.id,
+ "location_id": move.location_id.id,
+ "location_dest_id": move.location_dest_id.id,
+ }
+
def _generate_move_from_bom_line(self, product, product_uom, quantity, bom_line_id=False, byproduct_id=False):
# Default BoM line quantity is overwritten by custom ones
+ bom_quant_ids = False
if self.bom_custom_quants and bom_line_id:
bom_line_obj = self.bom_quants_total_ids.filtered(lambda x: x.bom_line_id.id == bom_line_id)
quantity = bom_line_obj.total_qty
+ bom_quant_ids = bom_line_obj.bom_quant_ids
+
+ move = super()._generate_move_from_bom_line(product, product_uom, quantity, bom_line_id=bom_line_id, byproduct_id=byproduct_id)
+
+ if self.bom_custom_quants and bom_line_id and product.tracking != "none" and not all(bom_quant_ids.mapped("has_lot")):
+ raise UserError(_("You have to specify a lot for %s") % product.name)
+
+ if bom_quant_ids and product.tracking != "none":
+ for bom_quant_id in bom_quant_ids:
+ self.env["stock.move.line"].create(
+ self._prepare_move_line_with_lot(move, bom_quant_id)
+ )
- return super()._generate_move_from_bom_line(product, product_uom, quantity, bom_line_id=bom_line_id, byproduct_id=byproduct_id)
+ return move
def _generate_produce_moves(self):
moves = super()._generate_produce_moves()
@@ -194,15 +314,18 @@ def _generate_produce_moves(self):
product_id = record.bom_line_id.product_id
if product_id.type not in ["product", "consu"]:
continue
- product_uom_id = record.bom_line_id.product_uom_id
- moves += self._generate_move_from_bom_quants_total(product_id, product_uom_id, record.total_qty)
+ moves += self._generate_move_from_bom_quants_total(record)
return moves
- def _generate_move_from_bom_quants_total(self, product, product_uom, quantity):
+ def _generate_move_from_bom_quants_total(self, quant_total):
+ product = quant_total.bom_line_id.product_id
+ product_uom = quant_total.bom_line_id.product_uom_id
+ quantity = quant_total.total_qty
+
location_id = product.property_stock_production or self.location_id
location_dest_id = self.location_dest_id or product.with_context(force_company=self.company_id.id).property_stock_production
warehouse = location_dest_id.get_warehouse()
- return self.env['stock.move'].create({
+ move = self.env['stock.move'].create({
'name': self.name,
'date': self.create_date,
'product_id': product.id,
@@ -216,6 +339,17 @@ def _generate_move_from_bom_quants_total(self, product, product_uom, quantity):
'company_id': self.company_id.id,
})
+ bom_quant_ids = quant_total.bom_quant_ids
+ if product.tracking != "none" and not all(bom_quant_ids.mapped("has_lot")):
+ raise UserError(_("You have to specify a lot for %s") % product.name)
+ if bom_quant_ids and product.tracking != "none":
+ for bom_quant_id in bom_quant_ids:
+ self.env["stock.move.line"].create(
+ self._prepare_move_line_with_lot(move, bom_quant_id)
+ )
+
+ return move
+
def _update_product_qty_from_bom_totals(self):
if not self.env.user.has_group(
"mrp_unbuild_bom_cust_qty.group_unbuild_force_exact_qty"
diff --git a/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_mixin.py b/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_mixin.py
index 7ac0f749..4dc82ab5 100644
--- a/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_mixin.py
+++ b/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_mixin.py
@@ -28,6 +28,7 @@ class MrpUnbuildBoMMixin(models.AbstractModel):
check_company=True,
string="Related BoM Line",
)
+ product_id = fields.Many2one(related="bom_line_id.product_id")
product_uom_id = fields.Many2one(related="bom_line_id.product_uom_id")
is_product_uom_diff = fields.Boolean(
compute="_compute_is_product_uom_diff",
diff --git a/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_quants.py b/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_quants.py
index 088c3dba..5d3386e0 100644
--- a/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_quants.py
+++ b/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_quants.py
@@ -15,10 +15,31 @@ class MrpUnbuildBoMQuants(models.Model):
digits='Product Unit of Measure',
required=True,
)
+ lot_id = fields.Many2one(
+ comodel_name="stock.production.lot",
+ )
+ has_lot = fields.Boolean(
+ compute="_compute_has_lot",
+ )
+ lot_required = fields.Boolean(
+ compute="_compute_lot_required",
+ )
departure_date = fields.Datetime(
readonly=True
)
+ @api.depends("lot_id")
+ def _compute_has_lot(self):
+ for record in self:
+ record.has_lot = bool(record.lot_id)
+
+ @api.depends("bom_line_id")
+ def _compute_lot_required(self):
+ for record in self:
+ record.lot_required = (
+ record.bom_line_id.product_id.tracking != "none"
+ )
+
@api.constrains("custom_qty")
def _check_custom_qty(self):
for record in self:
diff --git a/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_total.py b/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_total.py
index b1548a33..9e0a9625 100644
--- a/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_total.py
+++ b/mrp_unbuild_bom_cust_qty/models/mrp_unbuild_bom_total.py
@@ -27,6 +27,10 @@ class MrpUnbuildBoMTotals(models.Model):
deco_danger = fields.Boolean(
compute="_compute_deco_danger"
)
+ bom_quant_ids = fields.Many2many(
+ comodel_name="mrp.unbuild.bom.quants",
+ compute="_compute_bom_quant_ids",
+ )
@api.depends("unbuild_id.product_qty",
# "unbuild_id.bom_id.bom_line_ids.product_qty",
@@ -58,9 +62,14 @@ def _compute_deco_danger(self):
def _compute_total_qty(self):
for record in self:
record.total_qty = 0
+ # TODO replace with new field record.bom_quant_ids
for bom_quant in record.unbuild_id.bom_quants_ids.filtered(lambda x: x.bom_line_id.id == record.bom_line_id.id):
record.total_qty += bom_quant.custom_qty
+ def _compute_bom_quant_ids(self):
+ for record in self:
+ record.bom_quant_ids = record.unbuild_id.bom_quants_ids.filtered(lambda x: x.bom_line_id.id == record.bom_line_id.id)
+
def product_weighing(self):
self.ensure_one()
mrp_unbuild_bom = self.env.ref(
diff --git a/mrp_unbuild_bom_cust_qty/views/mrp_unbuild_bom_quants.xml b/mrp_unbuild_bom_cust_qty/views/mrp_unbuild_bom_quants.xml
index 922d5088..196900c0 100644
--- a/mrp_unbuild_bom_cust_qty/views/mrp_unbuild_bom_quants.xml
+++ b/mrp_unbuild_bom_cust_qty/views/mrp_unbuild_bom_quants.xml
@@ -18,6 +18,29 @@
nolabel="1"
style="font-size: 200%;"
/>
+
+
+
+
+
@@ -54,6 +77,11 @@
+