[WIP] stock: partial re-implementation of action_assign do_rereserve...

bzr revid: qdp-launchpad@openerp.com-20131115175505-8x75o2xcxxm58g0z
This commit is contained in:
Quentin (OpenERP) 2013-11-15 18:55:05 +01:00
parent 3e21ab83e8
commit 9a3da0db2b
2 changed files with 163 additions and 90 deletions

View File

@ -259,12 +259,25 @@ class stock_quant(osv.osv):
return result
def quants_reserve(self, cr, uid, quants, move, context=None):
'''This function reserves quants for the given move. If the total of quantity reserved is enough, the move's state is also set to 'assigned'
:param quants: list of tuple(quant browse record or None, qty to reserve). If None is given as first tuple element, the item will be ignored
:param move: browse record
'''
toreserve = []
for quant,qty in quants:
if not quant: continue
#split quants if needed
for quant, qty in quants:
if not quant:
continue
self._quant_split(cr, uid, quant, qty, context=context)
toreserve.append(quant.id)
return self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id}, context=context)
#reserve quants
if toreserve:
self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id}, context=context)
#check if move'state needs to be set as 'assigned'
move.refresh()
if sum([q.qty for q in move.reserved_quant_ids]) == move.product_qty and move.state == 'confirmed':
self.pool.get('stock.move').write(cr, uid, [move.id], {'state': 'assigned'}, context=context)
# add location_dest_id in parameters (False=use the destination of the move)
def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, package_id=False, context=None):
@ -325,7 +338,7 @@ class stock_quant(osv.osv):
Use the removal strategies of product to search for the correct quants
If you inherit, put the super at the end of your method.
:location: browse record of the parent location in which the quants have to be found
:location: browse record of the parent location where the quants have to be found
:product: browse record of the product to find
:qty in UoM of product
"""
@ -750,17 +763,50 @@ class stock_picking(osv.osv):
def action_assign(self, cr, uid, ids, context=None):
""" Check availability of picking moves.
This has the effect of changing the state and reserved quants of available moves, and may
This has the effect of changing the state and reserve quants on available moves, and may
also impact the state of the picking as it is computed based on move's states.
@return: True
"""
def build_domain(record):
op = record.operation_id
move = record.move_id
domain = [('product_id', '=', move.produc_id.id)]
if op.package_id:
domain.append(('id', 'in', package_obj. _get_content_package(cr, uid, [op.package_id.id, context=context]))
elif op.quant_id:
domain.append(('id', '=', op.quant_id.id))
elif op.lot_id:
domain.append(('lot_id', '=', op.lot_id.id))
else:
#fallback on the default behavior
return []
return domain
package_obj = self.pool.get('stock.quant.package')
quant_obj = self.pool.get('stock.quant')
deferred_move_ids = []
for pick in self.browse(cr, uid, ids, context=context):
if pick.state == 'draft':
self.action_confirm(cr, uid, [pick.id], context=context)
move_ids = [x.id for x in pick.move_lines if x.state == 'confirmed']
move_ids = [x.id for x in pick.move_lines if x.state not in ('draft', 'cancel', 'done')]
if not move_ids:
raise osv.except_osv(_('Warning!'), _('No product available.'))
self.pool.get('stock.move').action_assign(cr, uid, move_ids, context=context)
raise osv.except_osv(_('Warning!'), _('Nothing to check the availability for.'))
for move in pick.move_lines:
#skip the move that doesn't need to be checked
if move.state in ('draft', 'cancel', 'done'):
continue
#assign quants based on the pack operations of the picking
for record in move.linked_move_operation_ids:
domain = build_domain(record)
if domain:
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, record.qty, domain=domain)
quants_obj.quants_reserve(cr, uid, quants, move, context=context)
#if the move is still not totally available (not possible to reserve everything based on the pack operations),
#queue this move in the list to recompute based on generic action_assign of the move.
if move.state != 'assigned':
deferred_move_ids.append(move.id)
if deferred_move_ids:
self.pool.get('stock.move').action_assign(cr, uid, deferred_move_ids, context=context)
return True
def force_assign(self, cr, uid, ids, context=None):
@ -913,12 +959,19 @@ class stock_picking(osv.osv):
'cost': move.product_id.standard_price,
}, context=context)
def available_quants_assign(self, cr, uid, picking_ids, context=None):
'''Recompute the availability of quants for the given picking and assign them on the stock moves'''
#first unreserve the quants of related picking, because this function could be used to unreserve
#quants on several pickings in order to group the available ones on a single picking.
self.do_unreserve(cr, uid, picking_ids, context=context)
return self.action_assign(cr, uid picking_ids, context=context)
def do_rereserve(self, cr, uid, picking_ids, context=None):
'''
Needed for parameter create
'''
self.rereserve(cr, uid, picking_ids, context=context)
#def do_rereserve(self, cr, uid, picking_ids, context=None):
# '''
# Needed for parameter create
# '''
# self.rereserve(cr, uid, picking_ids, context=context)
def do_unreserve(self, cr, uid, picking_ids, context=None):
"""
@ -955,48 +1008,9 @@ class stock_picking(osv.osv):
# quant_obj.write(cr, SUPERUSER_ID, [x[0].id for x in quants if x[0]], {'reservation_op_id': ops.id}, context=context)
# return res_qty
def do_recompute_remaining_quantities(self, cr, uid, ids, context=None):
'''This function simply calls recompute_remaining_quantities but is needed in order to pass the context in the righ argument'''
return self.recompute_remaining_quantities(cr, uid, ids, product_ids=[], context=context)
def _get_total_from_pack_operations(self, cr, picking, ids, product_ids=[], context=None):
def _update_quantity(product, quantity, uom_id):
quantity_in_move_uom = uom_obj._compute_qty(cr, uid, uom_id, quantity, product.uom_id.id, round=False)
self._create_link_move_op(cr, uid, op
res = {}
#if product_ids is not given, we'll recompute the remaining quantity for all move lines
if not product_ids:
tocheck_product_ids = [m.product_id.id for m in picking.move_lines]
else:
tocheck_product_ids = product_ids
#loop on the operations to compute the total of each product
for op in picking.pack_operation_ids:
quant_ids = []
if op.product_id and op.product_id.id in tocheck_product_ids:
_update_quantity(op.product_id, op.product_qty, op.product_uom_id.id)
elif op.quant_id:
quant_ids = [op.quant_id.id]
elif op.package_id:
quant_ids = self.pool.get('stock.quant.package').get_content_package(cr, uid, ids, context=context)
if quant_ids:
for quant in self.pool.get('stock.quant').browse(cr, uid, quant_ids, context=context):
if quant.product_id.id in tocheck_product_ids:
_update_quantity(quant.product_id, quant.qty, quant.product_id.uom_id.id)
return res
def _set_remaining_on_lines(self, cr, uid, picking, product_dict, context=None):
#loop on the move lines to write the remaining quantity
for move in picking.move_lines:
if product_dict.get(move.product_id.id):
qty_to_write = min(move.product_qty, product_dict[move.product_id.id])
product_dict[move.product_id.id] -= qty_to_write
qty_to_write = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, qty_to_write, move.product_uom.id)
self.pool.get('stock.move').write(cr, uid, [move.id], {'remaining_qty': move.product_uom_qty - qty_to_write}, context=context)
#loop on the product_dict items to set the remaining_qty on the pack operations if needed
for product_id, remaining_qty in product_dict.items:
if remaining_qty > 0:
#there was more in pack operations than in move lines
### MEMO
###remaining_qty sur move et pack sont des champ calculés grace à des one2many vers une nouvelle table(move_id, pack_id, qty_in_move_uom)
###check_availability() (pas sur du nom)
@ -1028,17 +1042,23 @@ class stock_picking(osv.osv):
###3) action_done du move: base sur les quants assigned (semble ok pour l'instant)
###4) packaging (donc hors du action_done du move)
### package_obj._get_remaining_prod_quantities(operation):
###res = {}
####total found per product included in the operation
###for record in operation.linked_move_operation_ids:
### if record.product_id.id not in res:
### res[record.product_id.id] = 0
### res[record.product_id.id] += record.qty
####total per product included int the package
###res2 = {}
###for quant in quant_obj.browse(cr, uid, package_obj._get_content_package(cr, uid, op.package_id, context=context))
### if quant.product_id.id not in res2:
### res2[quant.product_id.id] = 0
### res2[quant.product_id.id] += quant.qty
def recompute_remaining_quantities(self, cr, uid, ids, product_ids=[], context=None):
""" This will recompute the remaining qty on all move lines on the given picking 'ids'
based on the pack operations of the picking. An optionnal product_ids may be given in
order to restrict the recomputation on stock moves having a product_id in 'product_ids' only.
"""
uom_obj = self.pool.get('product.uom')
for picking in self.browse(cr, uid, ids, context=context):
product_dict = self._get_total_from_pack_operations(cr, uid, picking, product_ids=product_ids, context=context)
self._set_remaining_on_lines(cr, uid, picking, product_dict, context=context)
#def rereserve(self, cr, uid, picking_ids, create=False, context=None):
# """
@ -1116,11 +1136,21 @@ class stock_picking(osv.osv):
# res[ops.id][quant.product_id.id] += qty_to_do
# return (res, res2, resneg)
def _create_extra_moves(self, cr, uid, picking, remaining_dict, context=None):
'''This function creates move lines on a picking, at the time of do_transfer, based on unexpected product transfers (or exceeding quantities)
def _create_extra_moves(self, cr, uid, picking, context=None):
'''This function creates move lines on a picking, at the time of do_transfer, based on unexpected product transfers (or exceeding quantities) found in the pack operations
'''
for
pass
move_obj = self.pool.get('stock.move')
for op in picking.pack_operation_ids:
for op_remaining_product_id, op_remaing_qty in package_obj._get_remaining_prod_quantities().items():
#TODO continue MEMO
if op_remaining_qty > 0:
vals = {
'picking_id': picking.id,
'location_id': picking.location_id.id,
'location_dest_id': picking.location_dest_id.id,
'product_id': op.
}
move_obj.create(cr, uid, vals, context=context)
def rereserve_quants(self, cr, uid, picking, move_ids=[], context=None):
stock_move_obj = self.pool.get('stock.move')
@ -1144,10 +1174,8 @@ class stock_picking(osv.osv):
self.action_done(cr, uid, [picking.id], context=context)
continue
else:
#recompute remaining quantities on stock move
remaining_dict = self.recompute_remaining_quantities(cr, uid, [picking.id], product_ids=[], context=context)
#create extra moves in the picking (unexpected product moves coming from pack operations)
self._create_extra_moves(cr, uid, picking, remaining_dict, context=context)
self._create_extra_moves(cr, uid, picking, context=context)
picking.refresh()
#split move lines eventually
todo_move_ids = []
@ -1350,9 +1378,11 @@ class stock_move(osv.osv):
def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None):
res = {}
for move in self.browse(cr, uid, ids, context=context):
res[move.id] = move.product_qty
for quant in move.reserved_quant_ids:
res[move.id] -= quant.qty
qty = move.product_qty
for record in move.linked_move_operation_ids:
qty -= record.qty
#converting the remaining quantity in the move UoM
res[move.id] = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, qty, move.product_uom.id)
return res
def _get_lot_ids(self, cr, uid, ids, field_name, args, context=None):
@ -1451,11 +1481,11 @@ class stock_move(osv.osv):
'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Moved Quants'),
'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'),
'remaining_qty': fields.float('Remaining Quantity'),
#'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
# digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]},
# store = {'stock.move': (lambda self, cr, uid, ids, c={}: ids , ['product_uom_qty', 'product_uom', 'reserved_quant_ids'], 20),
# 'stock.quant': (_get_move, ['reservation_id'], 10)}),
'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'move_id', string='Linked Operations', readonly=True, help='Operations that impact this move for the computation of the remaining quantities'),
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]},),
#store = {'stock.move': (lambda self, cr, uid, ids, c={}: ids , ['product_uom_qty', 'product_uom', 'reserved_quant_ids'], 20),
# 'stock.quant': (_get_move, ['reservation_id'], 10)}),
'procurement_id': fields.many2one('procurement.order', 'Procurement'),
'group_id': fields.many2one('procurement.group', 'Procurement Group'),
'rule_id': fields.many2one('procurement.rule', 'Procurement Rule', help='The pull rule that created this stock move'),
@ -1817,9 +1847,9 @@ class stock_move(osv.osv):
done.append(move.id)
continue
else:
qty_already_assigned = ...
qty_already_assigned = sum([q.qty for q in move.reserved_quant_ids])
qty = move.product_qty - qty_already_assigned
#we keep the quants already assigned and try to find the remaining qauntity on quants not assigned only
#we keep the quants already assigned and try to find the remaining quantity on quants not assigned only
domain = [('reservation_id', '=', False), ('qty', '>', 0)]
#build the prefered domain based on quants that moved in previous linked done move
prev_quant_ids = []
@ -3260,19 +3290,49 @@ class stock_package(osv.osv):
total += quant.qty
return total
def _get_all_products_quantities(cr, uid, package_id, context=None):
'''This function computes the different product quantities for the given package
'''
quant_obj = self.pool.get('stock.quant')
res = {}
for quant in quant_obj.browse(cr, uid, self._get_content_package(cr, uid, package_id, context=context))
if quant.product_id.id not in res:
res[quant.product_id.id] = 0
res[quant.product_id.id] += quant.qty
return res
class stock_pack_operation(osv.osv):
_name = "stock.pack.operation"
_description = "Packing Operation"
#def _get_remaining_qty(self, cr, uid, ids, name, args, context=None):
# res = {}
# for ops in self.browse(cr, uid, ids, context=context):
# qty = ops.product_qty
# for quant in ops.reserved_quant_ids:
# qty -= quant.qty
# res[ops.id] = qty
# return res
def _get_remaining_prod_quantities(cr, uid, operation, context=None):
res = {}
#total found per product included in the operation
for record in operation.linked_move_operation_ids:
if record.product_id.id not in res:
res[record.product_id.id] = 0
res[record.product_id.id] += record.qty
#if the operation concerns a package (this function could work only for this case => TODO: decide
if operation.package_id:
reference_dict = self.pool.get('stock.quant.package')._get_all_products_quantities(cr, uid operation.package_id.id, context=context)
#compare the 2 dicts to know what's the difference
def _get_remaining_qty(self, cr, uid, ids, name, args, context=None):
res = {}
for ops in self.browse(cr, uid, ids, context=context):
if ops.package_id or ops.quant_id:
#dont try to compute the remaining quantity for packages because it's not relevant (a package could include different products).
#should use
res[ops.id] = 0
continue
qty = ops.product_qty
for record in ops.linked_move_operation_ids:
qty -= record.qty
if ops.product_id and ops.product_uom_id:
#converting the remaining quantity in the pack operation UoM
qty = uom_obj._compute_qty(cr, uid, ops.product_id.uom_id.id, qty, ops.product_uom_id.id)
res[ops.id] = qty
return res
def product_id_change(self, cr, uid, ids, product_id, product_uom_id, product_qty, context=None):
res = self.on_change_tests(cr, uid, ids, product_id, product_uom_id, product_qty, context=context)
@ -3316,9 +3376,8 @@ class stock_pack_operation(osv.osv):
#'update_cost': fields.boolean('Need cost update'),
'cost': fields.float("Cost", help="Unit Cost for this product line"),
'currency': fields.many2one('res.currency', string="Currency", help="Currency in which Unit cost is expressed", ondelete='CASCADE'),
'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_op_id', string='Reserved Quants', readonly=True, help='Quants reserved for this operation'),
#'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Qty'),
'remaining_qty': fields.float('Remaining Qty'),
'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'operation_id', string='Linked Moves', readonly=True, help='Moves impacted by this operation for the computation of the remaining quantities'),
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Qty'),
}
_defaults = {
@ -3384,6 +3443,20 @@ class stock_pack_operation(osv.osv):
values.update({'id': operation_id})
return True
class stock_move_operation_link(osv.osv):
"""
Table making the link between stock.moves and stock.pack.operations to compute the remaining quantities on each of these objects
"""
_name = "stock.move.operation.link"
_description = "Link between stock moves and pack operations"
_columns = {
'qty': fields.float('Quantity', help="Quantity of products to consider when talking about the contribution of this pack operation towards the remaining quantity of the move (and inverse). Given in the product main uom."),
'operation_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, ondelete="cascade"),
'move_id': fields.many2one('stock.move', 'Move', required=True, ondelete="cascade"),
}
class stock_warehouse_orderpoint(osv.osv):
"""
Defines Minimum stock rules.

View File

@ -747,7 +747,7 @@
<form string="Transfer" version="7.0">
<header>
<button name="action_confirm" states="draft" string="Mark as Todo" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="do_rereserve" states="confirmed" string="Check Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="action_assign" states="confirmed" string="Check Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="force_assign" states="confirmed" string="Force Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="do_transfer" states="assigned" string="Transfer Done" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
<button name="do_prepare_partial" string="Partial Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', True),('state','!=','assigned')]}"/>