[WIP] pack operations + do_partial refactored again

bzr revid: qdp-launchpad@openerp.com-20130723153424-9179x8burkch0ap0
This commit is contained in:
Quentin (OpenERP) 2013-07-23 17:34:24 +02:00
parent 2525f33104
commit ffa06fc422
10 changed files with 200 additions and 791 deletions

View File

@ -69,8 +69,6 @@ Dashboard / Reports for Warehouse Management will include:
'stock_data.xml',
'wizard/stock_move_view.xml',
'wizard/stock_change_product_qty_view.xml',
'wizard/stock_partial_picking_view.xml',
'wizard/stock_partial_move_view.xml',
'wizard/stock_fill_inventory_view.xml',
'wizard/stock_inventory_merge_view.xml',
'wizard/stock_location_product_view.xml',

View File

@ -23,10 +23,9 @@ from openerp import tools
from openerp.osv import fields,osv
from openerp.addons.decimal_precision import decimal_precision as dp
# FP Note: TODO: drop this table and use the stock.move table instead
# FP Note: TODO: move in stock.py
class stock_quant(osv.osv):
_inherit = "stock.quant"
_description = "Stock Statistics"
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
res = super(stock_quant, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
product_obj = self.pool.get("product.product")

View File

@ -49,7 +49,7 @@ function openerp_picking_widgets(instance){
cols: {
product: op.product_id[1],
uom: op.product_uom ? product_uom[1] : '',
qty: op.product_uom_qty,
qty: op.product_qty,
}
});
});
@ -247,7 +247,7 @@ function openerp_picking_widgets(instance){
var self = this;
console.log('Copy Package:',package_id);
new instance.web.Model('stock.quant.package')
.call('action_copy',[[package_id]])
.call('copy',[[package_id]])
.then(function(){
return self.refresh_ui(self.picking.id);
});

View File

@ -220,8 +220,8 @@ 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_id: child_of this location_id
:product_id: id of product
:location: browse record of the parent location in which the quants have to be found
:product: browse record of the product to find
:qty in UoM of product
:lot_id NOT USED YET !
"""
@ -234,7 +234,6 @@ class stock_quant(osv.osv):
elif removal_strategy=='lifo':
result += self._quants_get_lifo(cr, uid, location, product, qty, domain, prefered_order=prefered_order, context=context)
else:
print removal_strategy
raise osv.except_osv(_('Error!'), _('Removal strategy %s not implemented.' % (removal_strategy,)))
return result
@ -303,7 +302,7 @@ class stock_quant(osv.osv):
if not quant_neg:
continue
result = True
to_solve_quant = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id)], context=context)
to_solve_quant = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id), ('id', '!=', quant.id)], context=context)
if not to_solve_quant:
continue
to_solve_quant = self.browse(cr, uid, to_solve_quant[0], context=context)
@ -372,7 +371,6 @@ class stock_quant(osv.osv):
def _location_owner(self, cr, uid, quant, location, context=None):
return location and (location.usage == 'internal') and location.company_id or False
#----------------------------------------------------------
# Stock Picking
#----------------------------------------------------------
@ -493,24 +491,6 @@ class stock_picking(osv.osv):
_sql_constraints = [
('name_uniq', 'unique(name, company_id)', 'Reference must be unique per Company!'),
]
def action_process(self, cr, uid, ids, context=None):
if context is None:
context = {}
"""Open the partial picking wizard"""
context.update({
'active_model': self._name,
'active_ids': ids,
'active_id': len(ids) and ids[0] or False
})
return {
'view_type': 'form',
'view_mode': 'form',
'res_model': 'stock.partial.picking',
'type': 'ir.actions.act_window',
'target': 'new',
'context': context,
'nodestroy': True,
}
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
@ -587,7 +567,7 @@ class stock_picking(osv.osv):
@return: True
"""
self.draft_force_assign(cr, uid, ids)
return self.action_process(cr, uid, ids, context=context)
return self.do_partial(cr, uid, ids, context=context)
def cancel_assign(self, cr, uid, ids, *args):
""" Cancels picking and moves.
@ -664,223 +644,121 @@ class stock_picking(osv.osv):
# FP Note: review all methods aboce this line for stock.picking
#TODO move this in another class?
def get_done_reserved_quants(self, cr, uid, picking_id, move, context=None):
stock_operation_obj = self.pool.get('stock.pack.operation')
quant_obj = self.pool.get('stock.quant')
operation_ids = stock_operation_obj.find_packaging_op_from_product(cr, uid, move.product_id, picking_id, context=context)
todo_later = []
possible_quants = [quant.id for quant in move.reserved_quant_ids]
done_reserved_quants = set()
for op in stock_operation_obj.browse(cr, uid, operation_ids, context=context):
if op.product_id:
#TODO: document me
todo_later += [op.id]
elif op.quant_id:
#split for partial and take care of reserved quants
quant_tuples = quant_obj._get_quant_tuples(cr, uid, [op.quant_id.id], op.product_qty, context=context)
quant_obj.real_split_quants(cr, uid, quant_tuples, context=context)
done_reserved_quants = done_reserved_quants.union(set([qt[0] for qt in quant_tuples]))
elif op.package_id:
#moving a package never splits quants but we need to take care of the reserved_quant_ids
all_children_quants = self.pool.get('stock.quant.package').quants_get(cr, uid, op.package_id, context=context)
done_reserved_quants = done_reserved_quants.union(set(all_children_quants))
#finish the partial split by operation that leaves the choice of quant to move
for op in stock_operation_obj.browse(cr, uid, todo_later, context=context):
quant_tuples = quant_obj._get_quant_tuples(cr, uid, possible_quants, op.product_qty, context=context)
quant_obj.real_split_quants(cr, uid, quant_tuples, context=context)
done_reserved_quants = done_reserved_quants.union(set([qt[0] for qt in quant_tuples]))
return done_reserved_quants
#TODO move this in another class?
def make_packaging(self, cr, uid, picking_id, todo_move_ids, context=None):
stock_operation_obj = self.pool.get('stock.pack.operation')
move_obj = self.pool.get('stock.move')
quant_obj = self.pool.get('stock.quant')
for move in move_obj.browse(cr, uid, todo_move_ids, context=context):
operation_ids = stock_operation_obj.find_packaging_op_from_product(cr, uid, move.product_id, picking_id, context=context)
possible_quants = [q.id for q in move.quant_ids]
for op in stock_operation_obj.browse(cr, uid, operation_ids, context=context):
if not op.result_package_id:
continue
if op.product_id:
quant_tuples = quant_obj._get_quant_tuples(cr, uid, possible_quants, op.product_qty, context=context)
quant_obj.real_split_quants(cr, uid, quant_tuples, context=context)
quant_obj.write(cr, uid, [qt[0] for qt in quant_tuples], {'package_id': op.result_package_id.id}, context=context)
elif op.quant_id:
quant_tuples = quant_obj._get_quant_tuples(cr, uid, [op.quant_id.id], op.product_qty, context=context)
quant_obj.real_split_quants(cr, uid, quant_tuples, context=context)
quant_obj.write(cr, uid, [qt[0] for qt in quant_tuples], {'package_id': op.result_package_id.id}, context=context)
elif op.package_id:
#pack existing packs
self.pool.get('stock.quant.package').write(cr, uid, op.package_id.id, {'parent_id': op.result_package_id.id}, context=context)
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
#TODO raise an error if the quant already had a package_id not null ?
quant_obj.write(cr, uid, quant.id, {'package_id': op.result_package_id.id}, context=context)
if op.product_id:
#this kind of opeiration 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 do_partial(self, cr, uid, picking_id, partial_datas, only_split_lines=False, context=None):
def _find_move_from_product(self, cr, uid, picking_id, product_id, 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
def _create_unexpected_move(self, cr, uid, picking, product, qty, cost, context=None):
stock_move_obj = self.pool.get('stock.move')
seq_obj_name = 'stock.picking.' + picking.type
return stock_move_obj.create(cr, uid, {'name': self.pool.get('ir.sequence').get(cr, uid, seq_obj_name),
'product_id': product.id,
'product_uom_qty': qty,
'product_uom': product.product_uom.id,
#'lot_id': wizard_line.lot_id.id,
'location_id': picking.location_id.id,
'location_dest_id': picking.location_dest_id.id,
'picking_id': False,
'price_unit': cost}, context=context)
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')
proc_group = self.pool.get("procurement.group")
group_id = proc_group.create(cr, uid, {}, context=context)
todo_move_ids = []
for partial_data in partial_datas:
todo_move_ids += [stock_move_obj.do_partial(cr, uid, partial_data.get('move_id'), partial_data, group_id, context=context)]
ctx = context.copy()
ctx.update({'backorder_of': picking_id})
stock_move_obj.action_confirm(cr, uid, todo_move_ids, context=ctx)
assert len(picking_ids) == 1, 'Partial picking is only supported fir 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.product_uom_id.id,
'cost': quant.cost,
}
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,
}
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)
if not only_split_lines:
stock_move_obj.action_done(cr, uid, todo_move_ids, context=context)
self.make_packaging(cr, uid, picking_id, todo_move_ids, context=context)
# """ Makes partial picking and moves done.
# @param partial_datas : Dictionary containing details of partial picking
# like partner_id, partner_id, delivery_date,
# delivery moves with product_id, product_qty, uom
# @return: Dictionary of values
# """
# if context is None:
# context = {}
# else:
# context = dict(context)
# res = {}
# move_obj = self.pool.get('stock.move')
# product_obj = self.pool.get('product.product')
# currency_obj = self.pool.get('res.currency')
# uom_obj = self.pool.get('product.uom')
# sequence_obj = self.pool.get('ir.sequence')
# for pick in self.browse(cr, uid, ids, context=context):
# new_picking = None
# complete, too_many, too_few = [], [], []
# move_product_qty, lot_ids, partial_qty, product_uoms = {}, {}, {}, {}
#
#
# for move in pick.move_lines:
# if move.state in ('done', 'cancel'):
# continue
# partial_data = partial_datas.get('move%s'%(move.id), {})
# product_qty = partial_data.get('product_qty',0.0)
# move_product_qty[move.id] = product_qty
# product_uom = partial_data.get('product_uom',False)
# product_price = partial_data.get('product_price',0.0)
# lot_id = partial_data.get('lot_id')
# lot_ids[move.id] = lot_id
# product_uoms[move.id] = product_uom
# partial_qty[move.id] = uom_obj._compute_qty(cr, uid, product_uoms[move.id], product_qty, move.product_uom.id)
# if move.product_qty == partial_qty[move.id]:
# complete.append(move)
# elif move.product_qty > partial_qty[move.id]:
# too_few.append(move)
# else:
# too_many.append(move)
#
#
# for move in too_few:
# #create a backorder stock move with the remaining quantity
# product_qty = move_product_qty[move.id]
# if not new_picking:
# #a backorder picking doesn't exist yet, create a new one
# new_picking_name = pick.name
# self.write(cr, uid, [pick.id],
# {'name': sequence_obj.get(cr, uid,
# 'stock.picking.%s'%(pick.type)),
# })
# new_picking = self.copy(cr, uid, pick.id,
# {
# 'name': new_picking_name,
# 'move_lines' : [],
# 'state':'draft',
# })
# #modify the existing picking (this trick is needed to keep the eventual workflows pointing on the first picking)
# unlink_operation_order = [(2, op.id) for op in pick.pack_operation_ids]
# self.write(cr, uid, [pick.id],
# {
# 'pack_operation_ids': unlink_operation_order
# })
# done_reserved_quants = set()
# if product_qty != 0:
# #take care of partial picking in reserved quants
# done_reserved_quants = self.get_done_reserved_quants(cr, uid, pick.id, move, context=context)
# #copy the stock move
# new_picking_record = self.browse(cr, uid, new_picking, context=context)
#
# defaults = {
# 'product_qty' : product_qty,
# 'product_uos_qty': product_qty, #TODO: put correct uos_qty
# 'picking_id' : new_picking,
# 'state': 'assigned',
# 'move_dest_id': False,
# 'price_unit': product_price,
# 'product_uom': product_uoms[move.id],
# 'reserved_quant_ids': [(4,x) for x in list(done_reserved_quants)]
# }
# lot_id = lot_ids[move.id]
# if lot_id:
# defaults.update(lot_id=lot_id)
# backorder_move_id = move_obj.copy(cr, uid, move.id, defaults)
# self.make_packaging(cr, uid, pick.id, move_obj.browse(cr, uid, backorder_move_id, context=context), list(done_reserved_quants), context=context)
# #modify the existing stock move
# possible_quants = [x.id for x in move.reserved_quant_ids]
# move_obj.write(cr, uid, [move.id],
# {
# 'product_qty': move.product_qty - partial_qty[move.id],
# 'product_uos_qty': move.product_qty - partial_qty[move.id], #TODO: put correct uos_qty
# 'lot_id': False,
# 'tracking_id': False,
# 'reserved_quant_ids': [(4,x) for x in list(set(possible_quants) - done_reserved_quants)],
# })
#
# if new_picking:
# move_obj.write(cr, uid, [c.id for c in complete], {'picking_id': new_picking})
# for move in complete:
# defaults = {'product_uom': product_uoms[move.id], 'product_qty': move_product_qty[move.id]}
# if lot_ids.get(move.id):
# defaults.update({'lot_id': lot_ids[move.id]})
# move_obj.write(cr, uid, [move.id], defaults)
#
#
# #take care of packaging for completed moves
# possible_quants = [x.id for x in move.reserved_quant_ids]
#
# self.make_packaging(cr, uid, new_picking, move, possible_quants, context=context)
#
#
#
# for move in too_many:
# product_qty = move_product_qty[move.id]
# defaults = {
# 'product_qty' : product_qty,
# 'product_uos_qty': product_qty, #TODO: put correct uos_qty
# 'product_uom': product_uoms[move.id]
# }
# lot_id = lot_ids.get(move.id)
# if lot_ids.get(move.id):
# defaults.update(lot_id=lot_id)
# if new_picking:
# defaults.update(picking_id=new_picking)
# move_obj.write(cr, uid, [move.id], defaults)
#
# possible_quants = [x.id for x in move.reserved_quant_ids]
# self.make_packaging(cr, uid, new_picking, move, possible_quants, context=context)
# # At first we confirm the new picking (if necessary)
# if new_picking:
# self.signal_button_confirm(cr, uid, [new_picking])
# # Then we finish the good picking
# self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
# self.action_move(cr, uid, [new_picking], context=context)
# self.signal_button_done(cr, uid, [new_picking])
# delivered_pack_id = new_picking
# back_order_name = self.browse(cr, uid, delivered_pack_id, context=context).name
# self.message_post(cr, uid, ids, body=_("Back order <em>%s</em> has been <b>created</b>.") % (back_order_name), context=context)
# else:
# self.action_move(cr, uid, [pick.id], context=context)
# self.signal_button_done(cr, uid, [pick.id])
# delivered_pack_id = pick.id
#
# delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
# res[pick.id] = {'delivered_picking': delivered_pack.id}
#
# return res
self.make_packaging(cr, uid, picking.id, todo_move_ids, context=context)
# views associated to each picking type
_VIEW_LIST = {
'out': 'view_picking_out_form',
@ -889,90 +767,24 @@ class stock_picking(osv.osv):
}
def _get_view_id(self, cr, uid, type):
"""Get the view id suiting the given type
@param type: the picking type as a string
@return: view i, or False if no view found
"""
res = self.pool.get('ir.model.data').get_object_reference(cr, uid,
'stock', self._VIEW_LIST.get(type, 'view_picking_form'))
res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', self._VIEW_LIST.get(type, 'view_picking_form'))
return res and res[1] or False
def _get_picking_for_packing_ui(self, cr, uid, context=None):
res = self.search(cr, uid, [('state', '=', 'assigned')], limit=1, context=context)
return res and res[0] or False # TODO: what to do if nothing is left to do?
def action_done_from_packing_ui(self, cr, uid, picking_id, context=None):
if context is None:
context = {}
#create partial picking wizard that handles the split of stock moves and the backorder if needed
ctx = context.copy()
ctx['active_ids'] = [picking_id]
ctx['active_model'] = 'stock.picking'
partial_picking_obj = self.pool.get('stock.partial.picking')
partial_wizard_id = partial_picking_obj.create(cr, uid, {}, context=ctx)
partial_wizard_result = partial_picking_obj.do_partial(cr, uid, [partial_wizard_id], context=context)
#todo_picking_id = picking_id if picking was total, it's the backorder if the picking was partial
#todo_picking_id = partial_wizard_result[picking_id]['delivered_picking']
#all stuff below should be removed except the parent packaging /!\
# all_done_quants = []
# for move in self.browse(cr, uid, todo_picking_id, context=context).move_lines:
# all_done_quants += [quant.id for quant in move.reserved_quant_ids]
#
# for operation in self.browse(cr, uid, todo_picking_id, context=context).pack_operation_ids:
# if operation.result_package_id:
# if operation.package_id:
# if operation.package_id.parent_id:
# # decide what to do ?
# pass
# #pack existing packs
# self.pool.get('stock.quant.package').write(cr, uid, operation.package_id.id, {'parent_id': operation.result_package_id.id}, context=context)
# elif operation.product_id:
# #self.split_and_assign_quants(
# pass
#
#
#
#
# if self.pool.get('stock.pack.operation').search(cr, uid, [('picking_id', '=', todo_picking_id), ('result_package_id', '!=', False)]
# pass
# #def split_and_assign_quants(self, cr, uid, quant_tuples, move, context=None):
# #fill all the packages with assigned operations
# for operation in self.browse(cr, uid, todo_picking_id, context=context).pack_operation_ids:
# if operation.result_package_id:
# if operation.package_id:
# #pack existing packs
# self.pool.get('stock.quant.package').write(cr, uid, operation.package_id.id, {'parent_id': operation.result_package_id.id}, context=context)
# elif operation.quant_id:
# if operation.quant_id.parent_id:
# # decide what to do
# pass
# #assign_pack may split the quant and write the package on it (above test should be in that method instead)
# self.pool.get('stock.quant').assign_pack(cr, uid, operation.quant_id.id, operation.product_qty, operation.result_package_id.id, context=context)
# elif operation.product_id:
# pass
# #don't call action_done of picking because it will make all moves don, but make a partial delivery
# line_ids = []
# for move in self.browse(cr, uid, picking_id, context=context).move_lines:
# line += [{
# 'product_id': move.product_id.id,
# 'quantity': move.product_qty - move.remaining_qty,
# 'product_uom': move.product_uom_id.id,
#
# }]
#
# #self.action_done(cr, uid, picking_id, context=context)
def action_done_from_packing_ui(self, cr, uid, picking_id, only_split_lines=False, context=None):
self.do_partial(cr, uid, picking_id, only_split_lines, context=context)
#return id of next picking to work on
return self._get_picking_for_packing_ui(cr, uid, context=context)
def action_pack(self, cr, uid, picking_id, context=None):
#put all the operations of the picking that aren't yet assigned to a package to this new one
#put all the operations of the picking that aren't yet assigned to a package to a new one
stock_operation_obj = self.pool.get('stock.pack.operation')
package_obj = self.pool.get('stock.quant.package')
#create a new empty stock.quant.package
@ -1003,9 +815,8 @@ class stock_picking(osv.osv):
error_msg = ''
todo_on_moves = []
todo_on_operations = []
#check if the barcode correspond to a product
matching_product_ids = product_obj.search(cr, uid, ['|', ('code','=',barcode_str), ('ean13', '=', barcode_str)], context=context)
matching_product_ids = product_obj.search(cr, uid, [('ean13', '=', barcode_str)], context=context)
if matching_product_ids:
todo_on_moves, todo_on_operations = stock_operation_obj._search_and_increment(cr, uid, picking_id, ('product_id', '=', matching_product_ids[0]), context=context)
@ -1203,7 +1014,7 @@ 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),
'type': fields.related('picking_id', 'type', type='selection', selection=[('out', 'Sending Goods'), ('in', 'Getting Goods'), ('internal', 'Internal')], string='Shipping Type'),
'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Quants'),
'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'),
@ -1524,11 +1335,13 @@ class stock_move(osv.osv):
#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(cr, uid, 'stock.picking.%s' % (original_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)
@ -1907,7 +1720,7 @@ class stock_move(osv.osv):
# product_avail[product.id] += product_uom_qty
# return res
def do_partial(self, cr, uid, move_id, partial_data, move_group_id, context=None):
def do_partial(self, cr, uid, move_id, partial_datas, move_group_id, context=None):
""" Partially (or not) moves a stock.move.
@param partial_datas: Dictionary containing details of partial picking
like partner_id, delivery_date, delivery
@ -1919,9 +1732,9 @@ class stock_move(osv.osv):
context = {}
move = self.browse(cr, uid, move_id, context=context)
product_uom_qty = partial_data.get('product_uom_qty', 0.0)
product_uom = partial_data.get('product_uom', False)
#lot_id = partial_data.get('lot_id')
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
@ -1947,18 +1760,16 @@ class stock_move(osv.osv):
#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:
todo_move_id = move.id
elif move.product_qty > partial_qty:
if move.product_qty > partial_qty:
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_data.get('price_unit', 0.0),
}
'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),
}
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,
@ -1966,7 +1777,7 @@ class stock_move(osv.osv):
'lot_id': False,
'tracking_id': False})
else:
self.write(cr, uid, [move.id], {'product_uom_qty': move.product_uom_qty, 'product_uos_qty': move.product_uom_qty})
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
@ -2298,7 +2109,8 @@ class stock_package(osv.osv):
"""Returns packages from quants for store"""
res = set()
for quant in self.browse(cr, uid, ids, context=context):
res.add(quant.package_id.id)
if quant.package_id:
res.add(quant.package_id.id)
return list(res)
_columns = {
@ -2307,10 +2119,10 @@ class stock_package(osv.osv):
store={'stock.quant.package': (_get_subpackages, ['name', 'parent_id'], 10)}),
'parent_left': fields.integer('Left Parent', select=1),
'parent_right': fields.integer('Right Parent', select=1),
'packaging_id': fields.many2one('product.packaging', 'Type of Packaging', type="many2one"),
'packaging_id': fields.many2one('product.packaging', 'Type of Packaging'),
'location_id': fields.related('quant_ids', 'location_id', type='many2one', relation='stock.location', string='Location',
store={'stock.quant': (_get_packages, ['location_id'], 10)}, readonly=True),
'quant_ids': fields.one2many('stock.quant', 'package_id', 'Bulk Content'),
'location_id': fields.related('quant_ids', 'location_id', type='many2one', relation='stock.location', string='Location',
store = {'stock.quant': (_get_packages, ['location_id'], 10)}, readonly=True),
'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'),
@ -2342,17 +2154,17 @@ 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)]
## 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'''
@ -2377,13 +2189,23 @@ class stock_pack_operation(osv.osv):
_name = "stock.pack.operation"
_description = "Packing Operation"
_columns = {
'picking_id': fields.many2one('stock.picking', 'Stock Picking', help='The stock operation where the packing has been made'),
'product_id': fields.many2one('product.product', 'Product'), # 1
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure'),
'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
'product_uom_id': fields.many2one('product.uom', 'Product Unit of Measure'),
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
'package_id': fields.many2one('stock.quant.package', 'Package'), # 2
'quant_id': fields.many2one('stock.quant', 'Quant'), # 3
'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'),
}
_defaults = {
'date': fields.date.context_today,
}
def _find_product_ids(self, cr, uid, operation_id, context=None):
@ -2398,22 +2220,7 @@ class stock_pack_operation(osv.osv):
included_quant_ids = quant_obj.search(cr, uid, [('package_id', 'in', included_package_ids)], context=context)
return [quant.product_id.id for quant in quant_obj.browse(cr, uid, included_quant_ids, context=context)]
def find_packaging_op_from_product(self, cr, uid, product_id, picking_id, context=None):
#returns all ops that touches this product
#TOCHECK: don't we need to take only the ops with a result_package_id != False ?
res = []
op_ids = self.search(cr, uid, [('picking_id', '=', picking_id)], context=context)
for operation in self.browse(cr, uid, op_ids, context=context):
if operation.product_id and operation.product_id.id == product_id:
res += [operation.id]
if operation.quant_id and operation.quant_id.product_id.id == product_id:
res += [operation.id]
if operation.package_id:
all_quants = self.pool.get('stock.quant.package').search(cr, uid, [('parent_id', 'child_of', [operation.package_id.id])], context=context)
if any([self.pool.get('stock.quant').browse(cr, uid, quant, context=context).product_id.id == product_id for quant in all_quants]):
res += [operation.id]
return res
#TODO: this function can be refactored
def _search_and_increment(self, cr, uid, picking_id, key, context=None):
'''Search for an operation on an existing key in a picking, if it exists increment the qty (+1) otherwise create it
@ -2425,6 +2232,7 @@ class stock_pack_operation(osv.osv):
(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 = {}
@ -2440,12 +2248,22 @@ class stock_pack_operation(osv.osv):
else:
#no existing operation found for the given key and picking => create a new one
var_name, dummy, value = key
qty = 1
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': qty,
#'product_uom': 1, # FIXME
'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})
@ -2458,11 +2276,10 @@ class stock_pack_operation(osv.osv):
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:
#decide what to do
#TODO: no move found for the given operation => decide what to do.. raise an error?
pass
return todo_on_moves, todo_on_operations
class stock_warehouse_orderpoint(osv.osv):
"""
Defines Minimum stock rules.

