82
122
class stock_picking(osv.osv):
83
123
_inherit = "stock.picking"
84
124
_description = "Picking List"
87
def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
89
hook to update defaults data
126
def _hook_state_list(self, cr, uid, *args, **kwargs):
128
Change terms into states list
130
state_list = kwargs['state_list']
132
state_list['done'] = _('is closed.')
133
state_list['shipped'] = _('is shipped.') # UF-1617: New state for the IN of partial shipment
137
def _get_stock_picking_from_partner_ids(self, cr, uid, ids, context=None):
139
ids represents the ids of res.partner objects for which values have changed
141
return the list of ids of stock.picking objects which need to get their fields updated
143
self is res.partner object
148
if isinstance(ids, (int, long)):
151
pick_obj = self.pool.get('stock.picking')
152
result = pick_obj.search(cr, uid, [('partner_id2', 'in', ids)], context=context)
155
def _vals_get_stock_ov(self, cr, uid, ids, fields, arg, context=None):
157
multi fields function method
162
if isinstance(ids, (int, long)):
166
for obj in self.browse(cr, uid, ids, context=context):
169
result[obj.id].update({f:False})
171
result[obj.id].update({'partner_type_stock_picking': obj.partner_id2.partner_type})
175
def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
177
for pick in self.browse(cr, uid, ids, context=context):
179
for line in pick.move_lines:
180
if line.inactive_product:
186
def _get_is_esc(self, cr, uid, ids, field_name, args, context=None):
188
Return True if the partner is an ESC
190
if isinstance(ids, (int, long)):
195
for pick in self.browse(cr, uid, ids, context=context):
196
res[pick.id] = pick.partner_id2 and pick.partner_id2.partner_type == 'esc' or False
200
def _get_dpo_incoming(self, cr, uid, ids, field_name, args, context=None):
202
Return True if the picking is an incoming and if one the stock move are linked to dpo_line
204
if isinstance(ids, (int, long)):
208
for pick in self.browse(cr, uid, ids, context=context):
209
res[pick.id] = {'dpo_incoming': False,
211
if pick.type == 'in':
212
for move in pick.move_lines:
213
if move.sync_dpo or move.dpo_line_id:
214
res[pick.id]['dpo_incoming'] = True
217
if pick.type == 'out' and pick.subtype in ('standard', 'picking'):
218
for move in pick.move_lines:
219
if move.sync_dpo or move.dpo_line_id:
220
res[pick.id]['dpo_out'] = True
224
def _get_dpo_picking_ids(self, cr, uid, ids, context=None):
226
for obj in self.browse(cr, uid, ids, context=context):
227
if obj.picking_id and obj.picking_id.id not in result:
228
result.append(obj.picking_id.id)
232
def _get_do_not_sync(self, cr, uid, ids, field_name, args, context=None):
238
current_company_p_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.partner_id.id
240
for pick in self.browse(cr, uid, ids, context=context):
242
if pick.partner_id.id == current_company_p_id:
247
def _src_do_not_sync(self, cr, uid, obj, name, args, context=None):
249
Returns picking ticket that do not synched because the partner of the
250
picking is the partner of the current company.
253
curr_partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.partner_id.id
260
eq_false = arg[1] == '=' and arg[2] in (False, 'f', 'false', 'False', 0)
261
neq_true = arg[1] in ('!=', '<>') and arg[2] in (True, 't', 'true', 'True', 1)
262
eq_true = arg[1] == '=' and arg[2] in (True, 't', 'true', 'True', 1)
263
neq_false = arg[1] in ('!=', '<>') and arg[2] in (False, 'f', 'false', 'False', 0)
265
if arg[0] == 'do_not_sync' and (eq_false or neq_true):
266
res.append(('partner_id', '!=', curr_partner_id))
267
elif arg[0] == 'do_not_sync' and (eq_true or neq_false):
268
res.append(('partner_id', '=', curr_partner_id))
274
'state': fields.selection([
277
('confirmed', 'Confirmed'),
278
('assigned', 'Available'),
279
('shipped', 'Available Shipped'), # UF-1617: new state of IN for partial shipment
281
('cancel', 'Cancelled'),
282
('import', 'Import in progress'),
283
], 'State', readonly=True, select=True,
284
help="* Draft: not confirmed yet and will not be scheduled until confirmed\n"\
285
"* Confirmed: still waiting for the availability of products\n"\
286
"* Available: products reserved, simply waiting for confirmation.\n"\
287
"* Available Shipped: products already shipped at supplier, simply waiting for arrival confirmation.\n"\
288
"* Waiting: waiting for another move to proceed before it becomes automatically available (e.g. in Make-To-Order flows)\n"\
289
"* Closed: has been processed, can't be modified or cancelled anymore\n"\
290
"* Cancelled: has been cancelled, can't be confirmed anymore"),
291
'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
292
'address_id': fields.many2one('res.partner.address', 'Delivery address', help="Address of partner", readonly=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, domain="[('partner_id', '=', partner_id)]"),
293
'partner_id2': fields.many2one('res.partner', 'Partner', required=False),
294
'from_wkf': fields.boolean('From wkf'),
295
'from_wkf_sourcing': fields.boolean('From wkf sourcing'),
296
'update_version_from_in_stock_picking': fields.integer(string='Update version following IN processing'),
297
'partner_type_stock_picking': fields.function(_vals_get_stock_ov, method=True, type='selection', selection=PARTNER_TYPE, string='Partner Type', multi='get_vals_stock_ov', readonly=True, select=True,
298
store={'stock.picking': (lambda self, cr, uid, ids, c=None: ids, ['partner_id2'], 10),
299
'res.partner': (_get_stock_picking_from_partner_ids, ['partner_type'], 10), }),
300
'inactive_product': fields.function(_get_inactive_product, method=True, type='boolean', string='Product is inactive', store=False),
301
'fake_type': fields.selection([('out', 'Sending Goods'), ('in', 'Getting Goods'), ('internal', 'Internal')], 'Shipping Type', required=True, select=True, help="Shipping type specify, goods coming in or going out."),
302
'shipment_ref': fields.char(string='Ship Reference', size=256, readonly=True), # UF-1617: indicating the reference to the SHIP object at supplier
303
'move_lines': fields.one2many('stock.move', 'picking_id', 'Internal Moves', states={'done': [('readonly', True)], 'cancel': [('readonly', True)], 'import': [('readonly', True)]}),
304
'state_before_import': fields.char(size=64, string='State before import', readonly=True),
305
'is_esc': fields.function(_get_is_esc, method=True, string='ESC Partner ?', type='boolean', store=False),
306
'dpo_incoming': fields.function(_get_dpo_incoming, method=True, type='boolean', string='DPO Incoming', multi='dpo',
307
store={'stock.move': (_get_dpo_picking_ids, ['sync_dpo', 'dpo_line_id', 'picking_id'], 10,),
308
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 10)}),
309
'dpo_out': fields.function(_get_dpo_incoming, method=True, type='boolean', string='DPO Out', multi='dpo',
310
store={'stock.move': (_get_dpo_picking_ids, ['sync_dpo', 'dpo_line_id', 'picking_id'], 10,),
311
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 10)}),
312
'previous_chained_pick_id': fields.many2one('stock.picking', string='Previous chained picking', ondelete='set null', readonly=True),
313
'do_not_sync': fields.function(
315
fnct_search=_src_do_not_sync,
318
string='Do not sync.',
321
'company_id2': fields.many2one('res.partner', string='Company', required=True),
324
_defaults = {'from_yml_test': lambda *a: False,
325
'from_wkf': lambda *a: False,
326
'from_wkf_sourcing': lambda *a: False,
327
'update_version_from_in_stock_picking': 0,
329
'shipment_ref':False,
330
'company_id2': lambda s,c,u,ids,ctx=None: s.pool.get('res.users').browse(c,u,u).company_id.partner_id.id,
333
def copy_data(self, cr, uid, id, default=None, context=None):
338
default.update(shipment_ref=False)
340
if not 'from_wkf_sourcing' in default:
341
default['from_wkf_sourcing'] = False
343
if not 'previous_chained_pick_id' in default:
344
default['previous_chained_pick_id'] = False
346
return super(stock_picking, self).copy_data(cr, uid, id, default=default, context=context)
348
def _check_active_product(self, cr, uid, ids, context=None):
350
Check if the stock picking contains a line with an inactive products
352
product_tbd = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
353
inactive_lines = self.pool.get('stock.move').search(cr, uid, [('product_id.active', '=', False),
354
('product_id', '!=', product_tbd),
355
('picking_id', 'in', ids),
356
('picking_id.state', 'not in', ['draft', 'cancel', 'done'])],
357
count=True, context=context)
360
plural = inactive_lines == 1 and _('A product has') or _('Some products have')
361
l_plural = inactive_lines == 1 and _('line') or _('lines')
362
p_plural = inactive_lines == 1 and _('this inactive product') or _('those inactive products')
363
raise osv.except_osv(_('Error'), _('%s been inactivated. If you want to validate this document you have to remove/correct the %s containing %s (see red %s of the document)') % (plural, l_plural, p_plural, l_plural))
368
(_check_active_product, "You cannot validate this document because it contains a line with an inactive product", ['order_line', 'state'])
371
def _check_restriction_line(self, cr, uid, ids, context=None):
373
Check restriction on products
375
if isinstance(ids, (int, long)):
378
line_obj = self.pool.get('stock.move')
381
for picking in self.browse(cr, uid, ids, context=context):
382
if picking.type == 'internal' and picking.state not in ('draft', 'done', 'cancel'):
383
res = res and line_obj._check_restriction_line(cr, uid, [x.id for x in picking.move_lines], context=context)
386
# UF-2148: override and use only this method when checking the cancel condition: if a line has 0 qty, then whatever state, it is always allowed to be canceled
387
def allow_cancel(self, cr, uid, ids, context=None):
388
for pick in self.browse(cr, uid, ids, context=context):
389
if not pick.move_lines:
391
for move in pick.move_lines:
392
if move.state == 'done' and move.product_qty != 0:
393
raise osv.except_osv(_('Error'), _('You cannot cancel picking because stock move is in done state !'))
397
def create(self, cr, uid, vals, context=None):
399
create method for filling flag from yml tests
404
if not context.get('active_id', False):
405
vals['from_wkf'] = True
406
# in case me make a copy of a stock.picking coming from a workflow
407
if context.get('not_workflow', False):
408
vals['from_wkf'] = False
410
if vals.get('from_wkf') and vals.get('purchase_id'):
411
po = self.pool.get('purchase.order').browse(cr, uid, vals.get('purchase_id'), context=context)
412
for line in po.order_line:
413
if line.procurement_id and line.procurement_id.sale_id:
414
vals['from_wkf_sourcing'] = True
417
if not vals.get('partner_id2') and vals.get('address_id'):
418
addr = self.pool.get('res.partner.address').browse(cr, uid, vals.get('address_id'), context=context)
419
vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
421
if not vals.get('address_id') and vals.get('partner_id2'):
422
addr = self.pool.get('res.partner').address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
423
if not addr.get('delivery'):
424
vals['address_id'] = addr.get('default')
426
vals['address_id'] = addr.get('delivery')
428
res = super(stock_picking, self).create(cr, uid, vals, context=context)
433
def write(self, cr, uid, ids, vals, context=None):
435
Update the partner or the address according to the other
437
if isinstance(ids, (int, long)):
440
if not vals.get('address_id') and vals.get('partner_id2'):
441
for pick in self.browse(cr, uid, ids, context=context):
442
if pick.partner_id.id != vals.get('partner_id2'):
443
addr = self.pool.get('res.partner').address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
444
if not addr.get('delivery'):
445
vals['address_id'] = addr.get('default')
447
vals['address_id'] = addr.get('delivery')
449
if not vals.get('partner_id2') and vals.get('address_id'):
450
for pick in self.browse(cr, uid, ids, context=context):
451
if pick.address_id.id != vals.get('address_id'):
452
addr = self.pool.get('res.partner.address').browse(cr, uid, vals.get('address_id'), context=context)
453
vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
455
res = super(stock_picking, self).write(cr, uid, ids, vals, context=context)
459
def go_to_simulation_screen(self, cr, uid, ids, context=None):
461
Return the simulation screen
463
simu_obj = self.pool.get('wizard.import.in.simulation.screen')
464
line_obj = self.pool.get('wizard.import.in.line.simulation.screen')
466
if isinstance(ids, (int, long)):
471
raise osv.except_osv(_('Error'), _('No picking defined'))
473
simu_id = simu_obj.create(cr, uid, {'picking_id': picking_id, }, context=context)
474
for move in self.browse(cr, uid, picking_id, context=context).move_lines:
475
if move.state not in ('draft', 'cancel', 'done'):
476
line_obj.create(cr, uid, {'move_id': move.id,
478
'move_product_id': move.product_id and move.product_id.id or False,
479
'move_product_qty': move.product_qty or 0.00,
480
'move_uom_id': move.product_uom and move.product_uom.id or False,
481
'move_price_unit': move.price_unit or move.product_id.standard_price,
482
'move_currency_id': move.price_currency_id and move.price_currency_id.id or False,
483
'line_number': move.line_number, }, context=context)
485
return {'type': 'ir.actions.act_window',
486
'res_model': 'wizard.import.in.simulation.screen',
493
def on_change_partner(self, cr, uid, ids, partner_id, address_id, context=None):
495
Change the delivery address when the partner change.
506
v.update({'address_id': False, 'is_esc': False})
508
partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
509
d.update({'address_id': [('partner_id', '=', partner_id)]})
510
v.update({'is_esc': partner.partner_type == 'esc'})
514
addr = self.pool.get('res.partner.address').browse(cr, uid, address_id, context=context)
516
if not address_id or addr.partner_id.id != partner_id:
517
addr = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
518
if not addr.get('delivery'):
519
addr = addr.get('default')
521
addr = addr.get('delivery')
523
v.update({'address_id': addr})
525
if partner_id and ids:
526
context['partner_id'] = partner_id
528
out_loc_ids = self.pool.get('stock.location').search(cr, uid, [
529
('outgoing_dest', '=', context['partner_id']),
530
], order='NO_ORDER', context=context)
531
move_ids = self.pool.get('stock.move').search(cr, uid, [
532
('picking_id', 'in', ids),
533
('location_dest_id', 'not in', out_loc_ids),
534
], limit=1, order='NO_ORDER', context=context)
537
'value': {'partner_id2': False, 'partner_id': False,},
541
You cannot choose this supplier because some destination locations are not available for this partner.
549
def return_to_state(self, cr, uid, ids, context=None):
551
Return to initial state if the picking is 'Import in progress'
556
if isinstance(ids, (int, long)):
559
for pick in self.browse(cr, uid, ids, context=context):
560
self.write(cr, uid, [pick.id], {'state': pick.state_before_import}, context=context)
564
def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
566
Set the picking to done
570
if isinstance(ids, (int, long)):
573
for pick in self.browse(cr, uid, ids, context=context):
574
for move in pick.move_lines:
575
if move.state not in ('cancel', 'done'):
576
move_ids.append(move.id)
578
# Set all stock moves to done
579
self.pool.get('stock.move').set_manually_done(cr, uid, move_ids, all_doc=all_doc, context=context)
584
def force_assign(self, cr, uid, ids, context=None):
585
res = super(stock_picking, self).force_assign(cr, uid, ids)
587
self.infolog(cr, uid, 'Force availability ran on stock.picking id:%s' % pick_id)
591
def action_assign(self, cr, uid, ids, context=None):
592
res = super(stock_picking, self).action_assign(cr, uid, ids, context=context)
594
self.infolog(cr, uid, 'Check availability ran on stock.picking id:%s' % pick_id)
598
def cancel_assign(self, cr, uid, ids, *args, **kwargs):
599
res = super(stock_picking, self).cancel_assign(cr, uid, ids)
601
self.infolog(cr, uid, 'Cancel availability ran on stock.picking id:%s' % pick_id)
606
def call_cancel_wizard(self, cr, uid, ids, context=None):
608
Call the wizard of cancelation (ask user if he wants to resource goods)
610
for pick_data in self.read(cr, uid, ids, ['sale_id', 'purchase_id', 'subtype', 'state'], context=context):
611
# if draft and shipment is in progress, we cannot cancel
612
if pick_data['subtype'] == 'picking' and pick_data['state'] in ('draft',):
613
if self.has_picking_ticket_in_progress(cr, uid, [pick_data['id']], context=context)[pick_data['id']]:
614
raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try to cancel again.'))
615
# if not draft or qty does not match, the shipping is already in progress
616
if pick_data['subtype'] == 'picking' and pick_data['state'] in ('done',):
617
raise osv.except_osv(_('Warning !'), _('The shipment process is completed and cannot be canceled!'))
619
if pick_data['sale_id'] or pick_data['purchase_id']:
620
return {'type': 'ir.actions.act_window',
621
'res_model': 'stock.picking.cancel.wizard',
625
'context': dict(context, active_id=pick_data['id'])}
627
wf_service = netsvc.LocalService("workflow")
629
wf_service.trg_validate(uid, 'stock.picking', id, 'button_cancel', cr)
633
def action_cancel(self, cr, uid, ids, context=None):
635
Re-source the FO/IR lines if needed
638
wf_service = netsvc.LocalService("workflow")
640
if isinstance(ids, (int, long)):
646
context['cancel_type'] = 'update_out'
647
res = super(stock_picking, self).action_cancel(cr, uid, ids, context=context)
649
# Re-source the sale.order.line
651
for pick in self.browse(cr, uid, ids, context=context):
652
# Don't delete lines if an Available PT is canceled
653
if pick.type == 'out' and pick.subtype == 'picking' and pick.backorder_id and True:
656
for move in pick.move_lines:
657
if move.sale_line_id and move.product_qty > 0.00:
658
fo_ids.add(move.sale_line_id.order_id.id)
660
# If the IN is linked to a PO and has a backorder not closed, change the subflow
661
# of the PO to the backorder
662
if pick.type == 'in' and pick.purchase_id:
663
po_id = pick.purchase_id.id
665
if pick.backorder_id and pick.backorder_id.state not in ('done', 'cancel'):
666
bo_id = pick.backorder_id.id
668
picking_ids = self.search(cr, uid, [
669
('purchase_id', '=', po_id),
670
('id', '!=', pick.id),
671
('state', 'not in', ['done', 'cancel']),
672
], limit=1, context=context)
674
bo_id = picking_ids[0]
677
netsvc.LocalService("workflow").trg_change_subflow(uid, 'purchase.order', [po_id], 'stock.picking', [pick.id], bo_id, cr)
679
# Run the signal 'ship_corrected' to the FO
681
wf_service.trg_validate(uid, 'sale.order', fo, 'ship_corrected', cr)
685
def _do_partial_hook(self, cr, uid, ids, context=None, *args, **kwargs):
687
Please copy this to your module's method also.
688
This hook belongs to the do_partial method from stock_override>stock.py>stock_picking
690
- allow to modify the defaults data for move creation and copy
91
692
defaults = kwargs.get('defaults')
92
693
assert defaults is not None, 'missing defaults'
697
def _picking_done_cond(self, cr, uid, ids, context=None, *args, **kwargs):
699
Please copy this to your module's method also.
700
This hook belongs to the do_partial method from stock_override>stock.py>stock_picking
702
- allow to conditionally execute the picking processing to done
706
def _custom_code(self, cr, uid, ids, context=None, *args, **kwargs):
708
Please copy this to your module's method also.
709
This hook belongs to the do_partial method from stock_override>stock.py>stock_picking
711
- allow to execute specific custom code before processing picking to done
712
- no supposed to modify partial_datas
97
716
# @@@override stock>stock.py>stock_picking>do_partial
98
def do_partial(self, cr, uid, ids, partial_datas, context=None):
717
def do_partial_deprecated(self, cr, uid, ids, partial_datas, context=None):
99
718
""" Makes partial picking and moves done.
100
719
@param partial_datas : Dictionary containing details of partial picking
101
720
like partner_id, address_id, delivery_date,
102
721
delivery moves with product_id, product_qty, uom
103
722
@return: Dictionary of values
724
if isinstance(ids, (int, long)):
105
727
if context is None:
231
869
defaults = self._do_partial_hook(cr, uid, ids, context, move=move, partial_datas=partial_datas, defaults=defaults)
232
870
move_obj.write(cr, uid, [move.id], defaults)
235
872
# At first we confirm the new picking (if necessary)
874
self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
875
# custom code execution
876
self._custom_code(cr, uid, ids, context=context, partial_datas=partial_datas, concerned_picking=self.browse(cr, uid, new_picking, context=context))
877
# we confirm the new picking after its name was possibly modified by custom code - so the link message (top message) is correct
237
878
wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_confirm', cr)
238
879
# Then we finish the good picking
239
self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
240
self.action_move(cr, uid, [new_picking])
241
wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_done', cr)
880
if self._picking_done_cond(cr, uid, ids, context=context, partial_datas=partial_datas):
881
self.action_move(cr, uid, [new_picking])
882
wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_done', cr)
883
# UF-1617: Hook a method to create the sync messages for some extra objects: batch number, asset once the OUT/partial is done
884
self._hook_create_sync_messages(cr, uid, new_picking, context)
242
886
wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
243
887
delivered_pack_id = new_picking
245
self.action_move(cr, uid, [pick.id])
246
wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
889
# custom code execution
890
self._custom_code(cr, uid, ids, context=context, partial_datas=partial_datas, concerned_picking=pick)
891
if self._picking_done_cond(cr, uid, ids, context=context, partial_datas=partial_datas):
892
self.action_move(cr, uid, [pick.id])
893
wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
894
# UF-1617: Hook a method to create the sync messages for some extra objects: batch number, asset once the OUT/partial is done
895
self._hook_create_sync_messages(cr, uid, ids, context)
247
897
delivered_pack_id = pick.id
899
# UF-1617: set the delivered_pack_id (new or original) to become already_shipped
900
self.write(cr, uid, [delivered_pack_id], {'already_shipped': True})
249
902
delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
250
903
res[pick.id] = {'delivered_picking': delivered_pack.id or False}
253
906
# @@@override end
908
# UF-1617: Empty hook here, to be implemented in sync modules
909
def _hook_create_sync_messages(self, cr, uid, ids, context=None):
912
# @@@override stock>stock.py>stock_picking>_get_invoice_type
913
def _get_invoice_type(self, pick):
914
src_usage = dest_usage = None
916
if pick.invoice_state == '2binvoiced':
918
src_usage = pick.move_lines[0].location_id.usage
919
dest_usage = pick.move_lines[0].location_dest_id.usage
920
if pick.type == 'out' and dest_usage == 'supplier':
921
inv_type = 'in_refund'
922
elif pick.type == 'out' and dest_usage == 'customer':
923
inv_type = 'out_invoice'
924
elif (pick.type == 'in' and src_usage == 'supplier') or (pick.type == 'internal'):
925
inv_type = 'in_invoice'
926
elif pick.type == 'in' and src_usage == 'customer':
927
inv_type = 'out_refund'
929
inv_type = 'out_invoice'
932
def _hook_get_move_ids(self, cr, uid, *args, **kwargs):
933
move_obj = self.pool.get('stock.move')
934
pick = kwargs['pick']
935
move_ids = move_obj.search(cr, uid, [('picking_id', '=', pick.id),
936
('state', 'in', ('waiting', 'confirmed'))], order='prodlot_id, product_qty desc')
940
def draft_force_assign(self, cr, uid, ids, context=None):
942
Confirm all stock moves
944
res = super(stock_picking, self).draft_force_assign(cr, uid, ids)
946
move_obj = self.pool.get('stock.move')
947
move_ids = move_obj.search(cr, uid, [('state', '=', 'draft'), ('picking_id', 'in', ids)], context=context)
948
move_obj.action_confirm(cr, uid, move_ids, context=context)
952
def is_invoice_needed(self, cr, uid, sp=None):
954
Check if invoice is needed. Cases where we do not need invoice:
955
- OUT from scratch (without purchase_id and sale_id) AND stock picking type in internal, external or esc
956
- OUT from FO AND stock picking type in internal, external or esc
957
So all OUT that have internel, external or esc should return FALSE from this method.
958
This means to only accept intermission and intersection invoicing on OUT with reason type "Deliver partner".
965
rt_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_deliver_partner')[1]
968
# type out and partner_type in internal, external or esc
969
if sp.type == 'out' and not sp.purchase_id and not sp.sale_id and sp.partner_id.partner_type in ['external', 'internal', 'esc']:
971
if sp.type == 'out' and not sp.purchase_id and not sp.sale_id and rt_id and sp.partner_id.partner_type in ['intermission', 'section']:
972
# Search all stock moves attached to this one. If one of them is deliver partner, then is_invoice_needed is ok
974
sm_ids = self.pool.get('stock.move').search(cr, uid, [('picking_id', '=', sp.id)])
976
for sm in self.pool.get('stock.move').browse(cr, uid, sm_ids):
977
if sm.reason_type_id.id == rt_id:
979
# partner is itself (those that own the company)
980
company_partner_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id
981
if sp.partner_id.id == company_partner_id.id:
985
def _create_invoice(self, cr, uid, stock_picking):
987
Creates an invoice for the specified stock picking
988
@param stock_picking browse_record: The stock picking for which to create an invoice
991
invoice_type = self._get_invoice_type(stock_picking)
993
# Check if no invoice needed
994
if not self.is_invoice_needed(cr, uid, stock_picking):
997
# we do not create invoice for procurement_request (Internal Request)
998
if not stock_picking.sale_id.procurement_request and stock_picking.subtype == 'standard':
999
if stock_picking.type == 'in' or stock_picking.type == 'internal':
1000
if invoice_type == 'out_refund':
1001
picking_type = 'sale_refund'
1003
picking_type = 'purchase'
1004
elif stock_picking.type == 'out':
1005
if invoice_type == 'in_refund':
1006
picking_type = 'purchase_refund'
1008
picking_type = 'sale'
1010
# Set journal type based on picking type
1011
journal_type = picking_type
1013
# Disturb journal for invoice only on intermission partner type
1014
if stock_picking.partner_id.partner_type == 'intermission':
1015
journal_type = 'intermission'
1017
# Find appropriate journal
1018
journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', journal_type),
1019
('is_current_instance', '=', True)])
1021
raise osv.except_osv(_('Warning'), _('No journal of type %s found when trying to create invoice for picking %s!') % (journal_type, stock_picking.name))
1024
self.action_invoice_create(cr, uid, [stock_picking.id], journal_ids[0], False, invoice_type, {})
1026
def action_done(self, cr, uid, ids, context=None):
1028
Create automatically invoice or NOT (regarding some criteria in is_invoice_needed)
1030
res = super(stock_picking, self).action_done(cr, uid, ids, context=context)
1033
if isinstance(ids, (int, long)):
1035
for sp in self.browse(cr, uid, ids):
1036
prog_id = self.update_processing_info(cr, uid, sp.id, False, {
1037
'close_in': _('Invoice creation in progress'),
1039
# If the IN is linked to a PO and has a backorder not closed, change the subflow
1040
# of the PO to the backorder
1041
if sp.type == 'in' and sp.purchase_id:
1042
po_id = sp.purchase_id.id
1044
if sp.backorder_id and sp.backorder_id.state not in ('done', 'cancel'):
1045
bo_id = sp.backorder_id.id
1047
picking_ids = self.search(cr, uid, [
1048
('purchase_id', '=', po_id),
1049
('id', '!=', sp.id),
1050
('state', 'not in', ['done', 'cancel']),
1051
], limit=1, context=context)
1053
bo_id = picking_ids[0]
1056
netsvc.LocalService("workflow").trg_change_subflow(uid, 'purchase.order', [po_id], 'stock.picking', [sp.id], bo_id, cr)
1058
self._create_invoice(cr, uid, sp)
1062
def _get_price_unit_invoice(self, cr, uid, move_line, type):
1064
Update the Unit price according to the UoM received and the UoM ordered
1066
res = super(stock_picking, self)._get_price_unit_invoice(cr, uid, move_line, type)
1067
if type == 'in_refund':
1068
if move_line.picking_id and move_line.picking_id.purchase_id:
1069
po_line_obj = self.pool.get('purchase.order.line')
1070
po_line_id = po_line_obj.search(cr, uid, [('order_id', '=', move_line.picking_id.purchase_id.id),
1071
('product_id', '=', move_line.product_id.id),
1072
('state', '!=', 'cancel')
1075
return po_line_obj.read(cr, uid, po_line_id[0], ['price_unit'])['price_unit']
1077
if move_line.purchase_line_id:
1078
po_uom_id = move_line.purchase_line_id.product_uom.id
1079
move_uom_id = move_line.product_uom.id
1080
uom_ratio = self.pool.get('product.uom')._compute_price(cr, uid, move_uom_id, 1, po_uom_id)
1081
return res / uom_ratio
1085
def action_invoice_create(self, cr, uid, ids, journal_id=False, group=False, type='out_invoice', context=None):
1087
Attach an intermission journal to the Intermission Voucher IN/OUT if partner type is intermission from the picking.
1088
Prepare intermission voucher IN/OUT
1089
Change invoice purchase_list field to TRUE if this picking come from a PO which is 'purchase_list'
1091
if isinstance(ids, (int, long)):
1096
res = super(stock_picking, self).action_invoice_create(cr, uid, ids, journal_id, group, type, context)
1097
intermission_journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'intermission'),
1098
('is_current_instance', '=', True)])
1099
company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1100
intermission_default_account = company.intermission_default_counterpart
1101
for pick in self.browse(cr, uid, [x for x in res]):
1102
# Check if PO and PO is purchase_list
1103
if pick.purchase_id and pick.purchase_id.order_type and pick.purchase_id.order_type == 'purchase_list':
1104
inv_id = res[pick.id]
1105
self.pool.get('account.invoice').write(cr, uid, [inv_id], {'purchase_list': True})
1106
# Check intermission
1107
if pick.partner_id.partner_type == 'intermission':
1108
inv_id = res[pick.id]
1109
if not intermission_journal_ids:
1110
raise osv.except_osv(_('Error'), _('No Intermission journal found!'))
1111
if not intermission_default_account or not intermission_default_account.id:
1112
raise osv.except_osv(_('Error'), _('Please configure a default intermission account in Company configuration.'))
1113
self.pool.get('account.invoice').write(cr, uid, [inv_id], {'journal_id': intermission_journal_ids[0],
1114
'is_intermission': True, 'account_id': intermission_default_account.id, })
1115
# Change currency for this invoice
1116
company_currency = company.currency_id and company.currency_id.id or False
1117
if not company_currency:
1118
raise osv.except_osv(_('Warning'), _('No company currency found!'))
1119
wiz_account_change = self.pool.get('account.change.currency').create(cr, uid, {'currency_id': company_currency}, context=context)
1120
self.pool.get('account.change.currency').change_currency(cr, uid, [wiz_account_change], context={'active_id': inv_id})
1123
def action_confirm(self, cr, uid, ids, context=None):
1125
stock.picking: action confirm
1126
if INCOMING picking: confirm and check availability
1128
super(stock_picking, self).action_confirm(cr, uid, ids, context=context)
1129
move_obj = self.pool.get('stock.move')
1131
if isinstance(ids, (int, long)):
1133
for pick in self.browse(cr, uid, ids):
1134
if pick.move_lines and pick.type == 'in':
1135
not_assigned_move = [x.id for x in pick.move_lines if x.state == 'confirmed']
1136
if not_assigned_move:
1137
move_obj.action_assign(cr, uid, not_assigned_move)
1140
def _hook_action_assign_batch(self, cr, uid, ids, context=None):
1142
Please copy this to your module's method also.
1143
This hook belongs to the action_assign method from stock>stock.py>stock_picking class
1145
- when product is Expiry date mandatory, we "pre-assign" batch numbers regarding the available quantity
1146
and location logic in addition to FEFO logic (First expired first out).
1148
if isinstance(ids, (int, long)):
1152
move_obj = self.pool.get('stock.move')
1153
if not context.get('already_checked'):
1154
for pick in self.browse(cr, uid, ids, context=context):
1155
# perishable for perishable or batch management
1156
move_obj.fefo_update(cr, uid, [move.id for move in pick.move_lines if move.product_id.perishable], context) # FEFO
1157
context['already_checked'] = True
1158
return super(stock_picking, self)._hook_action_assign_batch(cr, uid, ids, context=context)
1160
# UF-1617: Handle the new state Shipped of IN
1161
def action_shipped_wkf(self, cr, uid, ids, context=None):
1162
""" Changes picking state to assigned.
1165
self.write(cr, uid, ids, {'state': 'shipped'})
1166
self.log_picking(cr, uid, ids, context=context)
1167
move_obj = self.pool.get('stock.move')
1169
for pick in self.browse(cr, uid, ids):
1170
if pick.move_lines and pick.type == 'in':
1171
not_assigned_move = [x.id for x in pick.move_lines]
1172
move_obj.write(cr, uid, not_assigned_move, {'state': 'confirmed'})
1173
if not_assigned_move:
1174
move_obj.action_assign(cr, uid, not_assigned_move)
1178
def change_all_location(self, cr, uid, ids, context=None):
1180
Launch the wizard to change all destination location of stock moves
1185
if isinstance(ids, (int, long)):
1188
return {'type': 'ir.actions.act_window',
1189
'res_model': 'change.dest.location',
1190
'view_type': 'form',
1191
'view_mode': 'form',
1192
'res_id': self.pool.get('change.dest.location').create(cr, uid, {'picking_id': ids[0]}, context=context),
268
1208
_inherit = "stock.move"
269
1209
_description = "Stock Move with hook"
1211
def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
1213
Set the stock move to manually done
1215
return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
1217
def _get_from_dpo(self, cr, uid, ids, field_name, args, context=None):
1219
Return True if the move has a dpo_id
1222
if isinstance(ids, (int, long)):
1225
for move in self.browse(cr, uid, ids, context=context):
1226
res[move.id] = False
1232
def _search_from_dpo(self, cr, uid, obj, name, args, context=None):
1234
Returns the list of moves from or not from DPO
1237
if arg[0] == 'from_dpo' and arg[1] == '=':
1238
return [('dpo_id', '!=', False)]
1239
elif arg[0] == 'from_dpo' and arg[1] in ('!=', '<>'):
1240
return [('dpo_id', '=', False)]
1244
def _default_location_destination(self, cr, uid, context=None):
1247
partner_id = context.get('partner_id')
1248
company_part_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id
1249
if context.get('picking_type') == 'out':
1250
if partner_id != company_part_id:
1251
wh_ids = self.pool.get('stock.warehouse').search(cr, uid, [])
1253
return self.pool.get('stock.warehouse').browse(cr, uid, wh_ids[0]).lot_output_id.id
1257
def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
1259
Fill the error message if the product of the line is inactive
1262
product_tbd = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
1263
if isinstance(ids, (int, long)):
1266
for line in self.browse(cr, uid, ids, context=context):
1267
res[line.id] = {'inactive_product': False,
1268
'inactive_error': ''}
1269
if line.picking_id and line.picking_id.state not in ('cancel', 'done') and line.product_id and line.product_id.id != product_tbd and not line.product_id.active:
1270
res[line.id] = {'inactive_product': True,
1271
'inactive_error': _('The product in line is inactive !')}
1275
def _is_expired_lot(self, cr, uid, ids, field_name, args, context=None):
1277
Return True if the lot of stock move is expired
1281
if isinstance(ids, (int, long)):
1284
product_tbd = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
1285
for move in self.browse(cr, uid, ids, context=context):
1286
res[move.id] = {'expired_lot': False, 'product_tbd': False}
1287
if move.prodlot_id and move.prodlot_id.is_expired:
1288
res[move.id]['expired_lot'] = True
1290
if move.product_id.id == product_tbd:
1291
res[move.id]['product_tbd'] = True
1295
def _is_price_changed(self, cr, uid, ids, field_name, args, context=None):
1296
if isinstance(ids, (int, long)):
1300
for m in self.browse(cr, uid, ids, context=context):
1302
if m.purchase_line_id and abs(m.purchase_line_id.price_unit - m.price_unit) > 10**-3:
1308
'price_unit': fields.float('Unit Price', digits_compute=dp.get_precision('Picking Price Computation'), help="Technical field used to record the product cost set by the user during a picking confirmation (when average price costing method is used)"),
1309
'state': fields.selection([('draft', 'Draft'), ('waiting', 'Waiting'), ('confirmed', 'Not Available'), ('assigned', 'Available'), ('done', 'Closed'), ('cancel', 'Cancelled'), ('hidden', 'Hidden')], 'State', readonly=True, select=True,
1310
help='When the stock move is created it is in the \'Draft\' state.\n After that, it is set to \'Not Available\' state if the scheduler did not find the products.\n When products are reserved it is set to \'Available\'.\n When the picking is done the state is \'Closed\'.\
1311
\nThe state is \'Waiting\' if the move is waiting for another one.'),
1312
'address_id': fields.many2one('res.partner.address', 'Delivery address', help="Address of partner", readonly=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, domain="[('partner_id', '=', partner_id)]"),
1313
'partner_id2': fields.many2one('res.partner', 'Partner', required=False),
1314
'already_confirmed': fields.boolean(string='Already confirmed'),
1315
'dpo_id': fields.many2one('purchase.order', string='Direct PO', help='PO from where this stock move is sourced.'),
1316
'dpo_line_id': fields.integer(string='Direct PO line', help='PO line from where this stock move is sourced (for sync. engine).'),
1317
'from_dpo': fields.function(_get_from_dpo, fnct_search=_search_from_dpo, type='boolean', method=True, store=False, string='From DPO ?'),
1318
'sync_dpo': fields.boolean(string='Sync. DPO'),
1319
'from_wkf_line': fields.related('picking_id', 'from_wkf', type='boolean', string='Internal use: from wkf'),
1320
'fake_state': fields.related('state', type='char', store=False, string="Internal use"),
1321
'processed_stock_move': fields.boolean(string='Processed Stock Move'),
1322
'inactive_product': fields.function(_get_inactive_product, method=True, type='boolean', string='Product is inactive', store=False, multi='inactive'),
1323
'inactive_error': fields.function(_get_inactive_product, method=True, type='char', string='Error', store=False, multi='inactive'),
1324
'to_correct_ok': fields.boolean(string='Line to correct'),
1325
'text_error': fields.text(string='Error', readonly=True),
1326
'inventory_ids': fields.many2many('stock.inventory', 'stock_inventory_move_rel', 'move_id', 'inventory_id', 'Created Moves'),
1327
'expired_lot': fields.function(_is_expired_lot, method=True, type='boolean', string='Lot expired', store=False, multi='attribute'),
1328
'product_tbd': fields.function(_is_expired_lot, method=True, type='boolean', string='TbD', store=False, multi='attribute'),
1329
'has_to_be_resourced': fields.boolean(string='Has to be resourced'),
1330
'from_wkf': fields.related('picking_id', 'from_wkf', type='boolean', string='From wkf'),
1331
'price_changed': fields.function(_is_price_changed, method=True, type='boolean', string='Price changed',
1333
'stock.move': (lambda self, cr, uid, ids, c=None: ids, ['price_unit', 'purchase_order_line'], 10),
1339
'location_dest_id': _default_location_destination,
1340
'processed_stock_move': False, # to know if the stock move has already been partially or completely processed
1341
'inactive_product': False,
1342
'inactive_error': lambda *a: '',
1343
'has_to_be_resourced': False,
1347
def call_cancel_wizard(self, cr, uid, ids, context=None):
1349
Call the wizard to ask user if he wants to re-source the need
1351
mem_obj = self.pool.get('stock.picking.processing.info')
1356
if isinstance(ids, (int, long)):
1359
backmove_ids = self.search(cr, uid, [('backmove_id', 'in', ids), ('state', 'not in', ('done', 'cancel'))], context=context)
1361
for move in self.browse(cr, uid, ids, context=context):
1362
mem_ids = mem_obj.search(cr, uid, [
1363
('picking_id', '=', move.picking_id.id),
1364
('end_date', '=', False),
1367
raise osv.except_osv(
1369
_('The processing of the picking is in progress - You can\'t cancel this move.'),
1371
if backmove_ids or move.product_qty == 0.00:
1372
raise osv.except_osv(_('Error'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try to cancel again.'))
1373
if (move.sale_line_id and move.sale_line_id.order_id) or (move.purchase_line_id and move.purchase_line_id.order_id and (move.purchase_line_id.order_id.po_from_ir or move.purchase_line_id.order_id.po_from_fo)):
1374
vals = {'move_id': ids[0]}
1375
if 'from_int' in context:
1376
"""UFTP-29: we are in a INT stock move - line by line cancel
1377
do not allow Cancel and Resource if move linked to a PO line
1378
=> the INT is sourced from a PO-IN flow
1379
'It should only be possible to resource an INT created from the sourcing of an IR / FO from stock,
1380
but not an INT created by an incoming shipment (Origin field having a "PO" ref.)'
1382
if move.purchase_line_id:
1383
vals['cancel_only'] = True
1385
if move.sale_line_id and move.sale_line_id.type == 'make_to_order':
1386
vals['cancel_only'] = True
1388
wiz_id = self.pool.get('stock.move.cancel.wizard').create(cr, uid, vals, context=context)
1390
return {'type': 'ir.actions.act_window',
1391
'res_model': 'stock.move.cancel.wizard',
1392
'view_type': 'form',
1393
'view_mode': 'form',
1398
return self.unlink(cr, uid, ids, context=context)
1400
def get_price_changed(self, cr, uid, ids, context=None):
1401
if isinstance(ids, (int, long)):
1404
move = self.browse(cr, uid, ids[0], context=context)
1405
if move.price_changed:
1406
func_curr_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
1407
price_unit = move.price_unit
1408
# price_unit = self.pool.get('res.currency').compute(cr, uid,
1409
# func_curr_id, move.price_currency_id.id, move.price_unit, round=True)
1410
raise osv.except_osv(
1412
_('The initial unit price (coming from Purchase order line) is %s %s - The new unit price is %s %s') % (
1413
move.purchase_line_id.price_unit,
1414
move.purchase_line_id.currency_id.name,
1416
move.price_currency_id.name)
1422
def force_assign(self, cr, uid, ids, context=None):
1423
product_tbd = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
1425
for move in self.browse(cr, uid, ids, context=context):
1426
if move.product_id.id == product_tbd and move.from_wkf_line:
1427
ids.pop(ids.index(move.id))
1429
self.infolog(cr, uid, 'Force availability run on stock move #%s (id:%s) of picking id:%s' % (move.line_number, move.id, move.picking_id.id))
1431
return super(stock_move, self).force_assign(cr, uid, ids, context=context)
1433
def _uom_constraint(self, cr, uid, ids, context=None):
1434
for obj in self.browse(cr, uid, ids, context=context):
1435
if not self.pool.get('uom.tools').check_uom(cr, uid, obj.product_id.id, obj.product_uom.id, context):
1436
raise osv.except_osv(_('Error'), _('You have to select a product UOM in the same category than the purchase UOM of the product !'))
1440
def _check_restriction_line(self, cr, uid, ids, context=None):
1442
Check if there is restriction on lines
1444
if isinstance(ids, (int, long)):
1450
for move in self.browse(cr, uid, ids, context=context):
1451
if move.picking_id and move.picking_id.type == 'internal' and move.product_id:
1452
if not self.pool.get('product.product')._get_restriction_error(cr, uid, move.product_id.id, vals={'constraints': {'location_id': move.location_dest_id}}, context=context):
1457
_constraints = [(_uom_constraint, 'Constraint error on Uom', [])]
1459
def create(self, cr, uid, vals, context=None):
1461
1/ Add the corresponding line number: (delivery_mechanism)
1462
- if a corresponding purchase order line or sale order line
1463
exist, we take the line number from there
1464
2/ Add subtype on creation if product is specified (product_asset)
1465
3/ Complete info normally generated by javascript on_change function (specific_rules)
1466
4/ Update the partner or the address according to the other (stock_override)
1467
5/ Set default values for data.xml and tests.yml (reason_types)
1470
pick_obj = self.pool.get('stock.picking')
1471
seq_obj = self.pool.get('ir.sequence')
1472
prod_obj = self.pool.get('product.product')
1473
data_obj = self.pool.get('ir.model.data')
1474
addr_obj = self.pool.get('res.partner.address')
1475
user_obj = self.pool.get('res.users')
1476
location_obj = self.pool.get('stock.location')
1477
partner_obj = self.pool.get('res.partner')
1482
id_cross = data_obj.get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
1483
id_nonstock = data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
1484
id_pack = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'stock_location_packing')[1]
1486
# line number correspondance to be checked with Magali
1487
val_type = vals.get('type', False)
1489
if vals.get('picking_id', False):
1490
picking = pick_obj.browse(cr, uid, vals['picking_id'], context=context)
1491
if not vals.get('line_number', False):
1492
# new number need - gather the line number form the sequence
1493
sequence_id = picking.move_sequence_id.id
1494
line = seq_obj.get_id(cr, uid, sequence_id, code_or_id='id', context=context)
1495
# update values with line value
1496
vals['line_number'] = line
1499
val_type = picking.type
1501
if vals.get('product_id', False):
1502
product = prod_obj.browse(cr, uid, vals['product_id'], context=context)
1503
vals['subtype'] = product.subtype
1505
if not context.get('non_stock_noupdate') and vals.get('picking_id') \
1506
and product.type == 'consu' \
1507
and vals.get('location_dest_id') != id_cross:
1508
if vals.get('sale_line_id'):
1509
if picking.type == 'out':
1510
vals['location_id'] = id_cross
1512
vals['location_id'] = id_nonstock
1513
vals['location_dest_id'] = id_pack
1515
if picking.type != 'out':
1516
vals['location_dest_id'] = id_nonstock
1518
if product.batch_management:
1519
vals['hidden_batch_management_mandatory'] = True
1520
elif product.perishable:
1521
vals['hidden_perishable_mandatory'] = True
1523
vals.update({'hidden_batch_management_mandatory': False,
1524
'hidden_perishable_mandatory': False})
1526
if not vals.get('partner_id2', False):
1527
if vals.get('address_id', False):
1528
addr = addr_obj.read(cr, uid, vals['address_id'], ['partner_id'], context=context)
1529
vals['partner_id2'] = addr['partner_id'] and addr['partner_id'][0] or False
1531
vals['partner_id2'] = user_obj.browse(cr, uid, uid, context=context).company_id.partner_id.id
1533
if not vals.get('address_id', False) and vals.get('partner_id2', False):
1534
addr = partner_obj.address_get(cr, uid, vals['partner_id2'], ['delivery', 'default'])
1535
vals['address_id'] = addr.get('delivery', addr.get('default', False))
1537
if val_type == 'in' and not vals.get('date_expected'):
1538
vals['date_expected'] = time.strftime('%Y-%m-%d %H:%M:%S')
1540
if vals.get('date_expected'):
1541
vals['date'] = vals.get('date_expected')
1543
if vals.get('location_dest_id', False):
1544
loc_dest_id = location_obj.browse(cr, uid, vals['location_dest_id'], context=context)
1545
if not loc_dest_id.virtual_location:
1546
if loc_dest_id.scrap_location:
1547
vals['reason_type_id'] = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_scrap')[1]
1548
elif loc_dest_id.usage == 'inventory':
1549
vals['reason_type_id'] = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loss')[1]
1551
# If the source location and teh destination location are the same, the state should be 'Closed'
1552
if vals.get('location_id', False) == vals.get('location_dest_id', False):
1553
vals['state'] = 'done'
1555
# Change the reason type of the picking if it is not the same
1556
if picking and not context.get('from_claim') and not context.get('from_chaining') \
1557
and vals.get('reason_type_id', False) != picking.reason_type_id.id:
1558
other_type_id = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_other')[1]
1559
pick_obj.write(cr, uid, [picking.id], {'reason_type_id': other_type_id}, context=context)
1561
return super(stock_move, self).create(cr, uid, vals, context=context)
1563
def write(self, cr, uid, ids, vals, context=None):
1565
Update the partner or the address according to the other
1568
prod_obj = self.pool.get('product.product')
1569
data_obj = self.pool.get('ir.model.data')
1570
loc_obj = self.pool.get('stock.location')
1571
pick_obj = self.pool.get('stock.picking')
1572
addr_obj = self.pool.get('res.partner.address')
1573
partner_obj = self.pool.get('res.partner')
1578
if isinstance(ids, (int, long)):
1583
id_cross = data_obj.get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
1585
if vals.get('product_id', False):
1586
# complete hidden flags - needed if not created from GUI
1587
product = prod_obj.browse(cr, uid, vals['product_id'], context=context)
1589
'hidden_batch_management_mandatory': product.batch_management,
1590
'hidden_perishable_mandatory': product.perishable,
1593
if vals.get('picking_id'):
1594
pick_bro = pick_obj.browse(cr, uid, vals['picking_id'], context=context)
1596
if pick_bro and product and product.type == 'consu' and vals.get('location_dest_id') != id_cross:
1597
id_nonstock = data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')
1598
if vals.get('sale_line_id'):
1599
id_pack = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'stock_location_packing')
1601
'location_id': pick_bro.type == 'out' and id_cross or id_nonstock[1],
1602
'location_dest_id': id_pack[1],
1604
elif pick_bro.type != 'out':
1605
vals['location_dest_id'] = id_nonstock[1]
1607
if vals.get('location_dest_id'):
1608
dest_id = loc_obj.browse(cr, uid, vals['location_dest_id'], context=context)
1609
if dest_id.usage == 'inventory' and not dest_id.virtual_location:
1610
vals['reason_type_id'] = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loss')[1]
1611
if dest_id.scrap_location and not dest_id.virtual_location:
1612
vals['reason_type_id'] = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_scrap')[1]
1613
# if the source location and the destination location are the same, the state is done
1614
if 'location_id' in vals and vals['location_dest_id'] == vals['location_id']:
1615
vals['state'] = 'done'
1617
addr = vals.get('address_id')
1618
partner = vals.get('partner_id2')
1620
cond1 = not addr and partner
1621
cond2 = not partner and addr
1623
if vals.get('date_expected') or vals.get('reason_type_id') or cond1 or cond2:
1624
for move in self.browse(cr, uid, ids, context=context):
1625
if cond1 and move.partner_id.id != partner:
1626
addr = partner_obj.address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
1627
vals['address_id'] = addr.get('delivery', False) or addr.get('default')
1629
if cond2 and move.address_id.id != vals.get('address_id'):
1630
addr = addr_obj.browse(cr, uid, vals.get('address_id'), context=context)
1631
vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
1633
if vals.get('date_expected') and vals.get('state', move.state) not in ('done', 'cancel'):
1634
vals['date'] = vals.get('date_expected')
1636
# Change the reason type of the picking if it is not the same
1637
if 'reason_type_id' in vals:
1638
if move.picking_id and move.picking_id.reason_type_id.id != vals['reason_type_id']:
1639
other_type_id = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_other')[1]
1640
pick_obj.write(cr, uid, move.picking_id.id, {'reason_type_id': other_type_id}, context=context)
1642
return super(stock_move, self).write(cr, uid, ids, vals, context=context)
1644
def on_change_partner(self, cr, uid, ids, partner_id, address_id, context=None):
1646
Change the delivery address when the partner change.
1652
v.update({'address_id': False})
1654
d.update({'address_id': [('partner_id', '=', partner_id)]})
1658
addr = self.pool.get('res.partner.address').browse(cr, uid, address_id, context=context)
1660
if not address_id or addr.partner_id.id != partner_id:
1661
addr = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
1662
if not addr.get('delivery'):
1663
addr = addr.get('default')
1665
addr = addr.get('delivery')
1667
v.update({'address_id': addr})
1673
def copy(self, cr, uid, id, default=None, context=None):
1675
Remove the already confirmed flag
1679
default.update({'already_confirmed':False})
1681
return super(stock_move, self).copy(cr, uid, id, default, context=context)
1683
def copy_data(self, cr, uid, id, default=None, context=None):
1685
Remove the dpo_line_id link
1690
if not 'dpo_line_id' in default:
1691
default['dpo_line_id'] = 0
1693
if not 'sync_dpo' in default:
1694
default['sync_dpo'] = False
1696
return super(stock_move, self).copy_data(cr, uid, id, default, context=context)
1698
def fefo_update(self, cr, uid, ids, context=None):
1700
Update batch, Expiry Date, Location according to FEFO logic
1702
if isinstance(ids, (int, long)):
1707
loc_obj = self.pool.get('stock.location')
1708
prodlot_obj = self.pool.get('stock.production.lot')
1709
for move in self.browse(cr, uid, ids, context):
1710
compare_date = context.get('rw_date', False)
1711
move_unlinked = False
1713
compare_date = datetime.strptime(compare_date[0:10], '%Y-%m-%d')
1715
today = datetime.today()
1716
compare_date = datetime(today.year, today.month, today.day)
1718
if move.state == 'assigned' and not move.prodlot_id: # a check_availability has already been done in action_assign, so we take only the 'assigned' lines
1719
needed_qty = move.product_qty
1720
res = loc_obj.compute_availability(cr, uid, [move.location_id.id], True, move.product_id.id, move.product_uom.id, context=context)
1722
# We need to have the value like below because we need to have the id of the m2o (which is not possible if we do self.read(cr, uid, move.id))
1723
values = {'name': move.name,
1724
'sale_line_id': move.sale_line_id and move.sale_line_id.id or False,
1725
'picking_id': move.picking_id.id,
1726
'product_uom': move.product_uom.id,
1727
'product_id': move.product_id.id,
1728
'date_expected': move.date_expected,
1730
'state': 'assigned',
1731
'location_dest_id': move.location_dest_id.id,
1732
'reason_type_id': move.reason_type_id.id,
1734
for loc in res['fefo']:
1735
# if source == destination, the state becomes 'done', so we don't do fefo logic in that case
1736
if not move.location_dest_id.id == loc['location_id']:
1737
# we ignore the batch that are outdated
1738
expired_date = prodlot_obj.read(cr, uid, loc['prodlot_id'], ['life_date'], context)['life_date']
1739
if datetime.strptime(expired_date, "%Y-%m-%d") >= compare_date:
1741
if not move.move_dest_id:
1742
# Search if a stock move with the same location_id and same product_id and same prodlot_id exist
1743
existed_moves = self.search(cr, uid, [('picking_id', '!=', False), ('picking_id', '=', move.picking_id.id),
1744
('product_id', '=', move.product_id.id), ('product_uom', '=', loc['uom_id']),
1745
('line_number', '=', move.line_number), ('location_id', '=', loc['location_id']),
1746
('location_dest_id', '=', move.location_dest_id.id), ('prodlot_id', '=', loc['prodlot_id'])], context=context)
1747
# as long all needed are not fulfilled
1749
# if the batch already exists and qty is enough, it is available (assigned)
1750
if needed_qty <= loc['qty']:
1751
# TODO: Why this condition because move.prodlot_id is always False (e.g. line 1261 of this file)
1752
if move.prodlot_id.id == loc['prodlot_id']:
1753
self.write(cr, uid, move.id, {'state': 'assigned'}, context)
1755
exist_move = self.browse(cr, uid, existed_moves[0], context)
1756
self.write(cr, uid, [exist_move.id], {'product_qty': needed_qty + exist_move.product_qty}, context)
1757
self.write(cr, uid, [move.id], {'state': 'draft'}, context=context)
1758
# We update the linked documents
1759
self.update_linked_documents(cr, uid, [move.id], exist_move.id, context=context)
1760
self.unlink(cr, uid, [move.id], context)
1761
move_unlinked = True
1763
self.write(cr, uid, move.id, {'product_qty': needed_qty, 'product_uom': loc['uom_id'],
1764
'location_id': loc['location_id'], 'prodlot_id': loc['prodlot_id']}, context)
1768
# we take all available
1769
selected_qty = loc['qty']
1770
needed_qty -= selected_qty
1771
dict_for_create = {}
1772
dict_for_create = values.copy()
1773
dict_for_create.update({'product_uom': loc['uom_id'], 'product_qty': selected_qty, 'location_id': loc['location_id'], 'prodlot_id': loc['prodlot_id'], 'line_number': move.line_number, 'move_cross_docking_ok': move.move_cross_docking_ok})
1775
exist_move = self.browse(cr, uid, existed_moves[0], context)
1776
self.write(cr, uid, [exist_move.id], {'product_qty': selected_qty + exist_move.product_qty}, context)
1778
self.create(cr, uid, dict_for_create, context)
1779
self.write(cr, uid, move.id, {'product_qty': needed_qty})
1780
# if the batch is outdated, we remove it
1781
if not context.get('yml_test', False):
1782
if not move_unlinked and move.expired_date and not datetime.strptime(move.expired_date, "%Y-%m-%d") >= compare_date:
1783
# Don't remove the batch if the move is a chained move
1784
if not self.search(cr, uid, [('move_dest_id', '=',
1785
move.id)], limit=1, order='NO_ORDER', context=context):
1786
self.write(cr, uid, move.id, {'prodlot_id': False}, context)
1787
elif move.state == 'confirmed':
1788
# we remove the prodlot_id in case that the move is not available
1789
self.write(cr, uid, move.id, {'prodlot_id': False}, context)
1792
def action_confirm(self, cr, uid, ids, context=None):
1794
Set the bool already confirmed to True
1796
ids = isinstance(ids, (int, long)) and [ids] or ids
1798
no_product = self.search(cr, uid, [
1800
('product_qty', '<=', 0.00),
1801
], limit=1, order='NO_ORDER', context=context)
1804
raise osv.except_osv(_('Error'), _('You cannot confirm a stock move without quantity.'))
1806
res = super(stock_move, self).action_confirm(cr, uid, ids, context=context)
1808
self.write(cr, uid, ids, {'already_confirmed': True}, context=context)
1812
def _hook_confirmed_move(self, cr, uid, *args, **kwargs):
1816
move = kwargs['move']
1817
if not move.already_confirmed:
1818
self.action_confirm(cr, uid, [move.id])
1821
def _hook_move_cancel_state(self, cr, uid, *args, **kwargs):
1823
Change the state of the chained move
1825
if kwargs.get('context'):
1826
kwargs['context'].update({'call_unlink': True})
1827
return {'state': 'cancel'}, kwargs.get('context', {})
1829
def _hook_write_state_stock_move(self, cr, uid, done, notdone, count):
1835
# If source location == dest location THEN stock move is done.
1836
for line in self.read(cr, uid, done, ['location_id', 'location_dest_id']):
1837
if line.get('location_id') and line.get('location_dest_id') and line.get('location_id') == line.get('location_dest_id'):
1838
done_ids.append(line['id'])
1840
assigned_ids.append(line['id'])
1843
self.write(cr, uid, done_ids, {'state': 'done'})
1845
self.write(cr, uid, assigned_ids, {'state': 'assigned'})
1848
self.write(cr, uid, notdone, {'state': 'confirmed'})
1849
self.action_assign(cr, uid, notdone)
1852
def _hook_check_assign(self, cr, uid, *args, **kwargs):
1854
kwargs['move'] is the current move
1856
move = kwargs['move']
1857
return move.location_id.usage == 'supplier'
1859
def _hook_cancel_assign_batch(self, cr, uid, ids, context=None):
1861
Please copy this to your module's method also.
1862
This hook belongs to the cancel_assign method from stock>stock.py>stock_move class
1864
- it erases the batch number associated if any and reset the source location to the original one.
1866
if isinstance(ids, (int, long)):
1871
for line in self.browse(cr, uid, ids, context):
1873
self.write(cr, uid, ids, {'prodlot_id': False, 'expired_date': False})
1874
# UF-2426: If the cancel is called from sync, do not change the source location!
1875
if not context.get('sync_message_execution', False) and line.location_id.location_id and line.location_id.location_id.usage != 'view':
1876
self.write(cr, uid, ids, {'location_id': line.location_id.location_id.id})
1879
def check_assign(self, cr, uid, ids, context=None):
1880
res = super(stock_move, self).check_assign(cr, uid, ids, context=context)
1882
self.infolog(cr, uid, 'Check availability ran on stock.move id:%s' % move_id)
1886
def cancel_assign(self, cr, uid, ids, context=None):
1887
res = super(stock_move, self).cancel_assign(cr, uid, ids, context=context)
1890
fields_to_read = ['picking_id', 'product_id', 'product_uom', 'location_id',
1891
'product_qty', 'product_uos_qty', 'location_dest_id',
1892
'prodlot_id', 'asset_id', 'composition_list_id', 'line_number']
1894
for move_data in self.read(cr, uid, ids, fields_to_read, context=context):
1895
search_domain = [('state', '=', 'confirmed'), ('id', '!=', move_data['id'])]
1897
self.infolog(cr, uid, 'Cancel availability run on stock move #%s (id:%s) of picking id:%s' % (
1898
move_data['line_number'],
1900
move_data['picking_id'][0]))
1902
for f in fields_to_read:
1903
if f in ('product_qty', 'product_uos_qty'):
1906
if isinstance(move_data[f], tuple):
1908
search_domain.append((f, '=', d))
1910
move_ids = self.search(cr, uid, search_domain, context=context)
1912
move = self.browse(cr, uid, move_ids[0], context=context)
1914
self.write(cr, uid, [move.id], {'product_qty': move.product_qty + move_data['product_qty'],
1915
'product_uos_qty': move.product_uos_qty + move_data['product_uos_qty']}, context=context)
1917
# Update all link objects
1918
proc_ids = self.pool.get('procurement.order').search(cr, uid,
1919
[('move_id', '=', move_data['id'])], order='NO_ORDER',context=context)
1921
self.pool.get('procurement.order').write(cr, uid, proc_ids, {'move_id': move.id}, context=context)
1923
pol_ids = self.pool.get('purchase.order.line').search(cr, uid,
1924
[('move_dest_id', '=', move_data['id'])],
1925
order='NO_ORDER', context=context)
1927
self.pool.get('purchase.order.line').write(cr, uid, pol_ids, {'move_dest_id': move.id}, context=context)
1929
move_dest_ids = self.search(cr, uid, [('move_dest_id', '=',
1930
move_data['id'])], order='NO_ORDER', context=context)
1932
self.write(cr, uid, move_dest_ids, {'move_dest_id': move.id}, context=context)
1934
backmove_ids = self.search(cr, uid, [('backmove_id', '=',
1935
move_data['id'])], order='NO_ORDER', context=context)
1937
self.write(cr, uid, backmove_ids, {'backmove_id': move.id}, context=context)
1939
pack_backmove_ids = self.search(cr, uid,
1940
[('backmove_packing_id', '=', move_data['id'])],
1941
order='NO_ORDER', context=context)
1942
if pack_backmove_ids:
1943
self.write(cr, uid, pack_backmove_ids, {'backmove_packing_id': move.id}, context=context)
1945
self.write(cr, uid, [move_data['id']], {'state': 'draft'}, context=context)
1946
self.unlink(cr, uid, move_data['id'], context=context)
1950
def _hook_copy_stock_move(self, cr, uid, res, move, done, notdone):
1953
move_id = self.copy(cr, uid, move.id, {'line_number': move.line_number, 'product_qty': r[0], 'product_uos_qty': r[0] * move.product_id.uos_coeff, 'location_id': r[1]})
1955
done.append(move_id)
1957
notdone.append(move_id)
1958
return done, notdone
272
1960
def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
403
2098
return [move.id for move in complete]
404
2099
# @@@override end
2101
def _get_destruction_products(self, cr, uid, ids, product_ids=False, context=None, recursive=False):
2102
""" Finds the product quantity and price for particular location.
2106
if isinstance(ids, (int, long)):
2110
for move in self.browse(cr, uid, ids, context=context):
2111
# add this move into the list of result
2119
sub_total = move.product_qty * move.product_id.standard_price
2122
if move.purchase_line_id and move.purchase_line_id.currency_id:
2123
currency = move.purchase_line_id.currency_id.name
2124
elif move.sale_line_id and move.sale_line_id.currency_id:
2125
currency = move.sale_line_id.currency_id.name
2128
'prod_name': move.product_id.name,
2129
'prod_code': move.product_id.code,
2130
'prod_price': move.product_id.standard_price,
2131
'sub_total': sub_total,
2132
'currency': currency,
2133
'origin': move.origin,
2134
'expired_date': move.expired_date,
2135
'prodlot_id': move.prodlot_id.name,
2136
'dg_check': dg_check_flag,
2137
'np_check': np_check_flag,
2138
'uom': move.product_uom.name,
2139
'prod_qty': move.product_qty,
2143
def in_action_confirm(self, cr, uid, ids, context=None):
2145
Incoming: draft or confirmed: validate and assign
2147
if isinstance(ids, (int, long)):
2149
self.action_confirm(cr, uid, ids, context)
2150
self.action_assign(cr, uid, ids, context)
2153
# @@@override stock>stock.py>stock_move>_chain_compute
2154
def _chain_compute(self, cr, uid, moves, context=None):
2155
""" Finds whether the location has chained location type or not.
2156
@param moves: Stock moves
2157
@return: Dictionary containing destination location with chained location type.
2163
moves_by_location = {}
2164
pick_by_journal = {}
2167
partner_id = m.picking_id and m.picking_id.address_id and m.picking_id.address_id.partner_id or False
2168
dest = self.pool.get('stock.location').chained_location_get(
2174
m.product_id.nomen_manda_0,
2177
if dest and not m.not_chained:
2178
if dest[1] == 'transparent' and context.get('action_confirm', False):
2179
newdate = (datetime.strptime(m.date, '%Y-%m-%d %H:%M:%S') + relativedelta(days=dest[2] or 0)).strftime('%Y-%m-%d')
2180
moves_by_location.setdefault(dest[0].id, {}).setdefault(newdate, [])
2181
moves_by_location[dest[0].id][newdate].append(m.id)
2182
journal_id = dest[3] or (m.picking_id and m.picking_id.stock_journal_id and m.picking_id.stock_journal_id.id) or False
2183
pick_by_journal.setdefault(journal_id, set())
2184
pick_by_journal[journal_id].add(m.picking_id.id)
2185
elif not context.get('action_confirm', False):
2186
result.setdefault(m.picking_id, [])
2187
result[m.picking_id].append((m, dest))
2189
for journal_id, pick_ids in pick_by_journal.iteritems():
2191
self.pool.get('stock.picking').write(cr, uid, list(pick_ids), {'journal_id': journal_id}, context=context)
2194
for location_id in moves_by_location.keys():
2195
for newdate, move_ids in moves_by_location[location_id].iteritems():
2196
self.write(cr, uid, move_ids, {'location_dest_id': location_id,
2197
'date': newdate}, context=context)
2198
new_moves.extend(move_ids)
2201
new_moves = self.browse(cr, uid, new_moves, context=context)
2202
res2 = self._chain_compute(cr, uid, new_moves, context=context)
2203
for pick_id in res2.keys():
2204
result.setdefault(pick_id, [])
2205
result[pick_id] += res2[pick_id]
2210
# @@@override stock>stock.py>stock_move>_create_chained_picking
2211
def _create_chained_picking(self, cr, uid, pick_name, picking, ptype, move, context=None):
2215
res_obj = self.pool.get('res.company')
2216
picking_obj = self.pool.get('stock.picking')
2217
data_obj = self.pool.get('ir.model.data')
2219
context['from_chaining'] = True
2221
reason_type_id = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_move')[1]
2225
'origin': tools.ustr(picking.origin or ''),
2227
'note': picking.note,
2228
'move_type': picking.move_type,
2229
'auto_picking': move[0][1][1] == 'auto',
2230
'stock_journal_id': move[0][1][3],
2231
'company_id': move[0][1][4] or res_obj._company_default_get(cr, uid, 'stock.company', context=context),
2232
'address_id': picking.address_id.id,
2233
'invoice_state': 'none',
2234
'date': picking.date,
2235
'sale_id': picking.sale_id and picking.sale_id.id or False,
2236
'auto_picking': picking.type == 'in' and any(m.direct_incoming for m in picking.move_lines),
2237
'reason_type_id': reason_type_id,
2238
'previous_chained_pick_id': picking.id,
2241
return picking_obj.create(cr, uid, pick_values, context=context)
2246
#-----------------------------------------
2248
#-----------------------------------------
2249
class stock_location(osv.osv):
2250
_name = 'stock.location'
2251
_inherit = 'stock.location'
2257
if hasattr(super(stock_location, self), 'init'):
2258
super(stock_location, self).init(cr)
2260
mod_obj = self.pool.get('ir.module.module')
2261
logging.getLogger('init').info('HOOK: module stock_override: loading stock_data.xml')
2262
pathname = path.join('stock_override', 'stock_data.xml')
2263
file = tools.file_open(pathname)
2264
tools.convert_xml_import(cr, 'stock_override', file, {}, mode='init', noupdate=False)
2266
def _product_value(self, cr, uid, ids, field_names, arg, context=None):
2267
"""Computes stock value (real and virtual) for a product, as well as stock qty (real and virtual).
2268
@param field_names: Name of field
2269
@return: Dictionary of values
2271
result = super(stock_location, self)._product_value(cr, uid, ids, field_names, arg, context=context)
2273
product_product_obj = self.pool.get('product.product')
2274
currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
2275
currency_obj = self.pool.get('res.currency')
2276
currency = currency_obj.browse(cr, uid, currency_id, context=context)
2277
if context.get('product_id'):
2278
view_ids = self.search(cr, uid, [('usage', '=', 'view')], context=context)
2279
result.update(dict([(i, {}.fromkeys(field_names, 0.0)) for i in list(set([aaa for aaa in view_ids]))]))
2280
for loc_id in view_ids:
2281
c = (context or {}).copy()
2282
c['location'] = loc_id
2283
c['compute_child'] = True
2284
for prod in product_product_obj.browse(cr, uid, [context.get('product_id')], context=c):
2285
for f in field_names:
2286
if f == 'stock_real':
2287
if loc_id not in result:
2289
result[loc_id][f] += prod.qty_available
2290
elif f == 'stock_virtual':
2291
result[loc_id][f] += prod.virtual_available
2292
elif f == 'stock_real_value':
2293
amount = prod.qty_available * prod.standard_price
2294
amount = currency_obj.round(cr, uid, currency.rounding, amount)
2295
result[loc_id][f] += amount
2296
elif f == 'stock_virtual_value':
2297
amount = prod.virtual_available * prod.standard_price
2298
amount = currency_obj.round(cr, uid, currency.rounding, amount)
2299
result[loc_id][f] += amount
2303
def _fake_get(self, cr, uid, ids, fields, arg, context=None):
2305
if isinstance(ids, (int, long)):
2311
def _prod_loc_search(self, cr, uid, ids, fields, arg, context=None):
2312
if not arg or not arg[0] or not arg[0][2] or not arg[0][2][0]:
2316
id_nonstock = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
2317
id_cross = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
2318
prod_obj = self.pool.get('product.product').browse(cr, uid, arg[0][2][0])
2319
if prod_obj and prod_obj.type == 'consu':
2320
if arg[0][2][1] == 'in':
2321
id_virt = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_locations_virtual')[1]
2322
ids_child = self.pool.get('stock.location').search(cr, uid,
2323
[('location_id', 'child_of', id_virt)],
2325
return [('id', 'in', [id_nonstock, id_cross] + ids_child)]
2327
return [('id', 'in', [id_cross])]
2329
elif prod_obj and prod_obj.type != 'consu':
2330
if arg[0][2][1] == 'in':
2331
return [('id', 'in', ids_child)]
2333
return [('id', 'not in', [id_nonstock]), ('usage', '=', 'internal')]
2335
return [('id', 'in', [])]
2337
def _cd_search(self, cr, uid, ids, fields, arg, context=None):
2338
id_cross = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
2342
obj_pol = arg[0][2][0] and self.pool.get('purchase.order.line').browse(cr, uid, arg[0][2][0]) or False
2343
if (obj_pol and obj_pol.order_id.cross_docking_ok) or arg[0][2][1]:
2344
return [('id', 'in', [id_cross])]
2347
def _check_usage(self, cr, uid, ids, fields, arg, context=None):
2348
if not arg or not arg[0][2]:
2352
prod_obj = self.pool.get('product.product').browse(cr, uid, arg[0][2])
2353
if prod_obj.type == 'service_recep':
2354
ids = self.pool.get('stock.location').search(cr, uid, [('usage',
2355
'=', 'inventory')], order='NO_ORDER')
2356
return [('id', 'in', ids)]
2357
elif prod_obj.type == 'consu':
2360
ids = self.pool.get('stock.location').search(cr, uid, [('usage',
2361
'=', 'internal')], order='NO_ORDER')
2362
return [('id', 'in', ids)]
2367
'chained_location_type': fields.selection([('none', 'None'), ('customer', 'Customer'), ('fixed', 'Fixed Location'), ('nomenclature', 'Nomenclature')],
2368
'Chained Location Type', required=True,
2369
help="Determines whether this location is chained to another location, i.e. any incoming product in this location \n" \
2370
"should next go to the chained location. The chained location is determined according to the type :"\
2371
"\n* None: No chaining at all"\
2372
"\n* Customer: The chained location will be taken from the Customer Location field on the Partner form of the Partner that is specified in the Picking list of the incoming products." \
2373
"\n* Fixed Location: The chained location is taken from the next field: Chained Location if Fixed." \
2374
"\n* Nomenclature: The chained location is taken from the options field: Chained Location is according to the nomenclature level of product."\
2376
'chained_options_ids': fields.one2many('stock.location.chained.options', 'location_id', string='Chained options'),
2377
'optional_loc': fields.boolean(string='Is an optional location ?'),
2378
'stock_real': fields.function(_product_value, method=True, type='float', string='Real Stock', multi="stock"),
2379
'stock_virtual': fields.function(_product_value, method=True, type='float', string='Virtual Stock', multi="stock"),
2380
'stock_real_value': fields.function(_product_value, method=True, type='float', string='Real Stock Value', multi="stock", digits_compute=dp.get_precision('Account')),
2381
'stock_virtual_value': fields.function(_product_value, method=True, type='float', string='Virtual Stock Value', multi="stock", digits_compute=dp.get_precision('Account')),
2382
'check_prod_loc': fields.function(_fake_get, method=True, type='many2one', relation='stock.location', string='zz', fnct_search=_prod_loc_search),
2383
'check_cd': fields.function(_fake_get, method=True, type='many2one', relation='stock.location', string='zz', fnct_search=_cd_search),
2384
'check_usage': fields.function(_fake_get, method=True, type='many2one', relation='stock.location', string='zz', fnct_search=_check_usage),
2385
'virtual_location': fields.boolean(string='Virtual location'),
2389
# @@@override stock>stock.py>stock_move>chained_location_get
2390
def chained_location_get(self, cr, uid, location, partner=None, product=None, nomenclature=None, context=None):
2391
""" Finds chained location
2392
@param location: Location id
2393
@param partner: Partner id
2394
@param product: Product id
2395
@param nomen: Nomenclature of the product
2396
@return: List of values
2399
if location.chained_location_type == 'customer':
2401
result = partner.property_stock_customer
2402
elif location.chained_location_type == 'fixed':
2403
result = location.chained_location_id
2404
elif location.chained_location_type == 'nomenclature':
2405
nomen_id = nomenclature and nomenclature.id or (product and product.nomen_manda_0.id)
2406
for opt in location.chained_options_ids:
2407
if opt.nomen_id.id == nomen_id:
2408
result = opt.dest_location_id
2410
return result, location.chained_auto_packing, location.chained_delay, location.chained_journal_id and location.chained_journal_id.id or False, location.chained_company_id and location.chained_company_id.id or False, location.chained_picking_type
2414
def _hook_proct_reserve(self, cr, uid, product_qty, result, amount, id, ids):
2415
result.append((amount, id, True))
2416
product_qty -= amount
2417
if product_qty <= 0.0:
2421
result.append((amount, id, True))
2423
result.append((product_qty, ids[0], False))
2425
result.append((product_qty, id, False))
2429
def on_change_location_type(self, cr, uid, ids, chained_location_type, context=None):
2431
If the location type is changed to 'Nomenclature', set some other fields values
2433
if chained_location_type and chained_location_type == 'nomenclature':
2434
return {'value': {'chained_auto_packing': 'transparent',
2435
'chained_picking_type': 'internal',
2436
'chained_delay': 0}}
2443
class stock_location_chained_options(osv.osv):
2444
_name = 'stock.location.chained.options'
2445
_rec_name = 'location_id'
2448
'dest_location_id': fields.many2one('stock.location', string='Destination Location', required=True),
2449
'nomen_id': fields.many2one('product.nomenclature', string='Nomenclature Level', required=True),
2450
'location_id': fields.many2one('stock.location', string='Location', required=True),
2453
stock_location_chained_options()
2456
class stock_move_cancel_wizard(osv.osv_memory):
2457
_name = 'stock.move.cancel.wizard'
2460
'move_id': fields.many2one('stock.move', string='Move', required=True),
2461
'cancel_only': fields.boolean('Just allow cancel only', invisible=True),
2465
'move_id': lambda self, cr, uid, c: c.get('active_id'),
2466
'cancel_only': False,
2469
def just_cancel(self, cr, uid, ids, context=None):
2471
Just call the cancel of stock.move (re-sourcing flag not set)
2474
move_obj = self.pool.get('stock.move')
2475
pick_obj = self.pool.get('stock.picking')
2477
wf_service = netsvc.LocalService("workflow")
2479
for wiz in self.browse(cr, uid, ids, context=context):
2480
move_id = wiz.move_id.id
2481
picking_id = wiz.move_id.picking_id.id
2482
move_obj.action_cancel(cr, uid, [wiz.move_id.id], context=context)
2483
move_ids = move_obj.search(cr, uid, [('id', '=', wiz.move_id.id)],
2484
limit=1, order='NO_ORDER', context=context)
2485
if move_ids and wiz.move_id.has_to_be_resourced:
2486
self.infolog(cr, uid, "The stock.move id:%s of the picking id:%s has been canceled and resourced" % (move_id, picking_id))
2488
self.infolog(cr, uid, "The stock.move id:%s of the picking id:%s has been canceled" % (move_id, picking_id))
2490
if move_ids and wiz.move_id.picking_id:
2491
lines = wiz.move_id.picking_id.move_lines
2492
if all(l.state == 'cancel' for l in lines):
2493
wf_service.trg_validate(uid, 'stock.picking', wiz.move_id.picking_id.id, 'button_cancel', cr)
2495
return {'type': 'ir.actions.act_window_close'}
2498
def cancel_and_resource(self, cr, uid, ids, context=None):
2500
Call the cancel and resource method of the stock move
2503
move_obj = self.pool.get('stock.move')
2505
move_ids = [x.move_id.id for x in self.browse(cr, uid, ids, context=context)]
2506
move_obj.write(cr, uid, move_ids, {'has_to_be_resourced': True}, context=context)
2508
return self.just_cancel(cr, uid, ids, context=context)
2510
stock_move_cancel_wizard()
2513
class stock_picking_cancel_wizard(osv.osv_memory):
2514
_name = 'stock.picking.cancel.wizard'
2516
def _get_allow_cr(self, cr, uid, context=None):
2518
Define if the C&R are allowed on the wizard
2523
picking_id = context.get('active_id')
2524
for move in self.pool.get('stock.picking').browse(cr, uid, picking_id, context=context).move_lines:
2525
if move.sale_line_id and move.sale_line_id.type == 'make_to_order':
2531
'picking_id': fields.many2one('stock.picking', string='Picking', required=True),
2532
'allow_cr': fields.boolean(string='Allow Cancel and resource'),
2536
'picking_id': lambda self, cr, uid, c: c.get('active_id'),
2537
'allow_cr': _get_allow_cr,
2540
def just_cancel(self, cr, uid, ids, context=None):
2542
Just call the cancel of the stock.picking
2544
wf_service = netsvc.LocalService("workflow")
2545
for wiz in self.browse(cr, uid, ids, context=context):
2546
wf_service.trg_validate(uid, 'stock.picking', wiz.picking_id.id, 'button_cancel', cr)
2548
return {'type': 'ir.actions.act_window_close'}
2550
def cancel_and_resource(self, cr, uid, ids, context=None):
2552
Call the cancel and resource method of the picking
2554
# objects declarations
2555
pick_obj = self.pool.get('stock.picking')
2557
# variables declarations
2560
for wiz in self.browse(cr, uid, ids, context=context):
2561
pick_ids.append(wiz.picking_id.id)
2563
# Set the boolean 'has_to_be_resourced' to True for each picking
2564
vals = {'has_to_be_resourced': True}
2565
pick_obj.write(cr, uid, pick_ids, vals, context=context)
2567
return self.just_cancel(cr, uid, ids, context=context)
2570
stock_picking_cancel_wizard()
2573
class ir_values(osv.osv):
2575
_inherit = 'ir.values'
2577
def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True):
2580
values = super(ir_values, self).get(cr, uid, key, key2, models, meta, context, res_id_req, without_user, key2_req)
2581
trans_obj = self.pool.get('ir.translation')
2583
move_accepted_values = {'client_action_multi': [],
2584
'client_print_multi': [],
2585
'client_action_relate': ['act_relate_picking'],
2586
'tree_but_action': [],
2587
'tree_but_open': []}
2589
incoming_accepted_values = {'client_action_multi': ['act_stock_return_picking', 'action_stock_invoice_onshipping'],
2590
'client_print_multi': ['Reception', 'XML Export'],
2591
'client_action_relate': ['View_log_stock.picking'],
2592
'tree_but_action': [],
2593
'tree_but_open': []}
2595
internal_accepted_values = {'client_action_multi': [],
2596
'client_print_multi': ['Internal Move Excel Export', 'Internal Move'],
2597
'client_action_relate': [],
2598
'tree_but_action': [],
2599
'tree_but_open': []}
2601
delivery_accepted_values = {'client_action_multi': [],
2602
'client_print_multi': ['Labels', 'Delivery Order'],
2603
'client_action_relate': [''],
2604
'tree_but_action': [],
2605
'tree_but_open': []}
2607
picking_accepted_values = {'client_action_multi': [],
2608
'client_print_multi': ['Picking Ticket', 'Pre-Packing List', 'Labels'],
2609
'client_action_relate': [''],
2610
'tree_but_action': [],
2611
'tree_but_open': []}
2613
if 'stock.move' in [x[0] for x in models]:
2615
Destruction_Report = trans_obj.tr_view(cr, 'Destruction Report', context)
2617
if key == 'action' and v[1] in move_accepted_values[key2]:
2618
new_values.append(v)
2619
elif context.get('_terp_view_name', False) == Destruction_Report:
2620
new_values.append(v)
2621
elif context.get('picking_type', False) == 'incoming_shipment' and 'stock.picking' in [x[0] for x in models]:
2624
if key == 'action' and v[1] in incoming_accepted_values[key2]:
2625
new_values.append(v)
2626
elif context.get('picking_type', False) == 'internal_move' and 'stock.picking' in [x[0] for x in models]:
2629
if key == 'action' and v[1] in internal_accepted_values[key2]:
2630
new_values.append(v)
2631
elif context.get('picking_type', False) == 'delivery_order' and 'stock.picking' in [x[0] for x in models]:
2634
if key == 'action' and v[1] in delivery_accepted_values[key2]:
2635
new_values.append(v)
2636
elif context.get('picking_type', False) == 'picking_ticket' and 'stock.picking' in [x[0] for x in models]:
2639
if key == 'action' and v[1] in picking_accepted_values[key2]:
2640
new_values.append(v)