[WIP] Negative quants and packing operations
bzr revid: jco@openerp.com-20130924143220-dcm9u876tfbxzayz
This commit is contained in:
parent
434838b9a5
commit
f90c341a24
|
@ -323,13 +323,9 @@
|
|||
Process the delivery of the outgoing shipments
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
quants = quant_obj.search(cr, uid, [('product_id','=',ref("product_fifo_negative"))])
|
||||
picking_obj1 = self.browse(cr, uid, ref("outgoing_fifo_shipment_neg2"))
|
||||
print "Quants:", [(x.qty, x.location_id.name, x.in_date, x.cost, x.id) for x in quant_obj.browse(cr, uid, quants)]
|
||||
picking_obj1.do_partial(context=context)
|
||||
quants = quant_obj.search(cr, uid, [('product_id','=',ref("product_fifo_negative"))])
|
||||
print "Quants:", [(x.qty, x.location_id.name, x.in_date, x.cost, x.id) for x in quant_obj.browse(cr, uid, quants)]
|
||||
|
||||
-
|
||||
Receive purchase order with 50 kg FIFO Ice Cream at 50 euro/kg
|
||||
-
|
||||
|
@ -351,19 +347,13 @@
|
|||
Process the reception of purchase order 1
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
quants = quant_obj.search(cr, uid, [('product_id','=',ref("product_fifo_negative"))])
|
||||
picking_obj = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_fifo_neg")).picking_ids[0]
|
||||
quants = quant_obj.search(cr, uid, [('product_id','=',ref("product_fifo_negative"))])
|
||||
print "Quants:", [(x.qty, x.location_id.name, x.in_date, x.cost, x.id) for x in quant_obj.browse(cr, uid, quants)]
|
||||
picking_obj.do_partial(context=context)
|
||||
-
|
||||
Assert price on product is still the old price as the out move has not been received fully yet
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
quants = quant_obj.search(cr, uid, [('product_id','=',ref("product_fifo_negative"))])
|
||||
print "Quants:", [(x.qty, x.location_id.name, x.in_date, x.cost, x.id) for x in quant_obj.browse(cr, uid, quants)]
|
||||
|
||||
assert self.browse(cr, uid, ref("product_fifo_negative")).standard_price == 70, 'The product price should not have been updated'
|
||||
-
|
||||
Receive purchase order with 60 kg FIFO Ice Cream at 80 euro/kg
|
||||
|
@ -386,16 +376,10 @@
|
|||
Process the reception of purchase order 2
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
quants = quant_obj.search(cr, uid, [('product_id','=',ref("product_fifo_negative"))])
|
||||
print "Quants:", [(x.qty, x.location_id.name, x.in_date, x.cost, x.id) for x in quant_obj.browse(cr, uid, quants)]
|
||||
picking_obj = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_fifo_neg2")).picking_ids[0]
|
||||
picking_obj.do_partial(context=context)
|
||||
-
|
||||
The price of the product should have changed back to 65.0
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
quants = quant_obj.search(cr, uid, [('product_id','=',ref("product_fifo_negative"))])
|
||||
print "Quants:", [(x.qty, x.location_id.name, x.in_date, x.cost, x.id) for x in quant_obj.browse(cr, uid, quants)]
|
||||
assert self.browse(cr, uid, ref("product_fifo_negative")).standard_price == 65.0, "Product price not updated accordingly. %s found instead of 65" %(self.browse(cr, uid, ref("product_fifo_negative")).standard_price,)
|
||||
|
|
|
@ -93,6 +93,7 @@ Dashboard / Reports for Warehouse Management will include:
|
|||
'test/procrule.yml',
|
||||
'test/shipment.yml',
|
||||
'test/packing.yml',
|
||||
'test/packingneg.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
|
|
|
@ -178,7 +178,8 @@ class stock_quant(osv.osv):
|
|||
'qty': fields.float('Quantity', required=True, help="Quantity of products in this quant, in the default unit of measure of the product"),
|
||||
'package_id': fields.many2one('stock.quant.package', string='Package', help="The package containing this quant"),
|
||||
'packaging_type_id': fields.related('package_id', 'packaging_id', type='many2one', relation='product.packaging', string='Type of packaging', store=True),
|
||||
'reservation_id': fields.many2one('stock.move', 'Reserved for Move', help="Is this quant reserved for a stock.move?"),
|
||||
'reservation_id': fields.many2one('stock.move', 'Reserved for Move', help="The move the quant is reserved for"),
|
||||
'reservation_op_id': fields.many2one('stock.pack.operation', 'Reserved for Pack Operation', help="The operation the quant is reserved for"),
|
||||
'lot_id': fields.many2one('stock.production.lot', 'Lot'),
|
||||
'cost': fields.float('Unit Cost'),
|
||||
'owner_id': fields.many2one('res.partner', 'Owner', help="This is the owner of the quant"),
|
||||
|
@ -213,26 +214,27 @@ class stock_quant(osv.osv):
|
|||
return self.write(cr, uid, toreserve, {'reservation_id': move.id}, context=context)
|
||||
|
||||
# add location_dest_id in parameters (False=use the destination of the move)
|
||||
def quants_move(self, cr, uid, quants, move, context=None):
|
||||
def quants_move(self, cr, uid, quants, move, lot_id = False, owner_id = False, package_id = False, context=None):
|
||||
for quant, qty in quants:
|
||||
#quant may be a browse record or None
|
||||
quant_record = self.move_single_quant(cr, uid, quant, qty, move, context=context)
|
||||
quant_record = self.move_single_quant(cr, uid, quant, qty, move, lot_id = lot_id, package_id = package_id, context=context)
|
||||
#quant_record is the quant newly created or already split
|
||||
self._quant_reconcile_negative(cr, uid, quant_record, context=context)
|
||||
|
||||
|
||||
def check_preferred_location(self, cr, uid, move, context=None):
|
||||
return move.location_dest_id
|
||||
|
||||
def move_single_quant(self, cr, uid, quant, qty, move, context=None):
|
||||
def move_single_quant(self, cr, uid, quant, qty, move, lot_id = False, owner_id = False, package_id = False, context=None):
|
||||
if not quant:
|
||||
quant = self._quant_create(cr, uid, qty, move, context=context)
|
||||
quant = self._quant_create(cr, uid, qty, move, lot_id = lot_id, owner_id = owner_id, package_id = package_id, context = context)
|
||||
else:
|
||||
self._quant_split(cr, uid, quant, qty, context=context)
|
||||
# FP Note: improve this using preferred locations
|
||||
location_to = self.check_preferred_location(cr, uid, move, context=context)
|
||||
self.write(cr, uid, [quant.id], {
|
||||
'location_id': location_to.id,
|
||||
'reservation_id': move.move_dest_id and move.move_dest_id.id or False,
|
||||
#'reservation_id': move.move_dest_id and move.move_dest_id.id or False,
|
||||
'history_ids': [(4, move.id)]
|
||||
})
|
||||
quant.refresh()
|
||||
|
@ -267,9 +269,8 @@ class stock_quant(osv.osv):
|
|||
#
|
||||
# Create a quant in the destination location
|
||||
# Create a negative quant in the source location if it's an internal location
|
||||
# Reconcile a positive quant with a negative is possible
|
||||
#
|
||||
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, force_location=False, context=None):
|
||||
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, package_id = False, force_location=False, context=None):
|
||||
# FP Note: TODO: compute the right price according to the move, with currency convert
|
||||
# QTY is normally already converted to main product's UoM
|
||||
if context is None:
|
||||
|
@ -296,6 +297,7 @@ class stock_quant(osv.osv):
|
|||
negative_vals['qty'] = -qty
|
||||
negative_vals['cost'] = price_unit
|
||||
negative_vals['negative_dest_location_id'] = move.location_dest_id.id
|
||||
negative_vals['package_id'] = package_id
|
||||
negative_quant_id = self.create(cr, uid, negative_vals, context=context)
|
||||
vals.update({'propagated_from_id': negative_quant_id})
|
||||
|
||||
|
@ -402,7 +404,7 @@ class stock_quant(osv.osv):
|
|||
#cr.execute('update stock_quant set reservation_id=NULL where reservation_id=%s', (move.id,))
|
||||
#need write for related store of remaining qty
|
||||
related_quants = [x.id for x in move.reserved_quant_ids]
|
||||
self.write(cr, uid, related_quants, {'reservation_id': False}, context=context)
|
||||
self.write(cr, uid, related_quants, {'reservation_id': False, 'reservation_op_id': False}, context=context)
|
||||
return True
|
||||
|
||||
#
|
||||
|
@ -832,6 +834,39 @@ class stock_picking(osv.osv):
|
|||
Needed for parameter create
|
||||
'''
|
||||
self.rereserve(cr, uid, picking_ids, context=context)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _reserve_quants_ops_move(self, cr, uid, ops, move, qty, create=False, context=None):
|
||||
"""
|
||||
Will return the quantity that got reserved
|
||||
"""
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
op_obj = self.pool.get("stock.pack.operation")
|
||||
if create and move.location_id.usage != 'internal':
|
||||
# Create quants
|
||||
quant = quant_obj._quant_create(cr, uid, qty, move, lot_id = ops.lot_id and ops.lot_id.id or False, owner_id = ops.owner_id and ops.owner_id.id or False, context=context)
|
||||
#TODO: location_id -> force location?
|
||||
quant.write({'reservation_op_id': ops.id, 'location_id': move.location_id.id})
|
||||
quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context)
|
||||
return 0
|
||||
else:
|
||||
#Quants get
|
||||
prefered_order = "reservation_id IS NOT NULL"
|
||||
dom = op_obj._get_domain(cr, uid, ops, context=context)
|
||||
dom = dom + [('reservation_id', 'not in', [x.id for x in move.picking_id.move_lines])]
|
||||
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=dom, prefered_order=prefered_order, context=context)
|
||||
res_qty = qty
|
||||
for quant in quants:
|
||||
if quant[0]: #If quant can be reserved
|
||||
res_qty -= quant[1]
|
||||
quant_obj.quants_reserve(cr, uid, quants, move, context=context)
|
||||
quant_obj.write(cr, uid, [x[0].id for x in quants if x[0]], {'reservation_op_id': ops.id}, context=context)
|
||||
return res_qty
|
||||
|
||||
|
||||
|
||||
#
|
||||
# TODO:rereserve should be improved for giving negative quants when a certain lot is not there
|
||||
|
@ -844,7 +879,7 @@ class stock_picking(osv.osv):
|
|||
:return: Tuple (res, res2)
|
||||
res: dictionary of ops with quantity that could not be processed matching ops and moves
|
||||
res2: dictionary of moves with quantity that could not be processed
|
||||
resneg: the negative quants that should be created with ops and move (TODO)
|
||||
resneg: the negative quants to be created: resneg[move][ops] gives negative quant to be created (TODO:)
|
||||
tuple of dictionary with quantities of quant operation and product that can not be matched between ops and moves
|
||||
and dictionary with remaining values on moves
|
||||
|
||||
|
@ -852,13 +887,21 @@ class stock_picking(osv.osv):
|
|||
quant_obj = self.pool.get("stock.quant")
|
||||
move_obj = self.pool.get("stock.move")
|
||||
op_obj = self.pool.get("stock.pack.operation")
|
||||
pack_obj = self.pool.get("stock.quant.package")
|
||||
res = {} # Qty still to do from ops
|
||||
res2 = {} #what is left from moves
|
||||
resneg= {} #Number of negative quants to create for move : op
|
||||
for picking in self.browse(cr, uid, picking_ids, context=context):
|
||||
products_moves = {}
|
||||
# unreserve everything and initialize res2
|
||||
for move in picking.move_lines:
|
||||
quant_obj.quants_unreserve(cr, uid, move, context=context)
|
||||
res2[move.id] = move.product_qty
|
||||
resneg[move.id] = {}
|
||||
if move.state == 'assigned':
|
||||
products_moves.setdefault(move.product_id.id, []).append(move)
|
||||
|
||||
|
||||
# Resort pack_operation_ids
|
||||
|
||||
orderedpackops = picking.pack_operation_ids
|
||||
|
@ -869,68 +912,43 @@ class stock_picking(osv.osv):
|
|||
#Find moves that correspond
|
||||
if ops.product_id:
|
||||
#TODO: Should have order such that things with lots and packings are searched first
|
||||
move_ids = move_obj.search(cr, uid, [('picking_id','=',picking.id), ('product_id', '=', ops.product_id.id), ('remaining_qty', '>', 0.0), ('state', '=', 'assigned')], context=context)
|
||||
move_ids = ops.product_id.id in products_moves and filter(lambda x: res2[x.id] > 0, products_moves[ops.product_id.id]) or []
|
||||
qty_to_do = ops.product_qty
|
||||
while qty_to_do > 0 and move_ids:
|
||||
move = move_obj.browse(cr, uid, move_ids.pop(), context=context)
|
||||
if move.remaining_qty > qty_to_do:
|
||||
move = move_ids.pop()
|
||||
if res2[move.id] > qty_to_do:
|
||||
qty = qty_to_do
|
||||
qty_to_do = 0
|
||||
else:
|
||||
qty = move.remaining_qty
|
||||
qty_to_do -= move.remaining_qty
|
||||
|
||||
if create and move.location_id.usage != 'internal':
|
||||
# Create quants
|
||||
vals = {
|
||||
'product_id': move.product_id.id,
|
||||
'location_id': move.location_id.id,
|
||||
'qty': qty,
|
||||
#'cost': price_unit,
|
||||
'history_ids': [(4, move.id)],
|
||||
'in_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'company_id': move.company_id.id,
|
||||
'lot_id': ops.lot_id and ops.lot_id.id or False,
|
||||
'owner_id': ops.owner_id and ops.owner_id.id or False,
|
||||
'reservation_id': move.id, #Reserve at once
|
||||
'package_id': ops.result_package_id and ops.result_package_id.id or False,
|
||||
}
|
||||
quant_id = quant_obj.create(cr, uid, vals, context=context)
|
||||
else:
|
||||
#Quants get
|
||||
prefered_order = "reservation_id IS NOT NULL"
|
||||
dom = op_obj._get_domain(cr, uid, ops, context=context)
|
||||
dom = dom + [('reservation_id', 'not in', [x.id for x in picking.move_lines])]
|
||||
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=dom, prefered_order=prefered_order, context=context)
|
||||
quant_obj.quants_reserve(cr, uid, quants, move, context=context)
|
||||
#In the end, move quants in correct package
|
||||
if create:
|
||||
quant_obj.write(cr, uid, [x[0].id for x in quants if x[0] != None], {'package_id': ops.result_package_id and ops.result_package_id.id or False}, context=context)
|
||||
qty = res2[move.id]
|
||||
qty_to_do -= res2[move.id]
|
||||
neg_qty = self._reserve_quants_ops_move(cr, uid, ops, move, qty, create=create, context=context)
|
||||
if neg_qty > 0:
|
||||
resneg[move.id].setdefault(ops.id, 0)
|
||||
resneg [move.id][ops.id] += neg_qty
|
||||
res2[move.id] -= qty
|
||||
res[ops.id] = {}
|
||||
res[ops.id][ops.product_id.id] = qty_to_do
|
||||
elif ops.package_id:
|
||||
quants = ops.package_id.quant_ids #_get_package_lines
|
||||
|
||||
quants = quant_obj.browse(cr, uid, pack_obj.get_content(cr, uid, [ops.package_id.id], context=context))
|
||||
quants = [x for x in quants if x.qty > 0] #Negative quants should not be moved
|
||||
for quant in quants:
|
||||
move_ids = move_obj.search(cr, uid, [('picking_id', '=', picking.id), ('product_id', '=', quant.product_id.id), ('remaining_qty', '>', 0.0)])
|
||||
move_ids = quant.product_id.id in products_moves and filter(lambda x: res2[x.id] > 0, products_moves[quant.product_id.id]) or []
|
||||
qty_to_do = quant.qty
|
||||
while qty_to_do > 0 and move_ids:
|
||||
move = move_obj.browse(cr, uid, move_ids.pop(), context=context)
|
||||
if move.remaining_qty > qty_to_do:
|
||||
move = move_ids.pop()
|
||||
if res2[move.id] > qty_to_do:
|
||||
qty = qty_to_do
|
||||
qty_to_do = 0.0
|
||||
else:
|
||||
qty = move.remaining_qty
|
||||
qty_to_do -= move.remaining_qty
|
||||
qty = res2[move.id]
|
||||
qty_to_do -= res2[move.id]
|
||||
quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context)
|
||||
quant_obj.write(cr, uid, [quant.id], {'reservation_op_id': ops.id}, context=context)
|
||||
res2[move.id] -= qty
|
||||
res.setdefault(ops.id, {}).setdefault(quant.product_id.id, 0.0)
|
||||
res[ops.id][quant.product_id.id] += qty_to_do
|
||||
#Add parent package
|
||||
if create:
|
||||
self.pool.get("stock.quant.package").write(cr, uid, [ops.package_id.id], {'parent_id': ops.result_package_id and ops.result_package_id.id or False}, context=context)
|
||||
return (res, res2)
|
||||
return (res, res2, resneg)
|
||||
|
||||
|
||||
def do_partial(self, cr, uid, picking_ids, context=None):
|
||||
|
@ -946,6 +964,7 @@ class stock_picking(osv.osv):
|
|||
else:
|
||||
#First thing that needs to happen is rereserving the quants
|
||||
res = self.rereserve(cr, uid, [picking.id], create = True, context = context) #This time, quants need to be created
|
||||
resneg = res[2]
|
||||
orig_moves = picking.move_lines
|
||||
orig_qtys = {}
|
||||
for orig in orig_moves:
|
||||
|
@ -957,8 +976,7 @@ class stock_picking(osv.osv):
|
|||
product = self.pool.get('product.product').browse(cr, uid, prod, context=context)
|
||||
qty = res[0][ops][prod]
|
||||
if qty > 0:
|
||||
#TODO: Maybe should try to reserve quants? / Make copy instead of create, but have to put move_dest_id False then e.g. link purchase order line?
|
||||
quant = False
|
||||
#Create moves for products too many on operation
|
||||
move_id = stock_move_obj.create(cr, uid, {
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
|
@ -967,12 +985,17 @@ class stock_picking(osv.osv):
|
|||
'location_id': picking.location_id.id,
|
||||
'location_dest_id': picking.location_dest_id.id,
|
||||
'picking_id': picking.id,
|
||||
'reserved_quant_ids': quant and [(4, quant.id)] or [],
|
||||
'picking_type_id': picking.picking_type_id.id,
|
||||
'group_id': picking.group_id.id,
|
||||
}, context=context)
|
||||
stock_move_obj.action_confirm(cr, uid, [move_id], context=context)
|
||||
move = stock_move_obj.browse(cr, uid, move_id, context=context)
|
||||
ops_rec = self.pool.get("stock.pack.operation").browse(cr, uid, ops, context=context)
|
||||
resneg[move_id] = {}
|
||||
resneg[move_id][ops] = self._reserve_quants_ops_move(cr, uid, ops_rec, move, qty, create=True, context=context)
|
||||
extra_moves.append(move_id)
|
||||
res2 = res[1]
|
||||
#Backorder
|
||||
for move in res2.keys():
|
||||
if res2[move] > 0:
|
||||
mov = stock_move_obj.browse(cr, uid, move, context=context)
|
||||
|
@ -983,13 +1006,12 @@ class stock_picking(osv.osv):
|
|||
orig_moves = [x for x in orig_moves if res[1][x.id] < orig_qtys[x.id]]
|
||||
for move in orig_moves + stock_move_obj.browse(cr, uid, extra_moves, context=context):
|
||||
if move.state == 'draft':
|
||||
self.pool.get('stock.move').action_confirm(cr, uid, [move.id],
|
||||
context=context)
|
||||
self.pool.get('stock.move').action_confirm(cr, uid, [move.id], context=context)
|
||||
todo.append(move.id)
|
||||
elif move.state in ('assigned','confirmed'):
|
||||
todo.append(move.id)
|
||||
if len(todo):
|
||||
self.pool.get('stock.move').action_done(cr, uid, todo, context=context)
|
||||
self.pool.get('stock.move').action_done(cr, uid, todo, negatives = resneg, context=context)
|
||||
picking.refresh()
|
||||
self._create_backorder(cr, uid, picking, context=context)
|
||||
return True
|
||||
|
@ -1634,7 +1656,7 @@ class stock_move(osv.osv):
|
|||
# res[move.id] = [x.id for x in move.reserved_quant_ids]
|
||||
# return res
|
||||
|
||||
def action_done(self, cr, uid, ids, context=None):
|
||||
def action_done(self, cr, uid, ids, negatives = False, context=None):
|
||||
""" Makes the move done and if all moves are done, it will finish the picking.
|
||||
If quants are not assigned yet, it should assign them
|
||||
Putaway strategies should be applied
|
||||
|
@ -1642,13 +1664,20 @@ class stock_move(osv.osv):
|
|||
"""
|
||||
context = context or {}
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
|
||||
ops_obj = self.pool.get("stock.pack.operation")
|
||||
pack_obj = self.pool.get("stock.quant.package")
|
||||
todo = [move.id for move in self.browse(cr, uid, ids, context=context) if move.state == "draft"]
|
||||
if todo:
|
||||
self.action_confirm(cr, uid, todo, context=context)
|
||||
|
||||
pickings = set()
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
# if negatives and negatives[move.id]:
|
||||
# for ops in negatives[move.id].keys():
|
||||
# quants_to_move = [(None, negatives[move.id, x) for x in negatives]
|
||||
# quant_obj.quants_move(cr, uid, quants_to_move, move, context=context)
|
||||
|
||||
|
||||
if move.picking_id:
|
||||
pickings.add(move.picking_id.id)
|
||||
qty = move.product_qty
|
||||
|
@ -1657,17 +1686,33 @@ class stock_move(osv.osv):
|
|||
# quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, context=context)
|
||||
# quant_obj.quants_move(cr, uid, quants, move, location_dest_id, context=context)
|
||||
# should replace the above 2 lines
|
||||
domain = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id), ('qty', '>', 0)]
|
||||
dom = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id), ('qty', '>', 0)]
|
||||
prefered_order = 'reservation_id'
|
||||
# if lot_id:
|
||||
# prefered_order = 'lot_id<>' + lot_id + ", " + prefered_order
|
||||
# if pack_id:
|
||||
# prefered_order = 'pack_id<>' + pack_id + ", " + prefered_order
|
||||
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_order = prefered_order, context=context)
|
||||
#Will move all quants_get and as such create negative quants
|
||||
quant_obj.quants_move(cr, uid, quants, move, context=context)
|
||||
if move.picking_id and move.picking_id.pack_operation_ids:
|
||||
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty - move.remaining_qty, domain=dom, prefered_order = prefered_order, context=context)
|
||||
quant_obj.quants_move(cr, uid, quants, move, context=context)
|
||||
for negative_op in negatives[move.id].keys():
|
||||
ops = ops_obj.browse(cr, uid, negative_op, context=context)
|
||||
negatives[move.id][negative_op] = quant_obj.quants_move(cr, uid, [(None, negatives[move.id][negative_op])], move,
|
||||
lot_id = ops.lot_id and ops.lot_id.id or False,
|
||||
owner_id = ops.owner_id and ops.owner_id.id or False,
|
||||
package_id = ops.package_id and ops.package_id.id or False, context=context)
|
||||
#Packing:
|
||||
reserved_ops = list(set([x.reservation_op_id.id for x in move.reserved_quant_ids]))
|
||||
for ops in ops_obj.browse(cr, uid, reserved_ops, context=context):
|
||||
if ops.product_id:
|
||||
quant_obj.write(cr, uid, [x.id for x in ops.reserved_quant_ids], {'package_id': ops.result_package_id and ops.result_package_id.id or False}, context=context)
|
||||
else:
|
||||
pack_obj.write(cr, uid, [ops.package_id.id], {'parent_id': ops.result_package_id and ops.result_package_id.id or False}, context=context)
|
||||
else:
|
||||
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=dom, prefered_order = prefered_order, context=context)
|
||||
#Will move all quants_get and as such create negative quants
|
||||
quant_obj.quants_move(cr, uid, quants, move, context=context)
|
||||
quant_obj.quants_unreserve(cr, uid, move, context=context)
|
||||
|
||||
#
|
||||
#Check moves that were pushed
|
||||
if move.move_dest_id.state in ('waiting', 'confirmed'):
|
||||
|
@ -2280,6 +2325,17 @@ class stock_package(osv.osv):
|
|||
class stock_pack_operation(osv.osv):
|
||||
_name = "stock.pack.operation"
|
||||
_description = "Packing Operation"
|
||||
|
||||
def _get_remaining_qty(self, cr, uid, ids, 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
|
||||
|
||||
|
||||
_columns = {
|
||||
'picking_id': fields.many2one('stock.picking', 'Stock Picking', help='The stock operation where the packing has been made', required=True),
|
||||
'product_id': fields.many2one('product.product', 'Product', ondelete="CASCADE"), # 1
|
||||
|
@ -2294,6 +2350,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'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
backorder = self.search(cr, uid, [('backorder_id', '=', ref('pick1'))])
|
||||
assert not backorder, ""
|
||||
#Check extra moves created
|
||||
assert len(picking.move_lines) == 1, ""
|
||||
assert len(picking.move_lines) == 1, ""
|
||||
-
|
||||
Make a delivery order of 300 pieces to the customer
|
||||
-
|
||||
|
@ -114,9 +114,9 @@
|
|||
for rec in delivery_id.pack_operation_ids:
|
||||
if rec.package_id.name == 'Pallet 2':
|
||||
lot_ids = self.pool.get("stock.production.lot").search(cr, uid, [('product_id', '=', ref('product1')), ('name','=','Lot A')])
|
||||
stock_pack.write(cr, uid, rec.id, {'product_id': ref('product1'), 'product_qty': 20, 'lot_id': lot_ids[0]}, context=context)
|
||||
stock_pack.write(cr, uid, [rec.id], {'product_id': ref('product1'), 'product_qty': 20, 'lot_id': lot_ids[0]}, context=context)
|
||||
if rec.package_id.name == 'Pallet 3':
|
||||
stock_pack.write(cr, uid, rec.id, {'product_id': ref('product1'),'product_qty': 10}, context=context)
|
||||
stock_pack.write(cr, uid, [rec.id], {'product_id': ref('product1'),'product_qty': 10}, context=context)
|
||||
-
|
||||
Process this picking
|
||||
-
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
-
|
||||
Create a new "negative" stockable product
|
||||
-
|
||||
!record {model: product.product, id: product_neg}:
|
||||
name: Negative product
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
list_price: 100.0
|
||||
standard_price: 70.0
|
||||
seller_ids:
|
||||
- delay: 1
|
||||
name: base.res_partner_2
|
||||
min_qty: 2.0
|
||||
qty: 5.0
|
||||
uom_id: product.product_uom_unit
|
||||
uom_po_id: product.product_uom_unit
|
||||
-
|
||||
Create an incoming picking for this product of 300 PCE from suppliers to stock
|
||||
-
|
||||
!record {model: stock.picking, id: pick_neg}:
|
||||
name: Incoming picking
|
||||
partner_id: base.res_partner_2
|
||||
picking_type_id: picking_type_in
|
||||
move_lines:
|
||||
- product_id: product_neg
|
||||
product_uom_qty: 300.00
|
||||
location_id: stock_location_suppliers
|
||||
location_dest_id: stock_location_stock
|
||||
-
|
||||
Confirm and assign picking and prepare partial
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.action_confirm(cr, uid, [ref('pick_neg')], context=context)
|
||||
self.do_prepare_partial(cr, uid, [ref('pick_neg')], context=context)
|
||||
-
|
||||
Put 120 pieces on Palneg 1 (package), 120 pieces on Palneg 2 with lot A and 60 pieces on Palneg 3
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
#Change quantity of first to 120 and create 2 others quant operations
|
||||
record = self.browse(cr, uid, ref('pick_neg'), context=context)
|
||||
stock_pack = self.pool.get('stock.pack.operation')
|
||||
stock_quant_pack = self.pool.get('stock.quant.package')
|
||||
#create lot A
|
||||
lot_a = self.pool.get('stock.production.lot').create(cr, uid, {'name': 'Lot neg', 'product_id': ref('product_neg')}, context=context)
|
||||
#create package
|
||||
package1 = stock_quant_pack.create(cr, uid, {'name': 'Palneg 1'}, context=context)
|
||||
package2 = stock_quant_pack.create(cr, uid, {'name': 'Palneg 2'}, context=context)
|
||||
package3 = stock_quant_pack.create(cr, uid, {'name': 'Palneg 3'}, context=context)
|
||||
#Create package for each line and assign it as result_package_id
|
||||
#create pack operation
|
||||
stock_pack.write(cr, uid, record.pack_operation_ids[0].id, {'result_package_id': package1, 'product_qty': 120})
|
||||
new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product_neg'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick_neg'), 'lot_id': lot_a, 'result_package_id': package2, 'product_qty': 120}, context=context)
|
||||
new_pack2 = stock_pack.create(cr, uid, {'product_id': ref('product_neg'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick_neg'), 'result_package_id': package3, 'product_qty': 60}, context=context)
|
||||
-
|
||||
Transfer the reception
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.do_partial(cr, uid, [ref('pick_neg')], context=context)
|
||||
-
|
||||
Make a delivery order of 300 pieces to the customer
|
||||
-
|
||||
!record {model: stock.picking, id: delivery_order_neg}:
|
||||
name: outgoing picking
|
||||
partner_id: base.res_partner_4
|
||||
picking_type_id: stock.picking_type_out
|
||||
move_lines:
|
||||
- product_id: product_neg
|
||||
product_uom_qty: 300.00
|
||||
location_id: stock_location_stock
|
||||
location_dest_id: stock_location_customers
|
||||
-
|
||||
Assign and confirm
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.action_confirm(cr, uid, [ref('delivery_order_neg')], context=context)
|
||||
self.action_assign(cr, uid, [ref('delivery_order_neg')])
|
||||
-
|
||||
Instead of doing the 300 pieces, you decide to take pallet 1 (do not mention product in operation here) and 140 pieces from lot A/pallet 2 and 10 pieces from pallet 3
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
stock_pack = self.pool.get('stock.pack.operation')
|
||||
self.do_prepare_partial(cr, uid, [ref('delivery_order_neg')], context=context)
|
||||
delivery_id = self.browse(cr, uid, ref('delivery_order_neg'), context=context)
|
||||
for rec in delivery_id.pack_operation_ids:
|
||||
if rec.package_id.name == 'Palneg 2':
|
||||
lot_ids = self.pool.get("stock.production.lot").search(cr, uid, [('product_id', '=', ref('product_neg')), ('name','=','Lot neg')])
|
||||
stock_pack.write(cr, uid, [rec.id], {'product_id': ref('product_neg'), 'product_qty': 140, 'lot_id': lot_ids[0]}, context=context)
|
||||
if rec.package_id.name == 'Palneg 3':
|
||||
stock_pack.write(cr, uid, [rec.id], {'product_id': ref('product_neg'),'product_qty': 10}, context=context)
|
||||
-
|
||||
Process this picking
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.do_partial(cr, uid, [ref('delivery_order_neg')], context=context)
|
||||
-
|
||||
Check the quants that you have 120 pieces pallet 1 in customers, 100 pieces pallet 2 in stock and 20 with customers and 50 in stock, 10 in customers from pallet 3
|
||||
-
|
||||
!python {model: stock.quant}: |
|
||||
reco_id = self.search(cr ,uid , [('product_id','=',ref('product_neg'))], context=context)
|
||||
for rec in self.browse(cr, uid, reco_id, context=context):
|
||||
if rec.package_id.name == 'Palneg 1' and rec.location_id.id == ref('stock_location_customers'):
|
||||
assert rec.qty == 120, "Should have 120 pieces on pallet 1"
|
||||
elif rec.package_id.name == 'Palneg 2' and rec.location_id.id == ref('stock_location_stock'):
|
||||
assert rec.qty == -20, "Should have -20 pieces in stock on pallet 2"
|
||||
assert rec.lot_id.name == 'Lot neg', "It should have kept its Lot"
|
||||
elif rec.lot_id.name == 'Lot neg' and rec.location_id.id == ref('stock_location_customers'):
|
||||
assert ((rec.qty == 20 or rec.qty == 120) and not rec.package_id), "Should have 140 pieces (120+20) in customer location from pallet 2 and lot A"
|
||||
elif rec.package_id.name == 'Palneg 3' and rec.location_id.id == ref('stock_location_stock'):
|
||||
assert rec.qty == 30 or rec.qty == 20, "Should have 30 and 20 pieces in stock on pallet 3"
|
||||
elif not rec.package_id and not rec.lot_id and rec.location_id.id == ref('stock_location_customers'):
|
||||
assert rec.qty == 10, "Should have 10 pieces in customer location from pallet 3"
|
||||
else:
|
||||
assert False, "Unrecognized quant"
|
|
@ -124,11 +124,13 @@ class stock_quant(osv.osv):
|
|||
else:
|
||||
self._create_account_move_line(cr, uid, quant, move, acc_valuation, acc_dest, journal_id, context=ctx)
|
||||
|
||||
def move_single_quant(self, cr, uid, quant, qty, move, context=None):
|
||||
quant_record = super(stock_quant, self).move_single_quant(cr, uid, quant, qty, move, context=context)
|
||||
|
||||
def move_single_quant(self, cr, uid, quant, qty, move, lot_id=False, owner_id=False, package_id= False, context=None):
|
||||
quant_record = super(stock_quant, self).move_single_quant(cr, uid, quant, qty, move, lot_id = lot_id, owner_id = owner_id, package_id = package_id, context=context)
|
||||
self._account_entry_move(cr, uid, quant_record, move, context=context)
|
||||
return quant_record
|
||||
|
||||
|
||||
def _get_accounting_data_for_valuation(self, cr, uid, move, context=None):
|
||||
"""
|
||||
Return the accounts and journal to use to post Journal Entries for the real-time
|
||||
|
@ -223,9 +225,9 @@ class stock_quant(osv.osv):
|
|||
class stock_move(osv.osv):
|
||||
_inherit = "stock.move"
|
||||
|
||||
def action_done(self, cr, uid, ids, context=None):
|
||||
def action_done(self, cr, uid, ids, negatives = False, context=None):
|
||||
self.product_price_update_before_done(cr, uid, ids, context=context)
|
||||
super(stock_move, self).action_done(cr, uid, ids, context=context)
|
||||
super(stock_move, self).action_done(cr, uid, ids, negatives=negatives, context=context)
|
||||
self.product_price_update_after_done(cr, uid, ids, context=context)
|
||||
|
||||
def _store_average_cost_price(self, cr, uid, move, context=None):
|
||||
|
|
|
@ -68,20 +68,13 @@
|
|||
-
|
||||
!python {model: stock.picking}: |
|
||||
po_id = self.pool.get('purchase.order').search(cr, uid, [('partner_id', '=', ref('supplier_dropship'))])
|
||||
print "Picking_ids", self.pool.get("purchase.order").browse(cr, uid, po_id[0]).picking_ids
|
||||
picking_id = self.search(cr, uid, [('purchase_id', '=', po_id[0])])
|
||||
print "Pick_id", picking_id
|
||||
print [(x.move_lines[0].product_id.name, x.move_lines[0].product_uom_qty) for x in self.browse(cr, uid, picking_id)]
|
||||
self.do_partial(cr, uid, picking_id)
|
||||
-
|
||||
Check one quant was created in Customers location with 200 pieces and one move in the history_ids
|
||||
-
|
||||
!python {model: stock.quant}: |
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
quant_ids = self.search(cr, uid, [('product_id', '=', ref("drop_shop_product"))])
|
||||
print "Quants:", [(x.qty, x.location_id.name, x.in_date, x.cost) for x in quant_obj.browse(cr, uid, quant_ids)]
|
||||
quant_ids = self.search(cr, uid, [('location_id', '=', ref('stock.stock_location_customers')),('qty', '=', 200), ('product_id', '=', ref("drop_shop_product"))])
|
||||
print "Quants", quant_ids
|
||||
assert quant_ids, 'No Quant found'
|
||||
assert len(quant_ids) == 1
|
||||
assert len(self.browse(cr, uid, quant_ids)[0].history_ids) == 1
|
||||
|
|
Loading…
Reference in New Issue