View File

@ -587,7 +587,7 @@
<button name="draft_validate" states="draft" string="Confirm &amp; Transfer" type="object" class="oe_highlight" groups="base.group_user"/>
<!-- <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="action_process" states="assigned" string="Confirm &amp; Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
<button name="do_partial" states="assigned" string="Confirm &amp; Transfer" 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"}'/>
@ -611,8 +611,20 @@
<notebook>
<page string="Products">
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree', 'picking_type': 'internal'}"/>
<field name="pack_operation_ids" domain="[('result_package_id', '!=', False)]">
<tree editable="top">
<field name="product_id"/>
<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"/>
</tree>
</field>
<field name="note" placeholder="Add an internal note..." class="oe_inline"/>
</page>
<page string="Packed">
</page>
<page string="Additional Info">
<group>
<group>
@ -628,6 +640,10 @@
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
@ -734,8 +750,8 @@
<button name="draft_validate" states="draft" string="Confirm &amp; Deliver" type="object" class="oe_highlight"/>
<button name="action_assign" states="confirmed" string="Check Availability" type="object" class="oe_highlight"/>
</xpath>
<xpath expr="/form/header//button[@name='action_process']" position="replace">
<button name="action_process" states="assigned" string="Deliver" type="object" class="oe_highlight"/>
<xpath expr="/form/header//button[@name='do_partial']" position="replace">
<button name="do_partial" states="assigned" string="Deliver" type="object" class="oe_highlight"/>
</xpath>
<xpath expr="/form/header//field[@name='state']" position="replace">
<field name="state" nolabel="1" readonly="1" widget="statusbar" statusbar_visible="draft,confirmed,assigned,done" statusbar_colors='{"auto":"blue", "confirmed":"blue"}'/>
@ -859,8 +875,8 @@
<xpath expr="//button[@name='draft_validate']" position="replace">
<button name="draft_validate" states="draft" string="Confirm &amp; Receive" type="object" class="oe_highlight"/>
</xpath>
<xpath expr="//button[@name='action_process']" position="replace">
<button name="action_process" states="assigned" string="Receive" type="object" class="oe_highlight"/>
<xpath expr="//button[@name='do_partial']" position="replace">
<button name="do_partial" states="assigned" string="Receive" type="object" class="oe_highlight"/>
</xpath>
<xpath expr="//field[@name='move_lines']" position="replace">
<field name="move_lines" context="{'address_in_id': partner_id, 'picking_type': 'in', 'form_view_ref':'view_move_picking_form', 'tree_view_ref':'view_move_picking_tree'}"/>
@ -1003,7 +1019,6 @@
<form string="Stock Moves" version="7.0">
<header>
<button name="action_confirm" states="draft" string="Process Later" type="object" class="oe_highlight"/>
<button name="%(action_partial_move_server)d" string="Process Partially" type="action" states="assigned" class="oe_highlight"/>
<button name="action_done" states="draft,assigned,confirmed" string="Process Entirely" type="object" class="oe_highlight"/>
<button name="force_assign" states="confirmed" string="Set Available" type="object" class="oe_highlight"/>
<button name="action_cancel" states="assigned,confirmed" string="Cancel Move" type="object"/>
@ -1207,9 +1222,6 @@
icon="terp-gtk-jump-to-ltr" context="{'scrap': True}"
states="draft,waiting,confirmed,assigned"/>
<field name="state"/>
<button name="%(action_partial_move_server)d"
icon="terp-stock_effects-object-colorize" type="action"
states="assigned" class="oe_highlight"/>
<button name="action_done" states="draft,assigned,confirmed"
icon="gtk-go-forward" type="object"
class="oe_highlight" help="Done"/>

