[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:
Josse Colpaert
2016-09-28 12:38:13 +02:00
parent 3e95e4e58e
commit 24a16f5db5
8 changed files with 83 additions and 61 deletions
+4
View File
@@ -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
+12 -7
View File
@@ -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'))
+1 -1
View File
@@ -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')]}"/>
+27 -18
View File
@@ -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 {}
+26 -24
View File
@@ -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
+1 -1
View File
@@ -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)
+1
View File
@@ -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)