[MERGE] misc things: replacement of picking_ids on purchase.order by a fields.function; adding of a button on stock.warehouse to see all the routes related; usability improvement in stock.warehouse form view; bugfix in creation of picking/stock moves from purchase.order confirmation; view all picking-related from button on purchase.order instead of just the incoming shipment

bzr revid: qdp-launchpad@openerp.com-20131017160725-fjphdok34is6q4bc
This commit is contained in:
Quentin (OpenERP) 2013-10-17 18:07:25 +02:00
commit 9f2d0cc781
5 changed files with 110 additions and 96 deletions

View File

@ -154,6 +154,16 @@ class purchase_order(osv.osv):
obj_data = self.pool.get('ir.model.data')
return obj_data.get_object_reference(cr, uid, 'stock','picking_type_in') and obj_data.get_object_reference(cr, uid, 'stock','picking_type_in')[1] or False
def _get_picking_ids(self, cr, uid, ids, name, args, context=None):
res = {}
for purchase_id in ids:
picking_ids = set()
move_ids = self.pool.get('stock.move').search(cr, uid, [('purchase_line_id.order_id','=', purchase_id)] , context=context)
for move in self.pool.get('stock.move').browse(cr, uid, move_ids, context=context):
picking_ids.add(move.picking_id.id)
res[purchase_id] = list(picking_ids)
return res
STATE_SELECTION = [
('draft', 'Draft PO'),
('sent', 'RFQ Sent'),
@ -196,7 +206,7 @@ class purchase_order(osv.osv):
'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
'notes': fields.text('Terms and Conditions'),
'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
'picking_ids': fields.one2many('stock.picking', 'purchase_id', 'Picking List', readonly=True, help="This is the list of incoming shipments that have been generated for this purchase order."),
'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of operations that have been generated for this purchase order."),
'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'),
'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
@ -235,7 +245,7 @@ class purchase_order(osv.osv):
'bid_validity': fields.date('Bid Valid Until', help="Date on which the bid expired"),
'picking_type_id': fields.many2one('stock.picking.type', 'Deliver To', help="This will determine picking type of incoming shipment", required=True,
states={'confirmed': [('readonly', True)], 'approved': [('readonly', True)], 'done': [('readonly', True)]}),
'related_location_id': fields.related('picking_type_id', 'default_location_dest_id', type='many2one', relation='stock.location', string="Related location", store=True),
'related_location_id': fields.related('picking_type_id', 'default_location_dest_id', type='many2one', relation='stock.location', string="Related location", store=True),
}
_defaults = {
'date_order': fields.date.context_today,
@ -394,32 +404,22 @@ class purchase_order(osv.osv):
if context is None:
context = {}
mod_obj = self.pool.get('ir.model.data')
dummy, action_id = tuple(mod_obj.get_object_reference(cr, uid, 'stock', 'action_picking_tree'))
action = self.pool.get('ir.actions.act_window').read(cr, uid, action_id, context=context)
pick_ids = []
for po in self.browse(cr, uid, ids, context=context):
pick_ids += [picking.id for picking in po.picking_ids]
action_model, action_id = tuple(mod_obj.get_object_reference(cr, uid, 'stock', 'action_picking_tree'))
action = self.pool[action_model].read(cr, uid, action_id, context=context)
active_id = context.get('active_id',ids[0])
picking_type_id = self.browse(cr, uid, active_id, context=context)['picking_type_id'].id
ctx = eval(action['context'],{'active_id': picking_type_id}, nocopy=True)
ctx.update({
'search_default_purchase_id': ids[0]
})
if pick_ids and len(pick_ids) == 1:
form_view_ids = [view_id for view_id, view in action['views'] if view == 'form']
view_id = form_view_ids and form_view_ids[0] or False
action.update({
'views': [],
'view_mode': 'form',
'view_id': view_id,
'res_id': pick_ids[0]
})
action.update({
'context': ctx,
})
#override the context to get rid of the default filtering on picking type
action['context'] = {}
#choose the view_mode accordingly
if len(pick_ids) > 1:
action['domain'] = "[('id','in',[" + ','.join(map(str, pick_ids)) + "])]"
else:
res = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_form')
action['views'] = [(res and res[1] or False, 'form')]
action['res_id'] = pick_ids and pick_ids[0] or False
return action
def wkf_approve_order(self, cr, uid, ids, context=None):
@ -429,15 +429,6 @@ class purchase_order(osv.osv):
def wkf_bid_received(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state':'bid', 'bid_date': fields.date.context_today(self,cr,uid,context=context)})
def print_confirm(self,cr,uid,ids,context=None):
print "Confirmed"
def print_double(self,cr,uid,ids,context=None):
print "double Approval"
def print_router(self,cr,uid,ids,context=None):
print "Routed"
def wkf_send_rfq(self, cr, uid, ids, context=None):
'''
This function opens a window to compose an email, with the edi purchase template message loaded by default
@ -497,8 +488,7 @@ class purchase_order(osv.osv):
raise osv.except_osv(_('Error!'),_('You cannot confirm a purchase order without any purchase order line.'))
for line in po.order_line:
if line.state=='draft':
todo.append(line.id)
todo.append(line.id)
self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
for id in ids:
self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
@ -666,20 +656,6 @@ class purchase_order(osv.osv):
return user_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
return user_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
def _prepare_order_picking(self, cr, uid, order, context=None):
return {
'name': self.pool.get('ir.sequence').get_id(cr, uid, order.picking_type_id.sequence_id.id, 'id'),
'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
'date': self.date_to_datetime(cr, uid, order.date_order, context),
'partner_id': order.dest_address_id.id or order.partner_id.id,
'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
'partner_id': order.dest_address_id.id or order.partner_id.id,
'purchase_id': order.id,
'company_id': order.company_id.id,
'move_lines' : [],
'picking_type_id': order.picking_type_id.id,
}
def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, group_id, context=None):
''' prepare the stock move data from the PO line '''
price_unit = order_line.price_unit
@ -687,7 +663,7 @@ class purchase_order(osv.osv):
price_unit *= order_line.product_uom.factor
if order.currency_id.id != order.company_id.currency_id.id:
price_unit = self.pool.get('res.currency').compute(cr, uid, order.currency_id.id, order.company_id.currency_id.id, price_unit, context=context)
return {
'name': order_line.name or '',
'product_id': order_line.product_id.id,
@ -708,15 +684,16 @@ class purchase_order(osv.osv):
'price_unit': price_unit,
'picking_type_id': order.picking_type_id.id,
'group_id': group_id,
'route_ids': order.picking_type_id.warehouse_id and [(6, 0, [x.id for x in order.picking_type_id.warehouse_id.route_ids])] or [],
}
def _create_pickings(self, cr, uid, order, order_lines, picking_id=False, context=None):
"""Creates pickings and appropriate stock moves for given order lines, then
confirms the moves, makes them available, and confirms the picking.
def _create_stock_moves(self, cr, uid, order, order_lines, picking_id=False, context=None):
"""Creates appropriate stock moves for given order lines, whose can optionally create a
picking if none is given or no suitable is found, then confirms the moves, makes them
available, and confirms the pickings.
If ``picking_id`` is provided, the stock moves will be added to it, otherwise
a standard outgoing picking will be created to wrap the stock moves, as returned
by :meth:`~._prepare_order_picking`.
If ``picking_id`` is provided, the stock moves will be added to it, otherwise a standard
incoming picking will be created to wrap the stock moves (default behavior of the stock.move)
Modules that wish to customize the procurements or partition the stock moves over
multiple stock pickings may override this method and call ``super()`` with
@ -727,19 +704,16 @@ class purchase_order(osv.osv):
and moves should be created.
:param int picking_id: optional ID of a stock picking to which the created stock moves
will be added. A new picking will be created if omitted.
:return: list of IDs of pickings used/created for the given order lines (usually just one)
:return: None
"""
stock_picking = self.pool.get('stock.picking')
if not picking_id:
picking_id = stock_picking.create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
todo_moves = []
stock_move = self.pool.get('stock.move')
new_group = False
if any([(not x.group_id) for x in order_lines]):
new_group = self.pool.get("procurement.group").create(cr, uid, {'name':order.name, 'partner_id': order.partner_id.id}, context=context)
for order_line in order_lines:
if not order_line.product_id:
continue
@ -750,9 +724,9 @@ class purchase_order(osv.osv):
if order_line.move_dest_id:
order_line.move_dest_id.write({'location_id': order.location_id.id})
todo_moves.append(move)
stock_move.action_confirm(cr, uid, todo_moves)
stock_move.force_assign(cr, uid, todo_moves)
return [picking_id]
def test_moves_done(self, cr, uid, ids, context=None):
'''PO is done at the delivery side if all the incoming shipments are done'''
@ -784,15 +758,8 @@ class purchase_order(osv.osv):
return res
def action_picking_create(self, cr, uid, ids, context=None):
picking_ids = []
for order in self.browse(cr, uid, ids):
picking_ids.extend(self._create_pickings(cr, uid, order, order.order_line, None, context=context))
# Must return one unique picking ID: the one to connect in the subflow of the purchase order.
# In case of multiple (split) pickings, we should return the ID of the critical one, i.e. the
# one that should trigger the advancement of the purchase workflow.
# By default we will consider the first one as most important, but this behavior can be overridden.
return picking_ids[0] if picking_ids else False
self._create_stock_moves(cr, uid, order, order.order_line, None, context=context)
def picking_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)

View File

@ -136,6 +136,7 @@ class sale_order(osv.osv):
of given sales order ids. It can either be a in a list or in a form
view, if there is only one delivery order to show.
'''
mod_obj = self.pool.get('ir.model.data')
act_obj = self.pool.get('ir.actions.act_window')
@ -147,6 +148,7 @@ class sale_order(osv.osv):
pick_ids = []
for so in self.browse(cr, uid, ids, context=context):
pick_ids += [picking.id for picking in so.picking_ids]
#choose the view_mode accordingly
if len(pick_ids) > 1:
result['domain'] = "[('id','in',[" + ','.join(map(str, pick_ids)) + "])]"

View File

@ -25,7 +25,7 @@ import openerp.addons.decimal_precision as dp
class product_product(osv.osv):
_inherit = "product.product"
def _stock_move_count(self, cr, uid, ids, field_name, arg, context=None):
res = dict([(id, {'reception_count': 0, 'delivery_count': 0}) for id in ids])
move_pool=self.pool.get('stock.move')

View File

@ -562,6 +562,7 @@ class stock_picking(osv.osv):
ptype_id = vals.get('picking_type_id', context.get('default_picking_type_id', False))
sequence_id = self.pool.get('stock.picking.type').browse(cr, user, ptype_id, context=context).sequence_id.id
vals['name'] = self.pool.get('ir.sequence').get_id(cr, user, sequence_id, 'id', context=context)
return super(stock_picking, self).create(cr, user, vals, context)
# The state of a picking depends on the state of its related stock.move
@ -615,16 +616,10 @@ class stock_picking(osv.osv):
continue
return res
def _get_group_id(self, cr, uid, ids, field_name, args, context=None):
res = {}
for pick in self.browse(cr, uid, ids, context=context):
if pick.move_lines and pick.move_lines[0].group_id:
res[pick.id] = pick.move_lines[0].group_id.id
else:
res[pick.id] = False
return res
def action_assign_owner(self, cr, uid, ids, context=None):
for picking in self.browse(cr, uid, ids, context=context):
packop_ids = [op.id for op in picking.pack_operation_ids]
self.pool.get('stock.pack.operation').write(cr, uid, packop_ids, {'owner_id': picking.owner_id.id}, context=context)
_columns = {
'name': fields.char('Reference', size=64, select=True, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
@ -664,13 +659,14 @@ class stock_picking(osv.osv):
'pack_operation_exist': fields.function(_get_pack_operation_exist, type='boolean', string='Pack Operation Exists?', help='technical field for attrs in view'),
'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', required=True),
'owner_id': fields.many2one('res.partner', 'Owner', help="Default Owner"),
# Used to search on pickings
'product_id': fields.related('move_lines', 'product_id', type='many2one', relation='product.product', string='Product'),#?
'location_id': fields.related('move_lines', 'location_id', type='many2one', relation='stock.location', string='Location', readonly=True),
'location_dest_id': fields.related('move_lines', 'location_dest_id', type='many2one', relation='stock.location', string='Destination Location', readonly=True),
'group_id': fields.related('move_lines', 'group_id', type='many2one', relation='procurement.group', string='Procurement Group', readonly=True),
# 'group_id': fields.function(_get_group_id, type='many2one', store={'stock.move': (_get_pickings, ['picking_id', 'group_id'], 10)}),
}
_defaults = {
'name': lambda self, cr, uid, context: '/',
'state': 'draft',
@ -1431,10 +1427,12 @@ class stock_move(osv.osv):
for move in moves:
if not move.move_dest_id:
routes = [x.id for x in move.product_id.route_ids + move.product_id.categ_id.total_route_ids]
rules = push_obj.search(cr, uid, [('route_id', 'in', routes), ('location_from_id', '=', move.location_dest_id.id)], context=context)
if rules:
rule = push_obj.browse(cr, uid, rules[0], context=context)
push_obj._apply(cr, uid, rule, move, context=context)
routes = routes or [x.id for x in move.route_ids]
if routes:
rules = push_obj.search(cr, uid, [('route_id', 'in', routes), ('location_from_id', '=', move.location_dest_id.id)], context=context)
if rules:
rule = push_obj.browse(cr, uid, rules[0], context=context)
push_obj._apply(cr, uid, rule, move, context=context)
return True
# Create the stock.move.putaway records
@ -2756,6 +2754,33 @@ class stock_warehouse(osv.osv):
#TODO try to delete location and route and if not possible, put them in inactive
return super(stock_warehouse, self).unlink(cr, uid, ids, context=context)
def get_all_routes_for_wh(self, cr, uid, warehouse, context=None):
all_routes = []
all_routes += [warehouse.crossdock_route_id.id]
all_routes += [warehouse.reception_route_id.id]
all_routes += [warehouse.delivery_route_id.id]
all_routes += [warehouse.mto_pull_id.route_id.id]
all_routes += [route.id for route in warehouse.resupply_route_ids]
all_routes += [route.id for route in warehouse.route_ids]
return all_routes
def view_all_routes_for_wh(self, cr, uid, ids, context=None):
all_routes = []
for wh in self.browse(cr, uid, ids, context=context):
all_routes += self.get_all_routes_for_wh(cr, uid, wh, context=context)
res_id = ids and ids[0] or False
domain = [('id', 'in', all_routes)]
return {
'name': _('Warehouse\'s Routes'),
'domain': domain,
'res_model': 'stock.location.route',
'type': 'ir.actions.act_window',
'view_id': False,
'view_mode': 'tree,form',
'view_type': 'form',
'limit': 20
}
class stock_location_path(osv.osv):
_name = "stock.location.path"
@ -2814,7 +2839,7 @@ class stock_location_path(osv.osv):
}
def _apply(self, cr, uid, rule, move, context=None):
move_obj = self.pool.get('stock.move')
newdate = (datetime.strptime(move.date, '%Y-%m-%d %H:%M:%S') + relativedelta(days=rule.delay or 0)).strftime('%Y-%m-%d')
newdate = (datetime.strptime(move.date, '%Y-%m-%d %H:%M:%S') + relativedelta.relativedelta(days=rule.delay or 0)).strftime('%Y-%m-%d')
if rule.auto=='transparent':
move_obj.write(cr, uid, [move.id], {
'date': newdate,
@ -3015,7 +3040,7 @@ class stock_package(osv.osv):
class stock_pack_operation(osv.osv):
_name = "stock.pack.operation"
_description = "Packing Operation"
def _get_remaining_qty(self, cr, uid, ids, context=None):
res = {}
for ops in self.browse(cr, uid, ids, context=context):
@ -3024,8 +3049,7 @@ class stock_pack_operation(osv.osv):
qty -= quant.qty
res[ops.id] = qty
return res
_columns = {
'picking_id': fields.many2one('stock.picking', 'Stock Picking', help='The stock operation where the packing has been made', required=True),
'product_id': fields.many2one('product.product', 'Product', ondelete="CASCADE"), # 1
@ -3045,7 +3069,7 @@ class stock_pack_operation(osv.osv):
}
_defaults = {
'date': fields.date.context_today,
'date': fields.date.context_today,
}

View File

@ -648,6 +648,9 @@
<field name="arch" type="xml">
<form string="Warehouse" version="7.0">
<sheet>
<div class="oe_right oe_button_box">
<button name="view_all_routes_for_wh" string="View Warehouse Routes" type="object"/>
</div>
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
<group>
@ -668,9 +671,21 @@
<field name="default_resupply_wh_id"/> <!-- TODO should be filtered on resupply_wh_ids only... -->
</group>
</page>
<page string="Routes Information" colspan="4">
<group colspan="4">
<field name="route_ids" nolabel="1" readonly="1"/>
<page string="Technical Information" groups='base.group_no_one'>
<group>
<group string="Locations">
<field name="wh_input_stock_loc_id" readonly="1"/>
<field name="wh_qc_stock_loc_id" readonly="1"/>
<field name="wh_pack_stock_loc_id" readonly="1"/>
<field name="wh_output_stock_loc_id" readonly="1"/>
</group>
<group string="Picking Types">
<field name="in_type_id" readonly="1"/>
<field name="int_type_id" readonly="1"/>
<field name="pick_type_id" readonly="1"/>
<field name="pack_type_id" readonly="1"/>
<field name="out_type_id" readonly="1"/>
</group>
</group>
</page>
</notebook>
@ -771,6 +786,12 @@
<field name="date"/>
<field name="min_date"/>
<field name="origin" placeholder="e.g. PO0032" class="oe_inline"/>
<label for="owner_id" groups="stock.group_tracking_owner"/>
<div groups="stock.group_tracking_owner">
<field name="owner_id"/>
<button name="action_assign_owner" string="Assign Owner" type="object" attrs="{'invisible': ['|',('pack_operation_exist', '=', False),('state', 'not in', ('draft','assigned','confirmed'))]}"
class="oe_link oe_edit_only"/>
</div>
</group>
</group>
<notebook>
@ -781,8 +802,8 @@
<field name="pack_operation_exist" invisible="1"/>
<button name="action_pack" string="Create Package" type="object" attrs="{'invisible': ['|',('pack_operation_exist', '=', False),('state', 'not in', ('draft','assigned','confirmed'))]}"/>
<button name="do_split" string="Create Draft Backorder" groups="base.group_no_one" type="object" attrs="{'invisible': ['|',('pack_operation_exist', '=', False),('state','!=','assigned')]}"/>
<field name="pack_operation_ids" attrs="{'invisible': [('pack_operation_exist', '=', False)]}">
<tree editable="top">
<field name="pack_operation_ids" attrs="{'invisible': [('pack_operation_exist', '=', False)]}" context="{'default_owner_id': owner_id}">
<tree editable="top">
<field name="product_id"/>
<field name="product_uom_id" groups="product.group_uom"/>
<field name="lot_id" domain="[('product_id','=?', product_id)]" context="{'product_id': product_id}" groups="stock.group_production_lot"/>