[FIX] mrp, mrp_byproduct: updating the quantity of production orders fixes
When updating quantity, it should take use a similar method as for generating the moves, but with the quantity already produced (and posted) deduced. The parameters for these method needed to be changed. When all the moves are posted, we should create the necessary extra moves. If the quantity is put back to the quantity posted, it might also need to delete moves. In byproduct, it should take into account the subproduct factor. When the quantity produced on the work orders has reached the quantity to do, it should not allow anymore to use the "Done" button on work orders, but rather use the Finish button instead. With the Update button on the manufacturing order it is possible to do more.
This commit is contained in:
@@ -111,6 +111,10 @@ class MrpBom(models.Model):
|
||||
return self.search(domain, order='sequence, product_id', limit=1)
|
||||
|
||||
def explode(self, product, quantity, picking_type=False):
|
||||
"""
|
||||
Explodes the BoM and creates two lists with all the information you need: bom_done and line_done
|
||||
Quantity should be passed in the UoM of the BoM (don't divide by its quantity)
|
||||
"""
|
||||
boms_done = [(self, {'qty': quantity, 'product': product, 'original_qty': quantity, 'parent_line': False})]
|
||||
lines_done = []
|
||||
templates_done = product.product_tmpl_id
|
||||
|
||||
@@ -311,8 +311,7 @@ class MrpProduction(models.Model):
|
||||
source_location = self.bom_id.routing_id.location_id
|
||||
else:
|
||||
source_location = self.location_src_id
|
||||
original_quantity = self.env['product.uom']._compute_qty(self.product_uom_id.id, self.product_qty, self.bom_id.product_uom_id.id)
|
||||
|
||||
original_quantity = self.product_qty - self.qty_produced
|
||||
data = {
|
||||
'name': self.name,
|
||||
'date': self.date_planned_start,
|
||||
@@ -354,14 +353,21 @@ class MrpProduction(models.Model):
|
||||
move.procure_method = 'make_to_order'
|
||||
|
||||
@api.multi
|
||||
def _update_raw_move(self, bom_line, quantity, **kw):
|
||||
def _update_raw_move(self, bom_line, line_data):
|
||||
quantity = line_data['qty']
|
||||
self.ensure_one()
|
||||
move = self.move_raw_ids.filtered(lambda x: x.bom_line_id.id == bom_line.id and x.state not in ('done', 'cancel'))
|
||||
if move:
|
||||
move.write({'product_uom_qty': quantity})
|
||||
if quantity > 0:
|
||||
move[0].write({'product_uom_qty': quantity})
|
||||
else:
|
||||
if move[0].quantity_done > 0:
|
||||
raise UserError(_('Lines need to be deleted, but can not as you still have some quantities to consume in them. '))
|
||||
move[0].action_cancel()
|
||||
move[0].unlink()
|
||||
return move
|
||||
else:
|
||||
self._generate_raw_move(bom_line, quantity)
|
||||
self._generate_raw_move(bom_line, line_data)
|
||||
|
||||
@api.multi
|
||||
def action_assign(self):
|
||||
@@ -398,8 +404,7 @@ class MrpProduction(models.Model):
|
||||
workorders = self.env['mrp.workorder']
|
||||
qty = bom_data['qty']
|
||||
|
||||
use_serial = any(product.tracking == 'serial' for product in self.mapped('move_finished_ids.product_id'))
|
||||
if use_serial:
|
||||
if self.product_id.tracking == 'serial':
|
||||
quantity = 1.0
|
||||
else:
|
||||
quantity = self.product_qty - sum(self.move_finished_ids.mapped('quantity_done'))
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<button name="button_finish" type="object" string="Finish Order" attrs="{'invisible': ['|', ('state', '!=', 'progress'), ('is_produced', '=', False)]}" class="btn-info"/>
|
||||
<button name="button_start" type="object" string="Start Working" attrs="{'invisible': ['|', ('working_state', '=', 'blocked'), ('state', '!=', 'pending')]}"/>
|
||||
<button name="button_start" type="object" string="Start Working" attrs="{'invisible': ['|', ('working_state', '=', 'blocked'), ('state', '!=', 'ready')]}" class="btn-success"/>
|
||||
<button name="record_production" type="object" string="Done" class="btn-success" attrs="{'invisible': ['|', '|', ('working_state', '=', 'blocked'), ('state', '!=', 'progress'), ('is_user_working', '=', False)]}"/>
|
||||
<button name="record_production" type="object" string="Done" class="btn-success" attrs="{'invisible': ['|', '|', '|', ('is_produced', '=', True), ('working_state', '=', 'blocked'), ('state', '!=', 'progress'), ('is_user_working', '=', False)]}"/>
|
||||
<button name="button_pending" type="object" string="Pause" class="btn-warning" attrs="{'invisible': ['|', '|', ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'pending', 'ready', 'cancel')), ('is_user_working', '=', False)]}"/>
|
||||
<button name="%(act_mrp_block_workcenter_wo)d" type="action" context="{'default_workcenter_id': workcenter_id}" string="Block" class="btn-danger" attrs="{'invisible': ['|', '|', ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'pending', 'ready', 'cancel')), ('is_user_working', '=', False)]}"/>
|
||||
<button name="button_unblock" type="object" string="Unblock" class="btn-danger" attrs="{'invisible': [('working_state', '!=', 'blocked')]}"/>
|
||||
|
||||
@@ -37,32 +37,41 @@ class ChangeProductionQty(models.TransientModel):
|
||||
|
||||
@api.multi
|
||||
def change_prod_qty(self):
|
||||
MrpBom = self.env['mrp.bom']
|
||||
for wizard in self:
|
||||
production = wizard.mo_id
|
||||
produced = sum(production.move_finished_ids.mapped('quantity_done'))
|
||||
if wizard.product_qty < produced:
|
||||
raise UserError(_("You have already processed %d. Please input a quantity higher than %d ")%(produced, produced))
|
||||
production.write({'product_qty': wizard.product_qty})
|
||||
#production.action_compute()
|
||||
#TODO: Do we still need to change the quantity of a production order?
|
||||
production_move = production.move_finished_ids.filtered(lambda x : x.state not in ('done', 'cancel') and production.product_id.id == x.product_id.id)
|
||||
for move in production.move_raw_ids:
|
||||
bom_point = production.bom_id
|
||||
# TDE FIXME: this is not the place to do that kind of computation, please
|
||||
if not bom_point:
|
||||
bom_point = MrpBom._bom_find(product=production.product_id, picking_type=production.picking_type_id)
|
||||
if not bom_point:
|
||||
raise UserError(_("Cannot find bill of material for this production."))
|
||||
production.write({'bom_id': bom_point.id})
|
||||
if not bom_point:
|
||||
raise UserError(_("Cannot find bill of material for this production."))
|
||||
factor = (production.product_qty - production.qty_produced) * production.product_uom_id.factor / bom_point.product_uom_id.factor
|
||||
boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id)
|
||||
for line, line_data in lines:
|
||||
production._update_raw_move(line, line_data['qty'])
|
||||
bom_point = production.bom_id
|
||||
factor = self.env['product.uom']._compute_qty(production.product_uom_id.id, production.product_qty - production.qty_produced, production.bom_id.product_uom_id.id)
|
||||
boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id)
|
||||
for line, line_data in lines:
|
||||
production._update_raw_move(line, line_data)
|
||||
self._update_product_to_produce(production, production.product_qty - production.qty_produced)
|
||||
moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
|
||||
moves.do_unreserve()
|
||||
moves.action_assign()
|
||||
for wo in production.workorder_ids:
|
||||
if production.product_id.tracking == 'serial':
|
||||
quantity = 1.0
|
||||
else:
|
||||
quantity = wo.qty_production - wo.qty_produced
|
||||
quantity = quantity if (quantity > 0) else 0
|
||||
wo.qty_producing = quantity
|
||||
if wo.qty_produced < wo.qty_production and wo.state == 'done':
|
||||
wo.state = 'progress'
|
||||
# assign moves; last operation receive all unassigned moves (which case ?)
|
||||
operation = wo.operation_id
|
||||
|
||||
# TODO: following could be put in a function as it is similar as code in _workorders_create
|
||||
# TODO: only needed when creating new moves
|
||||
moves_raw = production.move_raw_ids.filtered(lambda move: move.operation_id == operation and move.state not in ('done', 'cancel'))
|
||||
if wo == production.workorder_ids[-1]:
|
||||
moves_raw |= production.move_raw_ids.filtered(lambda move: not move.operation_id)
|
||||
moves_finished = production.move_finished_ids.filtered(lambda move: move.operation_id == operation) #TODO: code does nothing, unless maybe by_products?
|
||||
moves_raw.mapped('move_lot_ids').write({'workorder_id': wo.id})
|
||||
(moves_finished + moves_raw).write({'workorder_id': wo.id})
|
||||
if wo.move_raw_ids.filtered(lambda x: x.product_id.tracking != 'none') and not wo.active_move_lot_ids:
|
||||
wo._generate_lot_ids()
|
||||
return {}
|
||||
|
||||
@@ -4,39 +4,41 @@
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class Production(models.Model):
|
||||
class MrpProduction(models.Model):
|
||||
_description = 'Production'
|
||||
_inherit = 'mrp.production'
|
||||
|
||||
def _create_byproduct_move(self, sub_product):
|
||||
UoM = self.env['product.uom']
|
||||
Move = self.env['stock.move']
|
||||
for production in self:
|
||||
source = production.product_id.property_stock_production.id
|
||||
product_uom_factor = UoM._compute_qty_obj(production.product_uom_id, production.product_qty - production.qty_produced, production.bom_id.product_uom_id)
|
||||
qty1 = sub_product.product_qty
|
||||
qty1 *= product_uom_factor / production.bom_id.product_qty
|
||||
data = {
|
||||
'name': 'PROD:%s' % production.name,
|
||||
'date': production.date_planned_start,
|
||||
'product_id': sub_product.product_id.id,
|
||||
'product_uom_qty': qty1,
|
||||
'product_uom': sub_product.product_uom_id.id,
|
||||
'location_id': source,
|
||||
'location_dest_id': production.location_dest_id.id,
|
||||
'production_id': production.id,
|
||||
'origin': production.name,
|
||||
'unit_factor': qty1 / (production.product_qty - production.qty_produced),
|
||||
'subproduct_id': sub_product.id
|
||||
}
|
||||
move = Move.create(data)
|
||||
move.action_confirm()
|
||||
|
||||
@api.multi
|
||||
def _generate_moves(self):
|
||||
""" Generates moves and work orders
|
||||
@return: Newly generated picking Id.
|
||||
"""
|
||||
# TDE FIXME: was action_confirm ?? -> refactored
|
||||
Move = self.env['stock.move']
|
||||
res = super(Production, self)._generate_moves()
|
||||
UoM = self.env['product.uom']
|
||||
res = super(MrpProduction, self)._generate_moves()
|
||||
for production in self.filtered(lambda production: production.bom_id):
|
||||
source = production.product_id.property_stock_production.id
|
||||
for sub_product in production.bom_id.sub_products:
|
||||
product_uom_factor = UoM._compute_qty_obj(production.product_uom_id, production.product_qty, production.bom_id.product_uom_id)
|
||||
qty1 = sub_product.product_qty
|
||||
qty1 *= product_uom_factor / production.bom_id.product_qty
|
||||
data = {
|
||||
'name': 'PROD:%s' % production.name,
|
||||
'date': production.date_planned_start,
|
||||
'product_id': sub_product.product_id.id,
|
||||
'product_uom_qty': qty1,
|
||||
'product_uom': sub_product.product_uom_id.id,
|
||||
'location_id': source,
|
||||
'location_dest_id': production.location_dest_id.id,
|
||||
'production_id': production.id,
|
||||
'origin': production.name,
|
||||
'unit_factor': qty1 / production.product_qty,
|
||||
'subproduct_id': sub_product.id
|
||||
}
|
||||
move = Move.create(data)
|
||||
move.action_confirm()
|
||||
production._create_byproduct_move(sub_product)
|
||||
return res
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Stok_Move(models.Model):
|
||||
class StockMove(models.Model):
|
||||
_inherit = 'stock.move'
|
||||
|
||||
subproduct_id = fields.Many2one(
|
||||
|
||||
@@ -4,19 +4,20 @@
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class change_production_qty(models.TransientModel):
|
||||
class ChangeProductionQty(models.TransientModel):
|
||||
_inherit = 'change.production.qty'
|
||||
|
||||
@api.model
|
||||
def _update_product_to_produce(self, prod, qty):
|
||||
# U
|
||||
super(ChangeProductionQty, self)._update_product_to_produce(prod, qty)
|
||||
Production = self.env['mrp.production']
|
||||
for move in prod.move_finished_ids:
|
||||
if move.product_id == prod.product_id:
|
||||
move.write({'product_uom_qty': qty})
|
||||
UoM = self.env['product.uom']
|
||||
for sub_product_line in prod.bom_id.sub_products:
|
||||
move = prod.move_finished_ids.filtered(lambda x: x.sub_product_id == sub_product_line and x.state not in ('done', 'cancel'))
|
||||
if move:
|
||||
product_uom_factor = UoM._compute_qty_obj(prod.product_uom_id, prod.product_qty - prod.qty_produced, prod.bom_id.product_uom_id)
|
||||
qty1 = sub_product_line.product_qty
|
||||
qty1 *= product_uom_factor / prod.bom_id.product_qty
|
||||
move[0].write({'product_uom_qty': qty1})
|
||||
else:
|
||||
for sub_product_line in prod.bom_id.sub_products:
|
||||
if sub_product_line.product_id == move.product_id:
|
||||
factor = Production._get_subproduct_factor(move)
|
||||
subproduct_qty = qty * factor
|
||||
move.write({'product_uom_qty': subproduct_qty})
|
||||
prod._create_byproduct_move(sub_product_line)
|
||||
@@ -629,6 +629,7 @@ class StockMove(models.Model):
|
||||
if move.state != 'assigned' and not self.env.context.get('reserve_only_ops'):
|
||||
qty_already_assigned = move.reserved_availability
|
||||
qty = move.product_qty - qty_already_assigned
|
||||
|
||||
quants = Quant.quants_get_preferred_domain(qty, move, domain=main_domain[move.id], preferred_domain_list=[])
|
||||
Quant.quants_reserve(quants, move)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user