[WIP] pack operations + do_partial refactored again
bzr revid: qdp-launchpad@openerp.com-20130723153424-9179x8burkch0ap0
This commit is contained in:
parent
2525f33104
commit
ffa06fc422
|
@ -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',
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -587,7 +587,7 @@
|
|||
<button name="draft_validate" states="draft" string="Confirm & 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 & Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
|
||||
<button name="do_partial" states="assigned" string="Confirm & 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 & 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 & 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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
|
@ -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>
|
|
@ -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:
|
|
@ -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>
|
Loading…
Reference in New Issue