[FIX] several fixes: packaging, do_partial, backorder creation, quants_get....

bzr revid: qdp-launchpad@openerp.com-20130726125532-zrjr08is2knsd8hu
This commit is contained in:
Quentin (OpenERP)
2013-07-26 14:55:32 +02:00
parent ed99377306
commit f0298ffbdc
2 changed files with 100 additions and 128 deletions
+99 -126
View File
@@ -334,7 +334,7 @@ class stock_quant(osv.osv):
if quantity >= abs(quant.qty):
res += [(quant, abs(quant.qty))]
quantity -= abs(quant.qty)
else:
elif quantity != 0:
res += [(quant, quantity)]
quantity = 0
break
@@ -557,6 +557,21 @@ class stock_picking(osv.osv):
self.pool.get('stock.move').cancel_assign(cr, uid, move_ids)
return True
def _create_backorder(self, cr, uid, picking, context=None):
sequence_obj = self.pool.get('ir.sequence')
new_picking_name = picking.name
#TODO back_order_name is False currently => find why
back_order_name = sequence_obj.get(cr, uid, 'stock.picking') #TODO: Need to have sequence for every picking type
self.write(cr, uid, [picking.id], {'name': back_order_name})
backorder_id = self.copy(cr, uid, picking.id, {
'name': new_picking_name,
'move_lines': [], # the move_lines are linked after
'state': 'done' # the backorder is already created as 'done' because it is created only after the action_done of stock moves
})
self.message_post(cr, uid, picking.id, body=_("Back order <em>%s</em> has been <b>created</b>.") % (back_order_name), context=context)
unlink_operation_order = [(2, op.id) for op in picking.pack_operation_ids]
self.write(cr, uid, [picking.id], {'backorder_id': backorder_id, 'pack_operation_ids': unlink_operation_order}, context=context)
return backorder_id
def action_cancel(self, cr, uid, ids, context=None):
""" Changes picking state to cancel.
@@ -654,12 +669,18 @@ class stock_picking(osv.osv):
quant_ids.append(quant.id)
quant_obj.write(cr, uid, quant_ids, {'package_id': op.result_package_id.id}, context=context)
def _find_move_from_product(self, cr, uid, picking_id, product_id, context=None):
def _find_moves_from_product(self, cr, uid, picking_id, product_id, qty_to_dispatch, context=None):
stock_move_obj = self.pool.get('stock.move')
move_id = stock_move_obj.search(cr, uid, [('picking_id', '=', picking_id), ('product_id', '=', product_id), ('state', 'not in', ['cancel', 'done'])], limit=1, context=context)
return move_id and move_id[0] or False
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
def _create_unexpected_move(self, cr, uid, picking, product, qty, cost, context=None):
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'
@@ -670,7 +691,8 @@ class stock_picking(osv.osv):
#'lot_id': wizard_line.lot_id.id,
'location_id': picking.location_id.id,
'location_dest_id': picking.location_dest_id.id,
'picking_id': False,
'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)
@@ -684,59 +706,48 @@ class stock_picking(osv.osv):
stock_move_obj = self.pool.get('stock.move')
quant_obj = self.pool.get('stock.quant')
quant_package_obj = self.pool.get('stock.quant.package')
proc_group = self.pool.get("procurement.group")
group_id = proc_group.create(cr, uid, {}, context=context)
todo_move_ids = []
assert len(picking_ids) == 1, 'Partial picking is only supported for one record at a time'
if context is None:
context = {}
picking = self.browse(cr, uid, picking_ids[0], context=context)
need_backorder = False
#first check that a backorder needs to be created or not
for move in picking.move_lines:
if move.remaining_qty != 0:
need_backorder = True
break
if need_backorder:
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):
move_id = self._find_move_from_product(cr, uid, picking.id, quant.product_id.id, context=context)
partial_datas = {
'product_qty': quant.qty * op.product_qty,
'product_uom_id': quant.product_id.uom_id.id,
'cost': quant.cost,
}
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, op.product_qty * quant.qty, quant.cost, context=context)
todo_move_ids += [stock_move_obj.do_partial(cr, uid, move_id, partial_datas, group_id, context=context)]
else:
move_id = op.move_id.id
partial_datas = {
'product_qty': op.product_qty,
'product_uom_id': op.product_uom_id.id,
'cost': op.cost,
}
move_id = self._create_unexpected_move(cr, uid, picking, quant.product_id, qty, quant.cost, quant, context=context)
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:
if op.product_id:
#product not expected
move_id = self._create_unexpected_move(cr, uid, picking, op.product_id, op.product_qty, op.product_id.standard_price, context=context)
elif op.quant_id:
#quant not expected
move_id = self._create_unexpected_move(cr, uid, picking, op.quant_id.product_id, op.product_qty, op.quant_id.cost, context=context)
todo_move_ids += [stock_move_obj.do_partial(cr, uid, move_id, partial_datas, group_id, context=context)]
else:
todo_move_ids = [m.id for m in picking.move_lines]
if need_backorder:
ctx = context.copy()
ctx.update({'backorder_of': picking.id})
stock_move_obj.action_confirm(cr, uid, todo_move_ids, context=ctx)
#product not expected
move_id = self._create_unexpected_move(cr, uid, picking, product, qty, cost, quant=reserved_quant_id, context=context)
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)
@@ -1264,40 +1275,23 @@ class stock_move(osv.osv):
context = {}
# TODO: Put the move in the right picking according to group_id -> should be more elaborated (draft is nok) and picking should be confirmed
pick_obj = self.pool.get("stock.picking")
sequence_obj = self.pool.get('ir.sequence')
picks = pick_obj.search(cr, uid, [('group_id', '=', move.group_id.id), ('location_id', '=', move.location_id.id),
('location_dest_id', '=', move.location_dest_id.id), ('state', 'in', ['confirmed', 'waiting', 'draft'])], context=context)
if picks:
pick = picks[0]
else:
if context.get('backorder_of'):
#copy the original picking
original_picking = pick_obj.browse(cr, uid, context.get('backorder_of'), context=context)
new_picking_name = original_picking.name
#TODO back_order_name is False currently => find why
back_order_name = sequence_obj.get_by_id(cr, uid, original_picking.picking_type_id.id, 'id') #TODO: Need to have sequence for every picking type
pick_obj.write(cr, uid, [original_picking.id], {'name': back_order_name})
pick = pick_obj.copy(cr, uid, original_picking.id, {'name': new_picking_name,
'move_lines': [],
'state': 'draft'
})
pick_obj.message_post(cr, uid, original_picking.id, body=_("Back order <em>%s</em> has been <b>created</b>.") % (back_order_name), context=context)
unlink_operation_order = [(2, op.id) for op in original_picking.pack_operation_ids]
pick_obj.write(cr, uid, [original_picking.id], {'backorder_id': pick, 'pack_operation_ids': unlink_operation_order}, context=context)
else:
#a backorder picking doesn't exist yet, create a new one
values = {'origin': move.origin,
'company_id': move.company_id and move.company_id.id or False,
'move_type': 'one',
'partner_id': move.partner_id and move.partner_id.id or False,
'date_done': move.date_expected,
#'invoice_state': move.invoice_state
'state': 'confirmed',
'group_id': move.group_id and move.group_id.id or False,
'picking_type_id': move.rule_id and move.rule_id.picking_type_id.id or False,
}
pick = pick_obj.create(cr, uid, values, context=context)
#a picking doesn't exist yet, create a new one
values = {'origin': move.origin,
'company_id': move.company_id and move.company_id.id or False,
'move_type': 'one',
'partner_id': move.partner_id and move.partner_id.id or False,
'date_done': move.date_expected,
#'invoice_state': move.invoice_state
'state': 'confirmed',
'group_id': move.group_id and move.group_id.id or False,
'picking_type_id': move.rule_id and move.rule_id.picking_type_id.id or False,
}
pick = pick_obj.create(cr, uid, values, context=context)
return pick
def onchange_date(self, cr, uid, ids, date, date_expected, context=None):
@@ -1402,15 +1396,15 @@ class stock_move(osv.osv):
self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'})
return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False})
def _get_quants_from_pack(self, cr, uid, ids, context=None):
"""
Suppose for the moment we don't have any packaging
"""
res = {}
for move in self.browse(cr, uid, ids, context=context):
#Split according to pack wizard if necessary
res[move.id] = [x.id for x in move.reserved_quant_ids]
return res
#def _get_quants_from_pack(self, cr, uid, ids, context=None):
# """
# Suppose for the moment we don't have any packaging
# """
# res = {}
# for move in self.browse(cr, uid, ids, context=context):
# #Split according to pack wizard if necessary
# res[move.id] = [x.id for x in move.reserved_quant_ids]
# return res
def action_done(self, cr, uid, ids, context=None):
""" Makes the move done and if all moves are done, it will finish the picking.
@@ -1426,7 +1420,10 @@ class stock_move(osv.osv):
if todo:
self.action_confirm(cr, uid, todo, context=context)
pickings = set()
for move in self.browse(cr, uid, ids, context=context):
if move.picking_id:
pickings.add(move.picking_id.id)
qty = move.product_uom_qty
# for qty, location_id in move_id.prefered_location_ids:
@@ -1449,6 +1446,12 @@ class stock_move(osv.osv):
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)
#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':
backorder_id = picking_obj._create_backorder(cr, uid, picking, context=context)
done_move_ids = [x.id for x in picking.move_lines if x.state == 'done']
self.write(cr, uid, done_move_ids, {'picking_id': backorder_id}, context=context)
return True
def unlink(self, cr, uid, ids, context=None):
@@ -1662,7 +1665,7 @@ class stock_move(osv.osv):
# product_avail[product.id] += product_uom_qty
# return res
def do_partial(self, cr, uid, move_id, partial_datas, move_group_id, context=None):
def do_partial(self, cr, uid, move_id, partial_datas, context=None):
""" Partially (or not) moves a stock.move.
@param partial_datas: Dictionary containing details of partial picking
like partner_id, delivery_date, delivery
@@ -1706,20 +1709,19 @@ class stock_move(osv.osv):
defaults = {
'product_uom_qty': product_uom_qty,
'product_uos_qty': product_uom_qty,
'picking_id': False,
'group_id': move_group_id,
'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})
'tracking_id': False,
'reserved_quant_ids': []})
else:
self.write(cr, uid, [move.id], {'product_uom_qty': move.product_uom_qty, 'product_uos_qty': move.product_uom_qty, 'group_id': move_group_id, 'picking_id': False})
todo_move_id = move.id
#TODO LOT management
@@ -2007,18 +2009,6 @@ class stock_package(osv.osv):
'datas': datas
}
## FP Note: why not just over ridding the copy method?
#def action_copy(self, cr, uid, ids, context=None):
# stock_operation_obj = self.pool.get('stock.pack.operation')
# #search all the operations of given package
# operation_ids = stock_operation_obj.search(cr, uid, [('result_package_id', 'in', ids)], context=context)
# #create a new empty stock.quant.package
# package_id = self.create(cr, uid, {}, context=context)
# new_ops = []
# #copy all operation and set the newly created package as result_package_id
# for op in operation_ids:
# new_ops += [stock_operation_obj.copy(cr, uid, op, {'result_package_id': package_id, 'quant_ids': []}, context=context)]
def quants_get(self, cr, uid, package_record, context=None):
''' find all the quants in the given package (browse record) recursively'''
res = []
@@ -2052,7 +2042,6 @@ class stock_pack_operation(osv.osv):
'result_package_id': fields.many2one('stock.quant.package', 'Container Package', help="If set, the operations are packed into this package", required=False, ondelete='cascade'),
'date': fields.datetime('Date', required=True),
#'lot_id': fields.many2one('stock.production.lot', 'Serial Number', ondelete='CASCADE'),
'move_id': fields.many2one('stock.move', "Move"),
#'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'),
@@ -2080,12 +2069,13 @@ class stock_pack_operation(osv.osv):
:param key: tuple directly reusable in a domain
context can receive a key 'current_package_id' with the package to consider for this operation
returns the update to do in stock.move one2many field of picking (adapt remaining quantities) and to the list of package in the classic one2many syntax
returns True
previously: returns the update to do in stock.move one2many field of picking (adapt remaining quantities) and to the list of package in the classic one2many syntax
(0, 0, { values }) link to a new record that needs to be created with the given values dictionary
(1, ID, { values }) update the linked record with id = ID (write *values* on it)
(2, ID) remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
'''
stock_move_obj = self.pool.get('stock.move')
quant_obj = self.pool.get('stock.quant')
if context is None:
context = {}
@@ -2098,41 +2088,24 @@ class stock_pack_operation(osv.osv):
operation_id = existing_operation_ids[0]
qty = self.browse(cr, uid, operation_id, context=context).product_qty + 1
self.write(cr, uid, operation_id, {'product_qty': qty}, context=context)
todo_on_operations = [(1, operation_id, {'product_qty': qty})]
else:
#no existing operation found for the given key and picking => create a new one
var_name, dummy, value = key
uom_id = False
move_id = False
if var_name == 'product_id':
uom_id = self.pool.get('product.product').browse(cr, uid, value, context=context).uom_id.id
move_id = picking_obj._find_move_from_product(cr, uid, picking_id, value, context=context)
elif var_name == 'quant_id':
quant = quant_obj.browse(cr, uid, value, context=context)
uom_id = quant.product_id.uom_id.id
move_id = picking_obj._find_move_from_product(cr, uid, picking_id, quant.product_id.id, context=context)
move_id = move_id and move_id[0] or False
values = {
'picking_id': picking_id,
var_name: value,
'product_qty': 1,
'product_uom_id': uom_id,
'move_id': move_id
}
operation_id = self.create(cr, uid, values, context=context)
values.update({'id': operation_id})
todo_on_operations = [(0, 0, values)]
todo_on_moves = []
product_ids = self._find_product_ids(cr, uid, operation_id, context=context)
for product_id in product_ids:
corresponding_move_ids = stock_move_obj.search(cr, uid, [('picking_id', '=', picking_id), ('product_id', '=', product_id)], context=context)
if corresponding_move_ids:
corresponding_move = stock_move_obj.browse(cr, uid, corresponding_move_ids[0], context=context)
todo_on_moves += [(1, corresponding_move.id, {'remaining_qty': corresponding_move.remaining_qty - 1})]
else:
#TODO: no move found for the given operation => decide what to do.. raise an error?
pass
return todo_on_moves, todo_on_operations
return True
class stock_warehouse_orderpoint(osv.osv):
"""
+1 -2
View File
@@ -587,7 +587,7 @@
<!-- <button name="check_assign" states="confirmed" string="Check Availability" type="object"/> -->
<button name="force_assign" states="confirmed" string="Force Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="do_partial" states="assigned" string="Confirm &amp; Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
<button name="action_pack" states="draft, assigned" string="Create Package" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
<button name="action_pack" states="draft,assigned,confirmed" string="Create Package" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
<button name="%(act_stock_return_picking)d" string="Reverse Transfer" states="done" type="action" groups="base.group_user"/>
<button name="button_cancel" states="assigned,confirmed,draft" string="Cancel Transfer" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="draft,assigned,done" statusbar_colors='{"shipping_except":"red","invoice_except":"red","waiting_date":"blue"}'/>
@@ -617,7 +617,6 @@
<field name="product_uom_id"/>
<field name="quant_id"/>
<field name="package_id"/>
<field name="move_id" domain="[('picking_id', '=', parent.id)]"/>
<field name="product_qty"/>
<field name="result_package_id"/>
</tree>