View File

@ -22,8 +22,6 @@
import stock_traceability
import stock_move
import stock_splitinto
import stock_partial_picking
import stock_partial_move
import stock_inventory_merge
import stock_fill_inventory
import stock_inventory_line_split

View File

@ -1,87 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-TODAY OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, osv
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
import time
from openerp.tools.translate import _
class stock_partial_move_line(osv.osv_memory):
_inherit = "stock.partial.picking.line"
_name = "stock.partial.move.line"
_columns = {
'wizard_id' : fields.many2one('stock.partial.move', string="Wizard", ondelete='CASCADE'),
}
class stock_partial_move(osv.osv_memory):
_name = "stock.partial.move"
_inherit = 'stock.partial.picking'
_description = "Partial Move Processing Wizard"
_columns = {
'date': fields.datetime('Date', required=True),
'move_ids' : fields.one2many('stock.partial.move.line', 'wizard_id', 'Moves'),
# picking_id is not used for move processing, so we remove the required attribute
# from the inherited column, and ignore it
'picking_id': fields.many2one('stock.picking', 'Picking'),
}
def default_get(self, cr, uid, fields, context=None):
if context is None: context = {}
# no call to super!
res = {}
move_ids = context.get('active_ids', [])
if not move_ids or not context.get('active_model') == 'stock.move':
return res
if 'move_ids' in fields:
move_ids = self.pool.get('stock.move').browse(cr, uid, move_ids, context=context)
moves = [self._partial_move_for(cr, uid, m) for m in move_ids if m.state not in ('done','cancel')]
res.update(move_ids=moves)
if 'date' in fields:
res.update(date=time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
return res
def do_partial(self, cr, uid, ids, context=None):
# no call to super!
assert len(ids) == 1, 'Partial move processing may only be done one form at a time.'
partial = self.browse(cr, uid, ids[0], context=context)
partial_data = {
'delivery_date' : partial.date
}
moves_ids = []
for move in partial.move_ids:
if not move.move_id:
raise osv.except_osv(_('Warning !'), _("You have manually created product lines, please delete them to proceed"))
move_id = move.move_id.id
partial_data['move%s' % (move_id)] = {
'product_id': move.product_id.id,
'product_qty': move.quantity,
'product_uom': move.product_uom.id,
'lot_id': move.lot_id.id,
}
moves_ids.append(move_id)
#if (move.move_id.picking_id.type == 'in') and (move.product_id.cost_method != 'standard'):
# partial_data['move%s' % (move_id)].update(product_price=move.cost,
# product_currency=move.currency.id)
self.pool.get('stock.move').do_partial(cr, uid, moves_ids, partial_data, context=context)
return {'type': 'ir.actions.act_window_close'}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="action_partial_move_server" model="ir.actions.server">
<field name="name">Deliver/Receive Products</field>
<field name="model_id" ref="model_stock_move"/>
<field name="state">code</field>
<field name="code">action = obj.action_partial_move(context=context)</field>
</record>
<record id="ir_open_partial_move_wizard" model="ir.values">
<field eval="'client_action_multi'" name="key2"/>
<field eval="'stock.move'" name="model"/>
<field name="name">Deliver/Receive Products</field>
<field eval="'ir.actions.server,%d'%action_partial_move_server" name="value"/>
</record>
<record id="stock_partial_move_form" model="ir.ui.view">
<field name="name">stock.partial.move.form</field>
<field name="model">stock.partial.move</field>
<field name="arch" type="xml">
<form string="Stock Move" version="7.0">
<separator string="Products"/>
<field name="move_ids"/>
<footer>
<button name="do_partial" string="_Validate" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="stock_partial_move_line_list" model="ir.ui.view">
<field name="name">stock.partial.move.line.list</field>
<field name="model">stock.partial.move.line</field>
<field name="arch" type="xml">
<tree editable="bottom" string="Product Moves">
<field name="product_id" />
<field name="quantity" />
<field name="product_uom" groups="product.group_uom"/>
<field name="lot_id" domain="[('product_id', '=', product_id)]" groups="stock.group_production_lot"/>
<field name="update_cost" invisible="1"/>
<field name="cost" attrs="{'invisible': [('update_cost','=', False)]}"/>
<field name="currency" attrs="{'invisible': [('update_cost','=', False)]}" groups="base.group_multi_currency"/>
</tree>
</field>
</record>
<record id="stock_partial_move_line_form" model="ir.ui.view">
<field name="name">stock.partial.move.line.form</field>
<field name="model">stock.partial.move.line</field>
<field name="arch" type="xml">
<form string="Stock Partial Move Line" version="7.0">
<group>
<field name="product_id" />
<field name="quantity" />
<field name="product_uom" />
<field name="lot_id" domain="[('product_id', '=', product_id)]"/>
<field name="update_cost" invisible="1"/>
<field name="cost" attrs="{'invisible': [('update_cost','=', False)]}"/>
<field name="currency" attrs="{'invisible': [('update_cost','=', False)]}" groups="base.group_multi_currency"/>
</group>
</form>
</field>
</record>
</data>
</openerp>

View File

@ -1,182 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-TODAY OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
from lxml import etree
from openerp.osv import fields, osv
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
import openerp.addons.decimal_precision as dp
from openerp.tools.translate import _
class stock_partial_picking_line(osv.TransientModel):
def _tracking(self, cursor, user, ids, name, arg, context=None):
res = {}
for tracklot in self.browse(cursor, user, ids, context=context):
if not tracklot.move_id:
continue
tracking = False
if (tracklot.move_id.picking_id.type == 'in' and tracklot.product_id.track_incoming) or \
(tracklot.move_id.picking_id.type == 'out' and tracklot.product_id.track_outgoing):
tracking = True
res[tracklot.id] = tracking
return res
_name = "stock.partial.picking.line"
_rec_name = 'product_id'
_columns = {
'product_id': fields.many2one('product.product', string="Product", required=True, ondelete='CASCADE'),
'quantity': fields.float("Quantity", digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
'product_uom': fields.many2one('product.uom', 'Unit of Measure', required=True, ondelete='CASCADE'),
'lot_id': fields.many2one('stock.production.lot', 'Serial Number', ondelete='CASCADE'),
'move_id': fields.many2one('stock.move', "Move", ondelete='CASCADE'),
'wizard_id': fields.many2one('stock.partial.picking', string="Wizard", ondelete='CASCADE'),
'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'),
'tracking': fields.function(_tracking, string='Tracking', type='boolean'),
}
def onchange_product_id(self, cr, uid, ids, product_id, context=None):
uom_id = False
if product_id:
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
uom_id = product.uom_id.id
return {'value': {'product_uom': uom_id}}
class stock_partial_picking(osv.osv_memory):
_name = "stock.partial.picking"
_rec_name = 'picking_id'
_description = "Partial Picking Processing Wizard"
def _hide_tracking(self, cursor, user, ids, name, arg, context=None):
res = {}
for wizard in self.browse(cursor, user, ids, context=context):
res[wizard.id] = any([not(x.tracking) for x in wizard.move_ids])
return res
_columns = {
'date': fields.datetime('Date', required=True),
'move_ids': fields.one2many('stock.partial.picking.line', 'wizard_id', 'Product Moves'),
'picking_id': fields.many2one('stock.picking', 'Picking', required=True, ondelete='CASCADE'),
'hide_tracking': fields.function(_hide_tracking, string='Tracking', type='boolean', help='This field is for internal purpose. It is used to decide if the column production lot has to be shown on the moves or not.'),
'only_split_lines': fields.boolean('Only Split', help="Check this field if you just want to split the picking and don't want to marke as done the lines in the backorder"),
}
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
#override of fields_view_get in order to change the label of the process button and the separator accordingly to the shipping type
if context is None:
context = {}
res = super(stock_partial_picking, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
type = context.get('default_type', False)
if type:
doc = etree.XML(res['arch'])
for node in doc.xpath("//button[@name='do_partial']"):
if type == 'in':
node.set('string', _('_Receive'))
elif type == 'out':
node.set('string', _('_Deliver'))
for node in doc.xpath("//separator[@name='product_separator']"):
if type == 'in':
node.set('string', _('Receive Products'))
elif type == 'out':
node.set('string', _('Deliver Products'))
res['arch'] = etree.tostring(doc)
return res
def default_get(self, cr, uid, fields, context=None):
if context is None:
context = {}
res = super(stock_partial_picking, self).default_get(cr, uid, fields, context=context)
picking_ids = context.get('active_ids', [])
active_model = context.get('active_model')
if not picking_ids or len(picking_ids) != 1:
# Partial Picking Processing may only be done for one picking at a time
return res
assert active_model in ('stock.picking', 'stock.picking.in', 'stock.picking.out'), 'Bad context propagation'
picking_id, = picking_ids
picking = self.pool.get('stock.picking').browse(cr, uid, picking_id, context=context)
if 'picking_id' in fields:
res.update(picking_id=picking_id)
if 'move_ids' in fields:
moves = [self._partial_move_for(cr, uid, m) for m in picking.move_lines if m.state not in ('done', 'cancel')]
res.update(move_ids=moves)
if 'date' in fields:
res.update(date=time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
return res
def _partial_move_for(self, cr, uid, move):
partial_move = {
'product_id': move.product_id.id,
'quantity': move.product_uom_qty,
'product_uom': move.product_uom.id,
'move_id': move.id,
'location_id': move.location_id.id,
'location_dest_id': move.location_dest_id.id,
'cost': move.product_id.standard_price,
}
return partial_move
def do_partial(self, cr, uid, ids, context=None):
assert len(ids) == 1, 'Partial picking processing may only be done one at a time.'
if context is None:
context = {}
stock_move_obj = self.pool.get('stock.move')
partial_wizard = self.browse(cr, uid, ids[0], context=context)
picking_id = partial_wizard.picking_id.id
assert picking_id, 'Picking not defined'
picking_type = partial_wizard.picking_id.type
partial_data = {
'delivery_date': partial_wizard.date
}
partial_datas = []
for wizard_line in partial_wizard.move_ids:
move_id = wizard_line.move_id.id
if not move_id:
seq_obj_name = 'stock.picking.' + picking_type
move_id = stock_move_obj.create(cr, uid, {'name': self.pool.get('ir.sequence').get(cr, uid, seq_obj_name),
'product_id': wizard_line.product_id.id,
'product_uom_qty': wizard_line.quantity,
'product_uom': wizard_line.product_uom.id,
'lot_id': wizard_line.lot_id.id,
'location_id': partial_wizard.picking_id.location_id.id,
'location_dest_id': partial_wizard.picking_id.location_dest_id.id,
'picking_id': False,
'price_unit': wizard_line.cost}, context=context)
partial_data.update({
'product_id': wizard_line.product_id.id,
'product_uom_qty': wizard_line.quantity,
'product_uom': wizard_line.product_uom.id,
'lot_id': wizard_line.lot_id.id,
'price_unit': wizard_line.cost,
'move_id': move_id,
})
tmp = partial_data.copy()
partial_datas += [tmp]
self.pool.get('stock.picking').do_partial(cr, uid, picking_id, partial_datas, partial_wizard.only_split_lines, context=context)
return {'type': 'ir.actions.act_window_close'}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="action_partial_picking" model="ir.actions.act_window">
<field name="name">Process Picking</field>
<field name="res_model">stock.partial.picking</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<!-- this view of stock.partial.picking wizard is dedicated to internal picking. The fields_view_get is ovveridden in order to change the label of the process button and the separator. -->
<record id="stock_partial_picking_form" model="ir.ui.view">
<field name="name">stock.partial.picking.form</field>
<field name="model">stock.partial.picking</field>
<field name="arch" type="xml">
<form string="Stock partial Picking" version="7.0">
<field name="hide_tracking" invisible="1"/>
<separator string="Transfer Products" name="product_separator"/>
<field name="move_ids" context="{'hide_tracking': hide_tracking}">
<tree editable="bottom" string="Product Moves">
<field name="product_id" on_change="onchange_product_id(product_id)"/>
<field name="quantity" />
<field name="product_uom" groups="product.group_uom"/>
<field name="tracking" invisible="1"/>
<field name="lot_id" domain="[('product_id', '=', product_id)]" invisible="context.get('hide_tracking',False)" attrs="{'required':[('tracking','=',True), ('quantity', '!=', 0)]}" groups="stock.group_production_lot" context="{'default_product_id':product_id}"/>
<!-- Removed as this feature is not logic: price must be updated upon reception of invoice -->
<field name="update_cost" invisible="1"/>
<field name="cost" invisible="1"/>
<field name="currency" invisible="1"/>
</tree>
</field>
<footer>
<button name="do_partial" string="_Transfer" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="stock_partial_picking_line_list" model="ir.ui.view">
<field name="name">stock.partial.picking.line.list</field>
<field name="model">stock.partial.picking.line</field>
<field name="arch" type="xml">
<tree editable="bottom" string="Product Moves">
<field name="product_id" />
<field name="quantity" />
<field name="product_uom" />
<field name="tracking" invisible="1"/>
<field name="lot_id" domain="[('product_id', '=', product_id)]" attrs="{'required':[('tracking','=',True)]}"/>
<!-- Removed as this feature is not logic: price must be updated upon reception of invoice -->
<field name="update_cost" invisible="1"/>
<field name="cost" invisible="1"/>
<field name="currency" invisible="1"/>
</tree>
</field>
</record>
<record id="stock_partial_picking_line_form" model="ir.ui.view">
<field name="name">stock.partial.picking.line.form</field>
<field name="model">stock.partial.picking.line</field>
<field name="arch" type="xml">
<form string="Stock Picking Line" version="7.0">
<group col="4">
<field name="product_id" />
<field name="quantity" />
<field name="product_uom" />
<field name="tracking" invisible="1"/>
<field name="lot_id" domain="[('product_id', '=', product_id)]" attrs="{'required':[('tracking','=',True)]}"/>
<field name="update_cost" invisible="1"/>
<field name="cost" attrs="{'invisible': [('update_cost','=', False)]}"/>
<field name="currency" attrs="{'invisible': [('update_cost','=', False)]}" groups="base.group_multi_currency"/>
</group>
</form>
</field>
</record>
</data>
</openerp>