[FIX] stock

bzr revid: fp@tinyerp.com-20130730074615-m2wb764f384y449c
This commit is contained in:
Fabien Pinckaers 2013-07-30 09:46:15 +02:00
parent de82d85c4f
commit a70fd162ce
1 changed files with 86 additions and 168 deletions

View File

@ -572,6 +572,9 @@ class stock_picking(osv.osv):
move_obj.unlink(cr, uid, ids2, ctx)
return super(stock_picking, self).unlink(cr, uid, ids, context=context)
# Methods for partial pickings
def _create_backorder(self, cr, uid, picking, context=None):
sequence_obj = self.pool.get('ir.sequence')
new_picking_name = picking.name
@ -616,121 +619,72 @@ class stock_picking(osv.osv):
'cost': move.product_id.standard_price,
}, context=context)
# FP Note: review all methods bellow this line for stock.picking
def make_packaging(self, cr, uid, picking_id, todo_move_ids, context=None):
quant_obj = self.pool.get('stock.quant')
package_obj = self.pool.get('stock.quant.package')
picking = self.browse(cr, uid, picking_id, context=context)
op_todo = []
for op in picking.pack_operation_ids:
if not op.result_package_id:
continue
if op.package_id:
#pack existing packs
package_obj.write(cr, uid, op.package_id.id, {'parent_id': op.result_package_id.id}, context=context)
if op.quant_id:
#if the quant was split, work on the new quant because the original quant may be referenced in another operation
quant = quant_obj._quant_split(cr, uid, op.quant_id, op.quant_id.qty - op.product_qty, context=context) or op.quant_id
quant_obj.write(cr, uid, quant.id, {'package_id': op.result_package_id.id}, context=context)
if op.product_id:
#this kind of operation has the lowest priority so we delay its resolution untill all others are done
op_todo.append(op)
for op in op_todo:
all_picking_quants = []
#make the list of all quants involved in this picking
for move in picking.move_lines:
all_picking_quants += [q.id for q in move.quant_ids]
quants = quant_obj.quants_get(cr, uid, picking.location_dest_id, op.product_id, op.product_qty, domain=[('id', 'in', all_picking_quants), ('package_id', '=', False)], context=context)
quant_ids = []
for quant, qty in quants:
quant_obj._quant_split(cr, uid, quant, qty, context=context)
quant_ids.append(quant.id)
quant_obj.write(cr, uid, quant_ids, {'package_id': op.result_package_id.id}, context=context)
def _find_moves_from_product(self, cr, uid, picking_id, product_id, qty_to_dispatch, context=None):
def _do_partial_product_move(self, cr, uid, picking, product, qty, quant=False, context=None):
moves = []
stock_move_obj = self.pool.get('stock.move')
move_ids = stock_move_obj.search(cr, uid, [('picking_id', '=', picking_id), ('product_id', '=', product_id), ('state', 'not in', ['cancel', 'done'])], limit=1, context=context)
res = []
for move in stock_move_obj.browse(cr, uid, move_ids, context=context):
res += [(move.id, min(move.product_qty, qty_to_dispatch))]
qty_to_dispatch -= move.product_qty
if qty_to_dispatch > 0:
res += [(None, qty_to_dispatch)]
return res
for move in picking.move_lines:
if move.state in ('cancel','done'): continue
if move.product_id.id == product.id:
todo = min(move.product_qty, qty)
partial_datas = {
'product_qty': todo,
'product_uom_id': quant.product_id.uom_id.id,
'reserved_quant_ids': [(4, quant.id)],
}
newmove_id = stock_move_obj.split(cr, uid, move_id, todo, context=context)
stock_move_obj.action_done(cr, uid, [newmove_id], context=context)
def _create_unexpected_move(self, cr, uid, picking, product, qty, cost, quant=False, context=None):
stock_move_obj = self.pool.get('stock.move')
#TODO fix me
move_name = 'stock.picking.in'
return stock_move_obj.create(cr, uid, {'name': move_name,
'product_id': product.id,
'product_uom_qty': qty,
'product_uom': product.uom_id.id,
#'lot_id': wizard_line.lot_id.id,
'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 [],
#TODO fix me
'picking_type_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'picking_type_internal')[1],
'price_unit': cost}, context=context)
# TODO: To be removed after new API implementation
move.refresh()
moves.append(move)
qty -= todo
if qty<=0: break
if qty>0:
move_id = stock_move_obj.create(cr, uid, {
'name': product.name,
'product_id': product.id,
'product_uom_qty': qty,
'product_uom': product.uom_id.id,
'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
}, context=context)
stock_move_obj.action_done(cr, uid, [move_id], context=context)
move = stock_move_obj.browse(cr, uid, move_id, context=context)
moves.append(move)
return moves
def do_partial(self, cr, uid, picking_ids, context=None):
""" Makes partial picking based on pack_operation_ids field.
:param only_split_line: boolen that depicts if the moves should not be set to done.
"""
#TODO: this variable should be in argument
only_split_lines = False
stock_move_obj = self.pool.get('stock.move')
quant_obj = self.pool.get('stock.quant')
quant_package_obj = self.pool.get('stock.quant.package')
todo_move_ids = []
# FP Note: remove this constraint
assert len(picking_ids) == 1, 'Partial picking is only supported for one record at a time'
picking = self.browse(cr, uid, picking_ids[0], context=context)
for op in picking.pack_operation_ids:
if op.package_id:
#moving a package that includes several quants
included_package_ids = quant_package_obj.search(cr, uid, [('parent_id', 'child_of', [op.package_id.id])], context=context)
included_quant_ids = quant_obj.search(cr, uid, [('package_id', 'in', included_package_ids)], context=context)
for quant in quant_obj.browse(cr, uid, included_quant_ids, context=context):
qty_to_dispatch = quant.qty * op.product_qty
for move_id, qty in self._find_moves_from_product(cr, uid, picking.id, quant.product_id.id, qty_to_dispatch, context=context):
if not move_id:
move_id = self._create_unexpected_move(cr, uid, picking, quant.product_id, qty, quant.cost, quant, context=context)
todo_move_ids += [move_id]
else:
partial_datas = {
'product_qty': qty,
'product_uom_id': quant.product_id.uom_id.id,
'cost': quant.cost,
'reserved_quant_ids': [(4, quant.id)],
}
todo_move_ids += [stock_move_obj.do_partial(cr, uid, move_id, partial_datas, context=context)]
else:
product = op.quant_id and op.quant_id.product_id or op.product_id
cost = op.quant_id and op.quant_id.cost or op.product_id.standard_price
reserved_quant_id = op.quant_id and op.quant_id.id or False
for move_id, qty in self._find_moves_from_product(cr, uid, picking.id, product.id, op.product_qty, context=context):
if not move_id:
#product not expected
move_id = self._create_unexpected_move(cr, uid, picking, product, qty, cost, quant=reserved_quant_id, context=context)
todo_move_ids += [move_id]
else:
partial_datas = {
'product_qty': qty,
'product_uom_id': op.product_uom_id.id,
'cost': op.cost,
'reserved_quant_id': [(4, reserved_quant_id)]
}
todo_move_ids += [stock_move_obj.do_partial(cr, uid, move_id, partial_datas, context=context)]
if not only_split_lines and todo_move_ids:
stock_move_obj.action_done(cr, uid, todo_move_ids, context=context)
#if there was a backorder, the picking to package may be different
picking_to_package = stock_move_obj.browse(cr, uid, todo_move_ids[0], context=context).picking_id
self.make_packaging(cr, uid, picking_to_package.id, todo_move_ids, context=context)
for picking in self.browse(cr, uid, picking_ids, context=context):
for op in picking.pack_operation_ids:
if op.package_id:
for quant in quant_package_obj.quants_get(cr, uid, op.package_id, context=context):
self._do_partial_product_move(cr, uid, picking, quant.product_id, quant.qty, quant, context=context)
op.package_id.write(cr, uid, {
'parent_id': op.result_package_id.id
}, context=context)
elif op.product_id:
moves = self._do_partial_product_move(cr, uid, picking, op.product_id, op.qty, op.quant_id, context=context)
quants = []
for m in moves:
for quant in m.quant_ids:
quants.append(quand.id)
quand_obj.write(cr, uid, quants, {
'package_id': op.result_package_id.id
}, context=context)
self._create_backorder(cr, uid, picking, context=context)
return True
# Methods for the barcode UI
def _get_picking_for_packing_ui(self, cr, uid, context=None):
res = self.search(cr, uid, [('state', '=', 'assigned')], limit=1, context=context)
@ -940,14 +894,13 @@ class stock_move(osv.osv):
# used for colors in tree views:
'scrapped': fields.related('location_dest_id','scrap_location',type='boolean',relation='stock.location',string='Scrapped', readonly=True),
'picking_type_id': fields.related('picking_id', 'picking_type_id', type='many2one', relation='stock.picking.type', string="Picking type" ,readonly=True),
'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Quants'),
'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'),
'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)]}),
'group_id': fields.many2one('procurement.group', 'Procurement Group'),
'rule_id': fields.many2one('procurement.rule', 'Procurement Rule'),
'propagate': fields.boolean('Propagate cancel and split', help='If checked, when this move is cancelled, cancel the linked move too'),
'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', required=True),
'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type'),
}
def copy(self, cr, uid, id, default=None, context=None):
@ -1320,12 +1273,6 @@ class stock_move(osv.osv):
if not other_upstream_move_ids and move.move_dest_id.state in ('waiting', 'confirmed'):
self.action_assign(cr, uid, [move.move_dest_id.id], context=context)
self.write(cr, uid, ids, {'state': 'done', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
# FP Note: I would move this code to the do_partial of stock.picking?
#if all moves of a picking aren't done, we create a backorder
for picking in picking_obj.browse(cr, uid, list(pickings), context=context):
if picking.state != 'done':
picking_obj._create_backorder(cr, uid, picking, context=context)
return True
def unlink(self, cr, uid, ids, context=None):
@ -1536,68 +1483,40 @@ class stock_move(osv.osv):
# product_avail[product.id] += product_uom_qty
# return res
def do_partial(self, cr, uid, move_id, partial_datas, context=None):
def split(self, cr, uid, move, qty, context=None):
""" Partially (or not) moves a stock.move.
@param partial_datas: Dictionary containing details of partial picking
like partner_id, delivery_date, delivery
moves with product_id, product_qty, uom
"""
if move.product_qty==qty:
return move
if (move.product_qty < qty) or (qty==0):
return False
uom_obj = self.pool.get('product.uom')
context = context or {}
if context is None:
context = {}
uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, qty, move.product_uom_id.id)
uos_qty = uom_qty * move.product_uos_qty / move.product_uom_qty
move = self.browse(cr, uid, move_id, context=context)
product_uom_qty = partial_datas.get('product_qty', 0.0)
product_uom = partial_datas.get('product_uom_id')
#lot_id = partial_datas.get('lot_id')
if move.state in ('done', 'cancel') or product_uom_qty == 0:
return
#TODO add back these constraint checks
##Compute the quantity for respective wizard_line in the line uom (this jsut do the rounding if necessary)
#qty_in_line_uom = uom_obj._compute_qty(cr, uid, line_uom.id, wizard_line.quantity, line_uom.id)
raise osv.except_osv(_('Error'), _('You cannot split a move done'))
#if line_uom.factor and line_uom.factor <> 0:
# if float_compare(qty_in_line_uom, wizard_line.quantity, precision_rounding=line_uom.rounding) != 0:
# raise osv.except_osv(_('Warning!'), _('The unit of measure rounding does not allow you to ship "%s %s", only rounding of "%s %s" is accepted by the Unit of Measure.') % (wizard_line.quantity, line_uom.name, line_uom.rounding, line_uom.name))
##Check rounding Quantity.ex.
##picking: 1kg, uom kg rounding = 0.01 (rounding to 10g),
##partial delivery: 253g
##=> result= refused, as the qty left on picking would be 0.747kg and only 0.75 is accepted by the uom.
#initial_uom = wizard_line.move_id.product_uom
##Compute the quantity for respective wizard_line in the initial uom
#qty_in_initial_uom = uom_obj._compute_qty(cr, uid, line_uom.id, wizard_line.quantity, initial_uom.id)
#without_rounding_qty = (wizard_line.quantity / line_uom.factor) * initial_uom.factor
#if float_compare(qty_in_initial_uom, without_rounding_qty, precision_rounding=initial_uom.rounding) != 0:
# raise osv.except_osv(_('Warning!'), _('The rounding of the initial uom does not allow you to ship "%s %s", as it would let a quantity of "%s %s" to ship and only roundings of "%s %s" are accepted by the uom.') % (wizard_line.quantity, line_uom.name, wizard_line.move_id.product_qty - without_rounding_qty, initial_uom.name, initial_uom.rounding, initial_uom.name))
##Quantity must be Positive
#if wizard_line.quantity < 0:
# raise osv.except_osv(_('Warning!'), _('Please provide proper Quantity.'))
defaults = {
'product_uom_qty': uom_qty,
'product_uos_qty': uos_qty,
'state': move.state,
'reserved_quant_ids': []
}
new_move = self.copy(cr, uid, move.id, defaults)
#partial_qty is the quantity processed in the normalized product uom
partial_qty = uom_obj._compute_qty(cr, uid, product_uom, product_uom_qty, move.product_id.uom_id.id)
if move.product_qty > partial_qty:
defaults = {
'product_uom_qty': product_uom_qty,
'product_uos_qty': product_uom_qty,
'state': 'assigned',
'move_dest_id': False,
'price_unit': partial_datas.get('cost', 0.0),
'reserved_quant_ids': partial_datas.get('reserved_quant_ids', [])
}
new_move = self.copy(cr, uid, move.id, defaults)
todo_move_id = new_move
self.write(cr, uid, [move.id], {'product_uom_qty': move.product_qty - product_uom_qty,
'product_uos_qty': move.product_qty - product_uom_qty,
'lot_id': False,
'tracking_id': False,
'reserved_quant_ids': []})
else:
todo_move_id = move.id
#TODO LOT management
#self.write(cr, uid, [todo_move_id], {'prefered_lot_ids': })
return todo_move_id
self.write(cr, uid, [move.id], {
'product_uom_qty': move.product_uom_qty - uom_qty,
'product_uos_qty': move.product_uos_qty - uos_qty,
'reserved_quant_ids': []
}, context=context)
return new_move
def get_type_from_usage(self, cr, uid, location, location_dest, context=None):
'''
@ -1848,7 +1767,6 @@ class stock_package(osv.osv):
'quant_ids': fields.one2many('stock.quant', 'package_id', 'Bulk Content'),
'parent_id': fields.many2one('stock.quant.package', 'Container Package', help="The package containing this item"),
'children_ids': fields.one2many('stock.quant.package', 'parent_id', 'Contained Packages'),
}
_defaults = {
'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack')
@ -1877,12 +1795,12 @@ class stock_package(osv.osv):
'datas': datas
}
def quants_get(self, cr, uid, package_record, context=None):
def quants_get(self, cr, uid, package, context=None):
''' find all the quants in the given package (browse record) recursively'''
res = []
for child in package_record.children_ids:
for child in package.children_ids:
res += self.quants_get(cr, uid, child, context=context)
res += [qt.id for qt in package_record.quant_ids]
res += package.quant_ids
return res
def _get_product_total_qty(self, cr, uid, package_record, product_id, context=None):