214
284
'consignee_signature': fields.char(string='Signature', size=1024),
216
286
'partner_id': fields.related('address_id', 'partner_id', type='many2one', relation='res.partner', string='Customer', store=True),
287
'partner_id2': fields.many2one('res.partner', string='Customer', required=False),
217
288
'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
218
289
'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
219
'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='Number of Packs', multi='get_vals_X',), # old_multi ship_vals
290
'num_of_packs': fields.function(_vals_get, method=True, fnct_search=_packs_search, type='integer', string='Number of Packs', multi='get_vals_X',), # old_multi ship_vals
220
291
'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
292
'total_volume': fields.function(_vals_get, method=True, type='float', string=u'Total Volume[dm³]', multi='get_vals',),
221
293
'state': fields.function(_vals_get, method=True, type='selection', selection=[('draft', 'Draft'),
222
294
('packed', 'Packed'),
223
295
('shipped', 'Shipped'),
225
('cancel', 'Canceled')], string='State', multi='get_vals',
226
store= {'stock.picking': (_get_shipment_ids, ['state', 'shipment_id',], 10),}),
297
('delivered', 'Delivered'),
298
('cancel', 'Cancelled')], string='State', multi='get_vals',
300
'stock.picking': (_get_shipment_ids, ['state', 'shipment_id', 'delivered'], 10),
227
302
'backshipment_id': fields.function(_vals_get, method=True, type='many2one', relation='shipment', string='Draft Shipment', multi='get_vals',),
303
# added by Quentin https://bazaar.launchpad.net/~unifield-team/unifield-wm/trunk/revision/426.20.14
304
'parent_id': fields.many2one('shipment', string='Parent shipment'),
305
'invoice_id': fields.many2one('account.invoice', string='Related invoice'),
306
'additional_items_ids': fields.one2many('shipment.additionalitems', 'shipment_id', string='Additional Items'),
307
'picking_ids': fields.one2many(
310
string='Associated Packing List',
314
def _get_sequence(self, cr, uid, context=None):
315
ir_id = self.pool.get('ir.model.data')._get_id(cr, uid, 'msf_outgoing', 'seq_shipment')
316
return self.pool.get('ir.model.data').browse(cr, uid, ir_id).res_id
319
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
320
'sequence_id': _get_sequence,
229
323
_order = 'name desc'
231
325
def create_shipment(self, cr, uid, ids, context=None):
233
open the wizard to create (partial) shipment
235
# we need the context for the wizard switch
239
wiz_obj = self.pool.get('wizard')
242
name = _("Create Shipment")
243
model = 'shipment.wizard'
245
# open the selected wizard
246
return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
248
def do_create_shipment(self, cr, uid, ids, context=None):
250
for each original draft picking:
251
- creation of the new packing object with empty moves
252
- convert partial data to move related data
253
- create corresponding moves in new packing
254
- update initial packing object
255
- trigger workflow for new packing object
258
assert context, 'no context, method call is wrong'
259
assert 'partial_datas_shipment' in context, 'partial_datas_shipment no defined in context'
261
pick_obj = self.pool.get('stock.picking')
262
move_obj = self.pool.get('stock.move')
263
shipment_obj = self.pool.get('shipment')
266
partial_datas_shipment = context['partial_datas_shipment']
267
# shipment ids from ids must be equal to shipment ids from partial datas
268
assert set(ids) == set(partial_datas_shipment.keys()), 'shipment ids from ids and partial do not match'
270
for draft_shipment in self.browse(cr, uid, partial_datas_shipment.keys(), context=context):
271
# for each shipment create a new shipment which will be used by the group of new packing objects
272
address_id = shipment_obj.read(cr, uid, [draft_shipment.id], ['address_id'], context=context)[0]['address_id'][0]
273
sequence = draft_shipment.sequence_id
274
shipment_number = sequence.get_id(test='id', context=context)
275
# state is a function - not set
276
shipment_name = draft_shipment.name + '-' + shipment_number
277
values = {'name': shipment_name, 'address_id': address_id}
278
shipment_id = shipment_obj.create(cr, uid, values, context=context)
327
Open the wizard to process the shipment
330
ship_proc_obj = self.pool.get('shipment.processor')
335
if isinstance(ids, (int, long)):
339
raise osv.except_osv(
340
_('Processing Error'),
341
_('No data to process !'),
344
for shipment in self.browse(cr, uid, ids, context=context):
345
# Create the shipment processor wizard
347
'shipment_id': shipment.id,
348
'address_id': shipment.address_id.id,
351
ship_proc_id = ship_proc_obj.create(cr, uid, ship_proc_vals, context=context)
352
ship_proc_obj.create_lines(cr, uid, ship_proc_id, context=context)
355
'type': 'ir.actions.act_window',
356
'name': _('Create shipment'),
357
'res_model': ship_proc_obj._name,
361
'res_id': ship_proc_id,
365
def do_create_shipment(self, cr, uid, wizard_ids, context=None):
369
BE CAREFUL: the wizard_ids parameters is the IDs of the shipment.processor objects,
370
not those of shipment objects
373
ship_proc_obj = self.pool.get('shipment.processor')
374
picking_obj = self.pool.get('stock.picking')
375
add_line_obj = self.pool.get('shipment.additionalitems')
376
data_obj = self.pool.get('ir.model.data')
381
if isinstance(wizard_ids, (int, long)):
382
wizard_ids = wizard_ids
384
for wizard in ship_proc_obj.browse(cr, uid, wizard_ids, context=context):
385
shipment = wizard.shipment_id
386
sequence = shipment.sequence_id
388
shipment_number = sequence.get_id(code_or_id='id', context=context)
389
shipment_name = '%s-%s' % (shipment.name, shipment_number)
392
'name': shipment_name,
393
'address': shipment.address_id.id,
394
'partner_id': shipment.partner_id.id,
395
'partner_id2': shipment.partner_id.id,
396
'shipment_expected_date': shipment.shipment_expected_date,
397
'shipment_actual_date': shipment.shipment_actual_date,
398
'parent_id': shipment.id,
401
shipment_id = self.create(cr, uid, ship_val, context=context)
279
403
context['shipment_id'] = shipment_id
280
for draft_packing in pick_obj.browse(cr, uid, partial_datas_shipment[draft_shipment.id].keys(), context=context):
281
# copy the picking object without moves
282
# creation of moves and update of initial in picking create method
283
context.update(draft_shipment_id=draft_shipment.id, draft_packing_id=draft_packing.id)
284
sequence = draft_packing.sequence_id
285
packing_number = sequence.get_id(test='id', context=context)
286
new_packing_id = pick_obj.copy(cr, uid, draft_packing.id,
287
{'name': draft_packing.name + '-' + packing_number,
288
'backorder_id': draft_packing.id,
289
'shipment_id': False,
290
'move_lines': []}, context=dict(context, keep_prodlot=True, allow_copy=True,))
405
for add_line in wizard.additional_line_ids:
407
'name': add_line.name,
408
'shipment_id': shipment_id,
410
'quantity': add_line.quantity,
411
'uom': add_line.uom_id.id,
412
'comment': add_line.comment,
413
'volume': add_line.volume,
414
'weight': add_line.weight,
417
add_line_obj.create(cr, uid, line_vals, context=context)
419
for family in wizard.family_ids:
420
picking = family.draft_packing_id
421
# Copy the picking object without moves
422
# Creation of moves and update of initial in picking create method
423
sequence = picking.sequence_id
424
packing_number = sequence.get_id(code_or_id='id', context=context)
428
'name': '%s-%s' % (picking.name, packing_number),
429
'backorder_id': picking.id,
430
'shipment_id': False,
434
# Update context for copy
436
'keep_prodlot': True,
437
'keepLineNumber': True,
439
'non_stock_noupdate': True,
440
'shipment_proc_id': wizard.id,
441
'draft_packing_id': picking.id,
444
new_packing_id = picking_obj.copy(cr, uid, picking.id, packing_data, context=context)
448
'keep_prodlot': False,
449
'keepLineNumber': False,
451
'non_stock_noupdate': False,
452
'draft_packing_id': False,
292
455
# confirm the new packing
293
456
wf_service = netsvc.LocalService("workflow")
294
457
wf_service.trg_validate(uid, 'stock.picking', new_packing_id, 'button_confirm', cr)
295
458
# simulate check assign button, as stock move must be available
296
pick_obj.force_assign(cr, uid, [new_packing_id])
298
# log creation message
299
self.log(cr, uid, shipment_id, _('The new Shipment %s has been created.'%shipment_name))
301
# TODO which behavior
302
return {'type': 'ir.actions.act_window_close'}
459
picking_obj.force_assign(cr, uid, [new_packing_id])
461
# Log creation message
462
self.log(cr, uid, shipment.id, _('The new Shipment %s has been created.') % (shipment_name,))
463
# The shipment is automatically shipped, no more pack states in between.
464
self.ship(cr, uid, [shipment_id], context=context)
466
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_shipment_form')
467
view_id = view_id and view_id[1] or False
469
# Remove unneeded data in context
471
'partial_datas_ppl1': {},
476
'name':_("Shipment"),
477
'type': 'ir.actions.act_window',
478
'res_model': 'shipment',
479
'view_mode': 'form,tree',
481
'view_id': [view_id],
482
'res_id': shipment_id,
304
487
def return_packs(self, cr, uid, ids, context=None):
306
open the wizard to return packs from draft shipment
308
# we need the context for the wizard switch
489
Open the wizard to return packs from draft shipment
492
proc_obj = self.pool.get('return.shipment.processor')
309
494
if context is None:
312
wiz_obj = self.pool.get('wizard')
315
name = _("Return Packs")
316
model = 'shipment.wizard'
318
# open the selected wizard
319
return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
321
def do_return_packs(self, cr, uid, ids, context=None):
323
for each original draft picking:
324
- convert partial data to move related data
325
- update the draft_packing's moves, decrease quantity and update from/to info
326
- update initial packing object
327
- create a back move for each move with return quantity to initial location
328
- increase quantity of related draft_picking_ticket's moves
331
assert context, 'no context, method call is wrong'
332
assert 'partial_datas' in context, 'partial_datas no defined in context'
334
pick_obj = self.pool.get('stock.picking')
497
if isinstance(ids, (int, long)):
501
raise osv.except_osv(
502
_('Processing Error'),
503
_('No data to process !'),
506
processor_id = proc_obj.create(cr, uid, {'shipment_id': ids[0]}, context=context)
507
proc_obj.create_lines(cr, uid, processor_id, context=context)
510
'type': 'ir.actions.act_window',
511
'res_model': proc_obj._name,
512
'name': _('Return Packs'),
515
'res_id': processor_id,
520
def do_return_packs(self, cr, uid, wizard_ids, context=None):
522
Return the selected packs to the draft picking ticket
524
BE CAREFUL: the wizard_ids parameters is the IDs of the return.shipment.processor objects,
525
not those of shipment objects
528
proc_obj = self.pool.get('return.shipment.processor')
529
picking_obj = self.pool.get('stock.picking')
335
530
move_obj = self.pool.get('stock.move')
336
obj_data = self.pool.get('ir.model.data')
339
partial_datas = context['partial_datas']
340
# shipment ids from ids must be equal to shipment ids from partial datas
341
assert set(ids) == set(partial_datas.keys()), 'shipment ids from ids and partial do not match'
343
for draft_shipment_id in partial_datas:
344
# log flag - log for draft shipment is displayed only one time for each draft shipment
531
data_obj = self.pool.get('ir.model.data')
536
if isinstance(wizard_ids, (int, long)):
537
wizard_ids = [wizard_ids]
540
raise osv.except_osv(
541
_('Processing Error'),
542
_('No data to process'),
547
for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
549
shipment = wizard.shipment_id
550
shipment_ids.append(shipment.id)
551
# log flag - res.log for draft shipment is displayed only one time for each draft shipment
346
# for each draft packing
347
for draft_packing in pick_obj.browse(cr, uid, partial_datas[draft_shipment_id].keys(), context=context):
348
# corresponding draft picking ticket -> draft_packing - ppl - picking_ticket - draft_picking_ticket
349
draft_picking = draft_packing.previous_step_id.previous_step_id.backorder_id
350
draft_picking_id = draft_packing.previous_step_id.previous_step_id.backorder_id.id
352
for from_pack in partial_datas[draft_shipment_id][draft_packing.id]:
353
for to_pack in partial_datas[draft_shipment_id][draft_packing.id][from_pack]:
354
# partial data for one sequence of one draft packing
355
data = partial_datas[draft_shipment_id][draft_packing.id][from_pack][to_pack][0]
356
# total number of packs
357
total_num = to_pack - from_pack + 1
358
# number of returned packs
359
selected_number = data['selected_number']
360
# we take the packs with the highest numbers
362
selected_from_pack = to_pack - selected_number + 1
363
selected_to_pack = to_pack
364
# update initial moves
365
if selected_number == total_num:
366
# if all packs have been selected, from/to are set to 0
367
initial_from_pack = 0
370
initial_from_pack = from_pack
371
initial_to_pack = to_pack - selected_number
372
# find the concerned stock moves
373
move_ids = move_obj.search(cr, uid, [('picking_id', '=', draft_packing.id),
374
('from_pack', '=', from_pack),
375
('to_pack', '=', to_pack)])
376
# update the moves, decrease the quantities
377
for move in move_obj.browse(cr, uid, move_ids, context=context):
378
# stock move are not canceled as for ppl return process
379
# because this represents a draft packing, meaning some shipment could be canceled and
380
# returned to this stock move
382
initial_qty = move.product_qty
384
return_qty = selected_number * move.qty_per_pack
385
# update initial quantity
386
initial_qty = max(initial_qty - return_qty, 0)
387
values = {'product_qty': initial_qty,
388
'from_pack': initial_from_pack,
389
'to_pack': initial_to_pack,}
391
move_obj.write(cr, uid, [move.id], values, context=context)
393
# create a back move with the quantity to return to the good location
394
# the good location is stored in the 'initial_location' field
395
copy_id = move_obj.copy(cr, uid, move.id, {'product_qty': return_qty,
396
'location_dest_id': move.initial_location.id,
397
'from_pack': selected_from_pack,
398
'to_pack': selected_to_pack,
399
'state': 'done'}, context=context)
400
# find the corresponding move in draft in the draft **picking**
401
draft_move = move.backmove_id
402
# increase the draft move with the move quantity
403
draft_initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
404
draft_initial_qty += return_qty
405
move_obj.write(cr, uid, [draft_move.id], {'product_qty': draft_initial_qty}, context=context)
407
# log the increase action - display the picking ticket view form - log message for each draft packing because each corresponds to a different draft picking
409
draft_shipment_name = self.read(cr, uid, draft_shipment_id, ['name'], context=context)['name']
410
self.log(cr, uid, draft_shipment_id, _("Packs from the draft Shipment (%s) have been returned to stock."%draft_shipment_name),)
412
res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
413
self.pool.get('stock.picking').log(cr, uid, draft_picking_id, _("The corresponding Draft Picking Ticket (%s) has been updated."%draft_picking.name), context={'view_id': res,})
415
# call complete_finished on the shipment object
416
# if everything is alright (all draft packing are finished) the shipment is done also
417
result = self.complete_finished(cr, uid, partial_datas.keys(), context=context)
419
# TODO which behavior
420
return {'type': 'ir.actions.act_window_close'}
554
for family in wizard.family_ids:
555
picking = family.draft_packing_id
556
draft_picking = family.ppl_id and family.ppl_id.previous_step_id and family.ppl_id.previous_step_id.backorder_id or False
558
# Update initial move
559
if family.selected_number == int(family.num_of_packs):
560
# If al packs have been selected, from/to are set to 0
561
initial_from_pack = 0
564
initial_from_pack = family.from_pack
565
initial_to_pack = family.to_pack - family.selected_number
567
# Find the concerned stock moves
568
move_ids = move_obj.search(cr, uid, [
569
('picking_id', '=', picking.id),
570
('from_pack', '=', family.from_pack),
571
('to_pack', '=', family.to_pack),
574
# Update the moves, decrease the quantities
575
for move in move_obj.browse(cr, uid, move_ids, context=context):
577
Stock moves are not canceled as for PPL return process
578
because this represents a draft packing, meaning some shipment could be canceled and
579
return to this stock move
581
return_qty = family.selected_number * move.qty_per_pack
583
'product_qty': max(move.product_qty - return_qty, 0),
584
'from_pack': initial_from_pack,
585
'to_pack': initial_to_pack,
588
move_obj.write(cr, uid, [move.id], move_vals, context=context)
591
Create a back move with the quantity to return to the good location.
592
The good location is store in the 'initial_location' field
595
'product_qty': return_qty,
596
'line_number': move.line_number,
597
'location_dest_id': move.initial_location.id,
598
'from_pack': family.to_pack - family.selected_number + 1,
599
'to_pack': family.to_pack,
602
context['non_stock_noupdate'] = True
604
move_obj.copy(cr, uid, move.id, cp_vals, context=context)
606
context['non_stock_noupdate'] = False
608
# Find the corresponding move in draft in the draft picking ticket
609
draft_move = move.backmove_id
610
# Increase the draft move with the move quantity
611
draft_initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
612
draft_initial_qty += return_qty
613
move_obj.write(cr, uid, [draft_move.id], {'product_qty': draft_initial_qty}, context=context)
615
# log the increase action - display the picking ticket view form - log message for each draft packing because each corresponds to a different draft picking
617
draft_shipment_name = self.read(cr, uid, shipment.id, ['name'], context=context)['name']
618
self.log(cr, uid, shipment.id, _("Packs from the draft Shipment (%s) have been returned to stock.") % (draft_shipment_name,))
621
res = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
624
'picking_type': 'picking.ticket'
627
picking_obj.log(cr, uid, draft_picking.id, _("The corresponding Draft Picking Ticket (%s) has been updated.") % (draft_picking.name,), context=context)
629
# Call complete_finished on the shipment object
630
# If everything is allright (all draft packing are finished) the shipment is done also
631
self.complete_finished(cr, uid, shipment_ids, context=context)
633
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
635
'name':_("Picking Ticket"),
636
'view_mode': 'form,tree',
637
'view_id': [view_id and view_id[1] or False],
639
'res_model': 'stock.picking',
640
'res_id': draft_picking.id,
641
'type': 'ir.actions.act_window',
422
646
def return_packs_from_shipment(self, cr, uid, ids, context=None):
424
open the wizard to return packs from draft shipment
426
# we need the context for the wizard switch
648
Open the wizard to return packs from draft shipment
651
proc_obj = self.pool.get('return.pack.shipment.processor')
427
653
if context is None:
430
wiz_obj = self.pool.get('wizard')
433
name = _("Return Packs from Shipment")
434
model = 'shipment.wizard'
435
step = 'returnpacksfromshipment'
436
# open the selected wizard
437
return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
656
if isinstance(ids, (int, long)):
660
raise osv.except_osv(
661
_('Processing Error'),
662
_('No data to process !'),
665
processor_id = proc_obj.create(cr, uid, {'shipment_id': ids[0]}, context=context)
666
proc_obj.create_lines(cr, uid, processor_id, context=context)
669
'type': 'ir.actions.act_window',
670
'res_model': proc_obj._name,
671
'name': _('Return Packs from Shipment'),
674
'res_id': processor_id,
439
679
def compute_sequences(self, cr, uid, ids, context=None, *args, **kwargs):
441
681
compute corresponding sequences
759
1049
# ask for draft picking validation, depending on picking completion
760
1050
# if picking ticket is not completed, the validation will not complete
761
1051
draft_packing.previous_step_id.previous_step_id.backorder_id.validate(context=context)
1053
# UF-1617: set the flag to PPL to indicate that the SHIP has been done, for synchronisation purpose
1054
# if draft_packing.previous_step_id and draft_packing.previous_step_id.id:
1055
# cr.execute('update stock_picking set already_shipped=\'t\' where id=%s' %draft_packing.previous_step_id.id)
763
1057
# all draft packing are validated (done state) - the state of shipment is automatically updated -> function
1060
def shipment_create_invoice(self, cr, uid, ids, context=None):
1062
Create invoices for validated shipment
1064
invoice_obj = self.pool.get('account.invoice')
1065
line_obj = self.pool.get('account.invoice.line')
1066
partner_obj = self.pool.get('res.partner')
1067
distrib_obj = self.pool.get('analytic.distribution')
1068
sale_line_obj = self.pool.get('sale.order.line')
1069
sale_obj = self.pool.get('sale.order')
1070
company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
1075
if isinstance(ids, (int, long)):
1078
for shipment in self.browse(cr, uid, ids, context=context):
1079
make_invoice = False
1080
for pack in shipment.pack_family_memory_ids:
1081
for move in pack.move_lines:
1082
if move.state != 'cancel' and (not move.sale_line_id or move.sale_line_id.order_id.order_policy == 'picking'):
1085
if not make_invoice:
1088
payment_term_id = False
1089
partner = shipment.partner_id2
1091
raise osv.except_osv(_('Error, no partner !'),
1092
_('Please put a partner on the shipment if you want to generate invoice.'))
1094
inv_type = 'out_invoice'
1096
if inv_type in ('out_invoice', 'out_refund'):
1097
account_id = partner.property_account_receivable.id
1098
payment_term_id = partner.property_payment_term and partner.property_payment_term.id or False
1100
account_id = partner.property_account_payable.id
1102
addresses = partner_obj.address_get(cr, uid, [partner.id], ['contact', 'invoice'])
1103
today = time.strftime('%Y-%m-%d', time.localtime())
1106
'name': shipment.name,
1107
'origin': shipment.name or '',
1109
'account_id': account_id,
1110
'partner_id': partner.id,
1111
'address_invoice_id': addresses['invoice'],
1112
'address_contact_id': addresses['contact'],
1113
'payment_term': payment_term_id,
1114
'fiscal_position': partner.property_account_position.id,
1115
'date_invoice': context.get('date_inv', False) or today,
1119
cur_id = shipment.pack_family_memory_ids[0].currency_id.id
1121
invoice_vals['currency_id'] = cur_id
1123
journal_type = 'sale'
1124
# Disturb journal for invoice only on intermission partner type
1125
if shipment.partner_id2.partner_type == 'intermission':
1126
if not company.intermission_default_counterpart or not company.intermission_default_counterpart.id:
1127
raise osv.except_osv(_('Error'), _('Please configure a default intermission account in Company configuration.'))
1128
invoice_vals['is_intermission'] = True
1129
invoice_vals['account_id'] = company.intermission_default_counterpart.id
1130
journal_type = 'intermission'
1131
journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', journal_type),
1132
('is_current_instance', '=', True)])
1134
raise osv.except_osv(_('Warning'), _('No %s journal found!') % (journal_type,))
1135
invoice_vals['journal_id'] = journal_ids[0]
1137
invoice_id = invoice_obj.create(cr, uid, invoice_vals,
1140
# Change currency for the intermission invoice
1141
if shipment.partner_id2.partner_type == 'intermission':
1142
company_currency = company.currency_id and company.currency_id.id or False
1143
if not company_currency:
1144
raise osv.except_osv(_('Warning'), _('No company currency found!'))
1145
wiz_account_change = self.pool.get('account.change.currency').create(cr, uid, {'currency_id': company_currency}, context=context)
1146
self.pool.get('account.change.currency').change_currency(cr, uid, [wiz_account_change], context={'active_id': invoice_id})
1148
# Link the invoice to the shipment
1149
self.write(cr, uid, [shipment.id], {'invoice_id': invoice_id}, context=context)
1151
# For each stock moves, create an invoice line
1152
for pack in shipment.pack_family_memory_ids:
1153
for move in pack.move_lines:
1154
if move.state == 'cancel':
1157
if move.sale_line_id and move.sale_line_id.order_id.order_policy != 'picking':
1160
origin = move.picking_id.name or ''
1161
if move.picking_id.origin:
1162
origin += ':' + move.picking_id.origin
1164
if inv_type in ('out_invoice', 'out_refund'):
1165
account_id = move.product_id.product_tmpl_id.\
1166
property_account_income.id
1168
account_id = move.product_id.categ_id.\
1169
property_account_income_categ.id
1171
account_id = move.product_id.product_tmpl_id.\
1172
property_account_expense.id
1174
account_id = move.product_id.categ_id.\
1175
property_account_expense_categ.id
1177
# Compute unit price from FO line if the move is linked to
1178
price_unit = move.product_id.list_price
1179
if move.sale_line_id and move.sale_line_id.product_id.id == move.product_id.id:
1180
uos_id = move.product_id.uos_id and move.product_id.uos_id.id or False
1181
price = move.sale_line_id.price_unit
1182
price_unit = self.pool.get('product.uom')._compute_price(cr, uid, move.sale_line_id.product_uom.id, price, move.product_uom.id)
1184
# Get discount from FO line
1186
if move.sale_line_id and move.sale_line_id.product_id.id == move.product_id.id:
1187
discount = move.sale_line_id.discount
1189
# Get taxes from FO line
1190
taxes = move.product_id.taxes_id
1191
if move.sale_line_id and move.sale_line_id.product_id.id == move.product_id.id:
1192
taxes = [x.id for x in move.sale_line_id.tax_id]
1194
if shipment.partner_id2:
1195
tax_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, shipment.partner_id2.property_account_position, taxes)
1197
tax_ids = map(lambda x: x.id, taxes)
1200
if move.sale_line_id:
1201
sol_ana_dist_id = move.sale_line_id.analytic_distribution_id or move.sale_line_id.order_id.analytic_distribution_id
1203
distrib_id = distrib_obj.copy(cr, uid, sol_ana_dist_id.id, context=context)
1205
# set UoS if it's a sale and the picking doesn't have one
1206
uos_id = move.product_uos and move.product_uos.id or False
1207
if not uos_id and inv_type in ('out_invoice', 'out_refund'):
1208
uos_id = move.product_uom.id
1209
account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
1211
line_id = line_obj.create(cr, uid, {'name': move.name,
1213
'invoice_id': invoice_id,
1215
'product_id': move.product_id.id,
1216
'account_id': account_id,
1217
'price_unit': price_unit,
1218
'discount': discount,
1219
'quantity': move.product_qty or move.product_uos_qty,
1220
'invoice_line_tax_id': [(6, 0, tax_ids)],
1221
'analytic_distribution_id': distrib_id,
1224
self.pool.get('shipment').write(cr, uid, [shipment.id], {'invoice_id': invoice_id}, context=context)
1225
if move.sale_line_id:
1226
sale_obj.write(cr, uid, [move.sale_line_id.order_id.id], {'invoice_ids': [(4, invoice_id)], })
1227
sale_line_obj.write(cr, uid, [move.sale_line_id.id], {'invoiced': True,
1228
'invoice_lines': [(4, line_id)], })
766
1232
def validate(self, cr, uid, ids, context=None):
768
1234
validate the shipment
770
1236
change the state to Done for the corresponding packing
771
1237
- validate the workflow for all the packings
773
1239
pick_obj = self.pool.get('stock.picking')
774
1240
wf_service = netsvc.LocalService("workflow")
776
1242
for shipment in self.browse(cr, uid, ids, context=context):
777
1243
# validate should only be called on shipped shipments
778
1244
assert shipment.state in ('shipped',), 'shipment state is not shipped'
779
1245
# corresponding packing objects - only the distribution -> customer ones
780
packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id),], context=context)
1246
# we have to discard picking object with state done, because when we return from shipment
1247
# all object of a given picking object, he is set to Done and still belong to the same shipment_id
1248
# another possibility would be to unlink the picking object from the shipment, set shipment_id to False
1249
# but in this case the returned pack families would not be displayed anymore in the shipment
1250
packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ('state', '!=', 'done'), ], context=context)
782
1252
for packing in pick_obj.browse(cr, uid, packing_ids, context=context):
783
1253
assert packing.subtype == 'packing' and packing.state == 'assigned'
784
1254
# trigger standard workflow
785
1255
pick_obj.action_move(cr, uid, [packing.id])
786
1256
wf_service.trg_validate(uid, 'stock.picking', packing.id, 'button_done', cr)
1257
pick_obj._hook_create_sync_messages(cr, uid, packing.id, context) # UF-1617: Create the sync message for batch and asset before shipping
1259
# UF-1617: set the flag to this packing object to indicate that the SHIP has been done, for synchronisation purpose
1260
cr.execute('update stock_picking set already_shipped=\'t\' where id=%s' % packing.id)
1263
# Create automatically the invoice
1264
self.shipment_create_invoice(cr, uid, shipment.id, context=context)
788
1266
# log validate action
789
self.log(cr, uid, shipment.id, _('The Shipment %s has been validated.'%shipment.name))
791
result = self.complete_finished(cr, uid, ids, context=context)
1267
self.log(cr, uid, shipment.id, _('The Shipment %s has been closed.') % (shipment.name,))
1269
self.complete_finished(cr, uid, ids, context=context)
1272
def set_delivered(self, cr, uid, ids, context=None):
1274
set the delivered flag
1277
pick_obj = self.pool.get('stock.picking')
1278
for shipment in self.browse(cr, uid, ids, context=context):
1279
# validate should only be called on shipped shipments
1280
assert shipment.state in ['done'], 'shipment state is not shipped'
1281
# gather the corresponding packing and trigger the corresponding function
1282
packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ('state', '=', 'done')], context=context)
1283
# set delivered all packings
1284
pick_obj.set_delivered(cr, uid, packing_ids, context=context)
797
class pack_family_memory(osv.osv_memory):
799
dynamic memory object for pack families
801
_name = 'pack.family.memory'
803
def _vals_get(self, cr, uid, ids, fields, arg, context=None):
805
get functional values
808
for pf_memory in self.browse(cr, uid, ids, context=context):
809
values = {'move_lines': [],
811
'location_id': False,
812
'location_dest_id': False,
815
'currency_id': False,
819
result[pf_memory.id] = values
820
# pack family related fields
821
if pf_memory.to_pack == 0:
824
num_of_packs = pf_memory.to_pack - pf_memory.from_pack + 1
825
values['num_of_packs'] = num_of_packs
826
values['total_weight'] = pf_memory.weight * num_of_packs
828
# moves related fields
829
for move in pf_memory.draft_packing_id.move_lines:
830
if move.from_pack == pf_memory.from_pack:
831
if move.to_pack == pf_memory.to_pack:
832
# this move is in the good packing object and corresponds to this pack family
833
# we add it to the stock move list
834
values['move_lines'].append(move.id)
835
values['state'] = move.state
836
values['location_id'] = move.location_id.id
837
values['location_dest_id'] = move.location_dest_id.id
838
values['total_amount'] += move.total_amount
839
values['amount'] += move.amount
840
values['currency_id'] = move.currency_id and move.currency_id.id or False
842
raise osv.except_osv(_('Error !'), _('Integrity check failed! Pack Family and Stock Moves from/to do not match.'))
846
_columns = {'name': fields.char(string='Reference', size=1024),
847
'shipment_id': fields.many2one('shipment', string='Shipment'),
848
'draft_packing_id': fields.many2one('stock.picking', string="Draft Packing Ref"),
849
'sale_order_id': fields.many2one('sale.order', string="Sale Order Ref"),
850
'ppl_id': fields.many2one('stock.picking', string="PPL Ref"),
851
'from_pack': fields.integer(string='From p.'),
852
'to_pack': fields.integer(string='To p.'),
853
'pack_type': fields.many2one('pack.type', string='Pack Type'),
854
'length' : fields.float(digits=(16,2), string='Length [cm]'),
855
'width' : fields.float(digits=(16,2), string='Width [cm]'),
856
'height' : fields.float(digits=(16,2), string='Height [cm]'),
857
'weight' : fields.float(digits=(16,2), string='Weight p.p [kg]'),
859
'move_lines': fields.function(_vals_get, method=True, type='one2many', relation='stock.move', string='Stock Moves', multi='get_vals',),
860
'state': fields.function(_vals_get, method=True, type='selection', selection=[('draft', 'Draft'),
861
('assigned', 'Available'),
862
('stock_return', 'Returned to Stock'),
863
('ship_return', 'Returned from Shipment'),
864
('cancel', 'Canceled'),
865
('done', 'Done'),], string='State', multi='get_vals',),
866
'location_id': fields.function(_vals_get, method=True, type='many2one', relation='stock.location', string='Src Loc.', multi='get_vals',),
867
'location_dest_id': fields.function(_vals_get, method=True, type='many2one', relation='stock.location', string='Dest. Loc.', multi='get_vals',),
868
'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
869
'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', multi='get_vals',),
870
'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
871
'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals',),
872
'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
1291
class shipment_additionalitems(osv.osv):
1292
_name = "shipment.additionalitems"
1293
_description = "Additional Items"
1295
_columns = {'name': fields.char(string='Additional Item', size=1024, required=True),
1296
'shipment_id': fields.many2one('shipment', string='Shipment', readonly=True, on_delete='cascade'),
1297
'picking_id': fields.many2one('stock.picking', string='Picking', readonly=True, on_delete='cascade'),
1298
'quantity': fields.float(digits=(16, 2), string='Quantity', required=True),
1299
'uom': fields.many2one('product.uom', string='UOM', required=True),
1300
'comment': fields.char(string='Comment', size=1024),
1301
'volume': fields.float(digits=(16, 2), string='Volume[dm³]'),
1302
'weight': fields.float(digits=(16, 2), string='Weight[kg]', required=True),
875
_defaults = {'shipment_id': False,
876
'draft_packing_id': False,
882
class shipment(osv.osv):
1305
shipment_additionalitems()
1308
class shipment2(osv.osv):
884
1310
add pack_family_ids
886
1312
_inherit = 'shipment'
888
def _vals_get_2(self, cr, uid, ids, fields, arg, context=None):
890
get functional values
892
picking_obj = self.pool.get('stock.picking')
895
for shipment in self.browse(cr, uid, ids, context=context):
896
values = {'pack_family_memory_ids':[],
898
result[shipment.id] = values
899
# look for all corresponding packing
900
packing_ids = picking_obj.search(cr, uid, [('shipment_id', '=', shipment.id),], context=context)
901
# get the corresponding data
902
data = picking_obj.generate_data_from_picking_for_pack_family(cr, uid, packing_ids, context=context)
903
# create a memory family
904
created_ids = picking_obj.create_pack_families_memory_from_data(cr, uid, data, shipment.id, context=context)
905
values['pack_family_memory_ids'].extend(created_ids)
909
_columns = {'pack_family_memory_ids': fields.function(_vals_get_2, method=True, type='one2many', relation='pack.family.memory', string='Memory Families', multi='get_vals_2',),
1314
def on_change_partner(self, cr, uid, ids, partner_id, address_id, context=None):
1316
Change the delivery address when the partner change.
1322
v.update({'address_id': False})
1324
d.update({'address_id': [('partner_id', '=', partner_id)]})
1328
addr = self.pool.get('res.partner.address').browse(cr, uid, address_id, context=context)
1330
if not address_id or addr.partner_id.id != partner_id:
1331
addr = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
1332
if not addr.get('delivery'):
1333
addr = addr.get('default')
1335
addr = addr.get('delivery')
1337
v.update({'address_id': addr})
1344
'pack_family_memory_ids': fields.one2many('pack.family.memory', 'shipment_id', string='Memory Families'),
915
1350
class ppl_customize_label(osv.osv):
1209
1759
mod_obj = self.pool.get('ir.module.module')
1211
mod_id = mod_obj.search(cr, 1, [('name', '=', 'msf_outgoing'),])
1761
mod_id = mod_obj.search(cr, 1, [('name', '=', 'msf_outgoing'), ])
1213
1763
demo = mod_obj.read(cr, 1, mod_id, ['demo'])[0]['demo']
1216
1766
logging.getLogger('init').info('HOOK: module msf_outgoing: loading data/msf_outgoing_data.xml')
1217
1767
pathname = path.join('msf_outgoing', 'data/msf_outgoing_data.xml')
1218
file = tools.file_open(pathname)
1219
tools.convert_xml_import(cr, 'msf_outgoing', file, {}, mode='init', noupdate=False)
1221
_columns = {'flow_type': fields.selection([('full', 'Full'),('quick', 'Quick')], readonly=True, states={'draft': [('readonly', False),],}, string='Flow Type'),
1222
'subtype': fields.selection([('standard', 'Standard'), ('picking', 'Picking'),('ppl', 'PPL'),('packing', 'Packing')], string='Subtype'),
1768
file_to_open = tools.file_open(pathname)
1769
tools.convert_xml_import(cr, 'msf_outgoing', file_to_open, {}, mode='init', noupdate=False)
1771
def _qty_search(self, cr, uid, obj, name, args, context=None):
1772
""" Searches Ids of stock picking
1773
@return: Ids of locations
1777
stock_pickings = self.pool.get('stock.picking').search(cr, uid, [], context=context)
1780
for stock_picking in self.browse(cr, uid, stock_pickings, context=context):
1781
result[stock_picking.id] = 0.0
1782
for move in stock_picking.move_lines:
1783
result[stock_picking.id] += move.product_qty
1784
# construct the request
1785
# adapt the operator
1789
ids = [('id', 'in', [x for x in result.keys() if eval("%s %s %s" % (result[x], op, args[0][2]))])]
1792
def _get_picking_ids(self, cr, uid, ids, context=None):
1794
ids represents the ids of stock.move objects for which values have changed
1795
return the list of ids of picking object which need to get their state field updated
1797
self is stock.move object
1800
for obj in self.browse(cr, uid, ids, context=context):
1801
if obj.picking_id and obj.picking_id.id not in result:
1802
result.append(obj.picking_id.id)
1805
def _get_draft_moves(self, cr, uid, ids, field_name, args, context=None):
1807
Returns True if there is draft moves on Picking Ticket
1811
for pick in self.browse(cr, uid, ids, context=context):
1812
res[pick.id] = False
1813
for move in pick.move_lines:
1814
if move.state == 'draft':
1820
def _get_lines_state(self, cr, uid, ids, field_name, args, context=None):
1822
Returns the state according to line states and picking state
1823
If the Picking Ticket is not draft, don't compute the line state
1824
Else, for all moves with quantity, check the state of the move
1825
and set the line state with these values :
1826
'mixed': 'Partially Available'
1827
'assigned': 'Available'
1828
'confirmed': 'Not available'
1832
for pick in self.browse(cr, uid, ids, context=context):
1833
if pick.type != 'out' or pick.subtype != 'picking' or pick.state != 'draft':
1834
res[pick.id] = False
1837
res[pick.id] = 'confirmed'
1841
empty = len(pick.move_lines)
1842
for move in pick.move_lines:
1843
if move.product_qty == 0.00:
1848
if move.state != 'assigned':
1853
if confirmed and available:
1856
if available and confirmed:
1857
res[pick.id] = 'mixed'
1859
res[pick.id] = 'assigned'
1861
res[pick.id] = 'confirmed'
1862
elif processed and empty:
1863
res[pick.id] = 'processed'
1865
res[pick.id] = 'empty'
1867
res[pick.id] = False
1871
_columns = {'flow_type': fields.selection([('full', 'Full'), ('quick', 'Quick')], readonly=True, states={'draft': [('readonly', False), ], }, string='Flow Type'),
1872
'subtype': fields.selection([('standard', 'Standard'), ('picking', 'Picking'), ('ppl', 'PPL'), ('packing', 'Packing')], string='Subtype'),
1223
1873
'backorder_ids': fields.one2many('stock.picking', 'backorder_id', string='Backorder ids',),
1224
1874
'previous_step_id': fields.many2one('stock.picking', 'Previous step'),
1225
1875
'previous_step_ids': fields.one2many('stock.picking', 'previous_step_id', string='Previous Step ids',),
1226
1876
'shipment_id': fields.many2one('shipment', string='Shipment'),
1227
1877
'sequence_id': fields.many2one('ir.sequence', 'Picking Ticket Sequence', help="This field contains the information related to the numbering of the picking tickets.", ondelete='cascade'),
1228
1878
'first_shipment_packing_id': fields.many2one('stock.picking', 'Shipment First Step'),
1229
#'pack_family_ids': fields.one2many('pack.family', 'ppl_id', string='Pack Families',),
1879
# 'pack_family_ids': fields.one2many('pack.family', 'ppl_id', string='Pack Families',),
1230
1880
# attributes for specific packing labels
1231
1881
'ppl_customize_label': fields.many2one('ppl.customize.label', string='Labels Customization',),
1232
1882
# warehouse info (locations) are gathered from here - allow shipment process without sale order
1233
1883
'warehouse_id': fields.many2one('stock.warehouse', string='Warehouse', required=True,),
1235
'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X',), # old_multi get_vals
1236
'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
1237
'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
1238
'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
1239
'is_dangerous_good': fields.function(_vals_get, method=True, type='boolean', string='Dangerous Good', multi='get_vals',),
1240
'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals',),
1241
'is_narcotic': fields.function(_vals_get, method=True, type='boolean', string='Narcotic', multi='get_vals',),
1242
#'is_completed': fields.function(_vals_get, method=True, type='boolean', string='Completed Process', multi='get_vals',),
1243
'pack_family_memory_ids': fields.function(_vals_get_2, method=True, type='one2many', relation='pack.family.memory', string='Memory Families', multi='get_vals_2',),
1244
1884
# flag for converted picking
1245
1885
'converted_to_standard': fields.boolean(string='Converted to Standard'),
1887
'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X'), # old_multi get_vals
1888
'total_volume': fields.function(_vals_get, method=True, type='float', string=u'Total Volume[dm³]', multi='get_vals'),
1889
'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals'),
1890
'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', digits_compute=dp.get_precision('Picking Price'), multi='get_vals'),
1891
'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals'),
1892
'is_dangerous_good': fields.function(_vals_get, method=True, type='boolean', string='Dangerous Good', multi='get_vals'),
1893
'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals'),
1894
'is_narcotic': fields.function(_vals_get, method=True, type='boolean', string='Narcotic', multi='get_vals'),
1895
'overall_qty': fields.function(_get_overall_qty, method=True, fnct_search=_qty_search, type='float', string='Overall Qty',
1896
store={'stock.move': (_get_picking_ids, ['product_qty', 'picking_id'], 10), }),
1897
'line_state': fields.function(_get_lines_state, method=True, type='selection',
1898
selection=[('confirmed', 'Not available'),
1899
('assigned', 'Available'),
1901
('processed', 'Processed'),
1902
('mixed', 'Partially available')], string='Lines state',
1903
store={'stock.move': (_get_picking_ids, ['picking_id', 'state', 'product_qty'], 10),
1904
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 10)}),
1905
# 'is_completed': fields.function(_vals_get, method=True, type='boolean', string='Completed Process', multi='get_vals',),
1906
'pack_family_memory_ids': fields.one2many('pack.family.memory', 'draft_packing_id', string='Memory Families'),
1907
'description_ppl': fields.char('Description', size=256),
1908
'already_shipped': fields.boolean(string='The shipment is done'), # UF-1617: only for indicating the PPL that the relevant Ship has been closed
1909
'has_draft_moves': fields.function(_get_draft_moves, method=True, type='boolean', string='Has draft moves ?', store=False),
1910
'has_to_be_resourced': fields.boolean(string='Picking has to be resourced'),
1247
1913
_defaults = {'flow_type': 'full',
1248
'ppl_customize_label': lambda obj, cr, uid, c: len(obj.pool.get('ppl.customize.label').search(cr, uid, [('name', '=', 'Default Label'),], context=c)) and obj.pool.get('ppl.customize.label').search(cr, uid, [('name', '=', 'Default Label'),], context=c)[0] or False,
1914
'ppl_customize_label': lambda obj, cr, uid, c: len(obj.pool.get('ppl.customize.label').search(cr, uid, [('name', '=', 'Default Label'), ], context=c)) and obj.pool.get('ppl.customize.label').search(cr, uid, [('name', '=', 'Default Label'), ], context=c)[0] or False,
1249
1915
'subtype': 'standard',
1250
1916
'first_shipment_packing_id': False,
1251
1917
'warehouse_id': lambda obj, cr, uid, c: len(obj.pool.get('stock.warehouse').search(cr, uid, [], context=c)) and obj.pool.get('stock.warehouse').search(cr, uid, [], context=c)[0] or False,
1252
1918
'converted_to_standard': False,
1919
'already_shipped': False,
1920
'line_state': 'empty',
1254
#_order = 'origin desc, name asc'
1255
1923
_order = 'name desc'
1925
def onchange_move(self, cr, uid, ids, context=None):
1927
Display or not the 'Confirm' button on Picking Ticket
1929
res = super(stock_picking, self).onchange_move(cr, uid, ids, context=context)
1932
has_draft_moves = self._get_draft_moves(cr, uid, ids, 'has_draft_moves', False)[ids[0]]
1933
res.setdefault('value', {}).update({'has_draft_moves': has_draft_moves})
1257
1937
def picking_ticket_data(self, cr, uid, ids, context=None):
1259
1939
generate picking ticket data for report creation
1261
1941
- sale order line without product: does not work presently
1263
1943
- many sale order line with same product: stored in different dictionary with line id as key.
1264
1944
so the same product could be displayed many times in the picking ticket according to sale order
1266
1946
- many stock move with same product: two cases, if from different sale order lines, the above rule applies,
1267
1947
if from the same order line, they will be stored according to prodlot id
1269
1949
- many stock move with same prodlot (so same product): if same sale order line, the moves will be
1270
1950
stored in the same structure, with global quantity, i.e. this batch for this product for this
1271
1951
sale order line will be displayed only once with summed quantity from concerned stock moves
1273
1953
[sale_line.id][product_id][prodlot_id]
1275
other prod lot, not used are added in order that all prod lot are displayed
1955
other prod lot, not used are added in order that all prod lot are displayed
1277
1957
to check, if a move does not come from the sale order line:
1278
1958
stored with line id False, product is relevant, multiple
1279
1959
product for the same 0 line id is possible
1670
2433
# if the picking is converted to standard, and state is confirmed
1671
2434
if pick.converted_to_standard and pick.state == 'confirmed':
1672
2435
return 'The Preparation Picking has been converted to simple Out. ' + message
2436
if pick.type == 'out' and pick.subtype == 'picking':
2437
kwargs['message'] = message.replace('Delivery Order', 'Picking Ticket')
2438
elif pick.type == 'out' and pick.subtype == 'packing':
2439
kwargs['message'] = message.replace('Delivery Order', 'Packing List')
2440
elif pick.type == 'out' and pick.subtype == 'ppl':
2441
kwargs['message'] = message.replace('Delivery Order', 'Pre-Packing List')
1673
2442
return super(stock_picking, self)._hook_log_picking_modify_message(cr, uid, ids, context, *args, **kwargs)
2444
def _get_keep_move(self, cr, uid, ids, context=None):
2446
Returns for each stock move of the draft PT, if we should keep it
2449
for pick in self.browse(cr, uid, ids, context=context):
2450
for bo in pick.backorder_ids:
2451
if not bo.is_completed()[bo.id]:
2452
pick_moves.setdefault(pick.id, {})
2453
for m in bo.move_lines:
2454
if m.state not in ('done', 'cancel'):
2455
pick_moves[pick.id].setdefault(m.backmove_id.id, True)
1675
2459
def convert_to_standard(self, cr, uid, ids, context=None):
1677
2461
check of back orders exists, if not, convert to standard: change subtype to standard, and trigger workflow
1679
2463
only one picking object at a time
2468
move_obj = self.pool.get('stock.move')
2469
date_tools = self.pool.get('date.tools')
2470
fields_tools = self.pool.get('fields.tools')
2471
db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
2473
pick_to_check = set()
1681
2475
for obj in self.browse(cr, uid, ids, context=context):
1682
if obj.backorder_ids:
1683
raise osv.except_osv(_('Warning !'), _('You cannot convert a picking which has already been started.'))
2476
# the convert function should only be called on draft picking ticket
2477
assert obj.subtype == 'picking' and obj.state in ('draft', 'assigned'), 'the convert function should only be called on draft picking ticket objects'
2478
# if self.has_picking_ticket_in_progress(cr, uid, [obj.id], context=context)[obj.id]:
2479
# raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try again.'))
1685
2481
# log a message concerning the conversion
1686
2482
new_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
1687
self.log(cr, uid, obj.id, _('The Preparation Picking (%s) has been converted to simple Out (%s).'%(obj.name, new_name)))
2483
self.log(cr, uid, obj.id, _('The Preparation Picking (%s) has been converted to simple Out (%s).') % (obj.name, new_name))
2485
keep_move = self._get_keep_move(cr, uid, [obj.id], context=context).get(obj.id, {})
1688
2487
# change subtype and name
1689
obj.write({'name': new_name,
1690
'subtype': 'standard',
1691
'converted_to_standard': True,
2488
default_vals = {'name': new_name,
2490
'subtype': 'standard',
2491
'converted_to_standard': True,
2492
'backorder_id': False,
2497
if obj.state == 'draft' and keep_move:
2498
context['wkf_copy'] = True
2499
new_pick_id = self.copy(cr, uid, obj.id, default_vals, context=context)
2500
pick_to_check.add(obj.id)
2502
self.write(cr, uid, obj.id, default_vals, context=context)
2504
if obj.backorder_id:
2505
pick_to_check.add(obj.backorder_id.id)
1693
2507
# all destination location of the stock moves must be output location of warehouse - lot_output_id
2508
# if corresponding sale order, date and date_expected are updated to rts + shipment lt
1694
2509
for move in obj.move_lines:
1695
move.write({'location_dest_id': obj.warehouse_id.lot_output_id.id,}, context=context)
1697
self.draft_force_assign(cr, uid, [obj.id])
2510
# was previously set to confirmed/assigned, otherwise, when we confirm the stock picking,
2511
# using draft_force_assign, the moves are not treated because not in draft
2512
# and the corresponding chain location on location_dest_id was not computed
2513
# we therefore set them back in draft state before treatment
2514
if move.product_qty == 0.0:
2515
vals = {'state': 'done'}
2517
# Save the state of this stock move to set it before action_assign()
2518
moves_states[move.id] = move.state
2519
if move.state not in ('cancel', 'done'):
2520
vals = {'state': 'draft'}
2522
vals = {'state': move.state}
2523
vals.update({'backmove_id': False})
2524
# If the move comes from a DPO, don't change the destination location
2526
vals.update({'location_dest_id': obj.warehouse_id.lot_output_id.id})
2530
shipment_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
2531
rts = datetime.strptime(obj.sale_id.ready_to_ship_date, db_date_format)
2532
rts = rts + relativedelta(days=shipment_lt or 0)
2533
rts = rts.strftime(db_date_format)
2534
vals.update({'date': rts, 'date_expected': rts, 'state': 'draft'})
2537
move.write(vals, context=context)
2538
keep_backmove = move_obj.search(cr, uid, [('backmove_id', '=', move.backmove_id.id)], context=context)
2539
if move.backmove_id and move.backmove_id.product_qty == 0.00:
2540
keep_backmove = move_obj.search(cr, uid, [('backmove_id', '=', move.backmove_id.id), ('state', 'not in', ('done', 'cancel'))], context=context)
2541
if not keep_backmove:
2542
move_obj.write(cr, uid, [move.backmove_id.id], {'state': 'done'}, context=context)
2543
move_obj.update_linked_documents(cr, uid, move.backmove_id.id, move.id, context=context)
2544
if move.product_qty == 0.00:
2545
move.write({'state': 'draft'})
2547
# move.action_done(context=context)
2548
elif move.product_qty != 0.00:
2549
vals.update({'picking_id': new_pick_id,
2550
'line_number': move.line_number,
2551
'state': moves_states[move.id],
2552
'product_qty': move.product_qty, })
2554
new_move_id = move_obj.copy(cr, uid, move.id, vals, context=context)
2556
# Compute the chained location as an initial confirmation of move
2557
if move.state == 'assigned':
2558
new_move = move_obj.browse(cr, uid, new_move_id, context=context)
2559
tmp_ac = context.get('action_confirm', False)
2560
context['action_confirm'] = True
2561
move_obj.create_chained_picking(cr, uid, [new_move], context=context)
2562
context['action_confirm'] = tmp_ac
2564
# Update all linked objects to avoid close of related documents
2565
if move.id not in keep_move or not keep_move[move.id]:
2566
move_obj.update_linked_documents(cr, uid, move.id, new_move_id, context=context)
2568
# Set the stock move to done with 0.00 qty
2569
if move.id in keep_move and keep_move[move.id]:
2573
moves_states[move.id] = 'done'
2574
move_obj.write(cr, uid, [move.id], {'product_qty': 0.00,
2575
'state': m_st}, context=context)
2577
new_lines.append(new_move_id)
2580
for ptc_id in pick_to_check:
2581
ptc = self.browse(cr, uid, ptc_id, context=context)
2582
if all(m.product_qty == 0.00 and m.state in ('done', 'cancel') for m in ptc.move_lines):
2583
ptc.action_done(context=context)
2585
# trigger workflow (confirm picking)
2586
self.draft_force_assign(cr, uid, [new_pick_id or obj.id])
2588
for s in moves_states:
2589
move_obj.write(cr, uid, [s], {'state': moves_states[s]}, context=context)
2591
# check availability
2592
self.action_assign(cr, uid, [new_pick_id or obj.id], context=context)
2594
if 'assigned' in moves_states.values():
2595
# Add an empty write to display the 'Process' button on OUT
2596
self.write(cr, uid, [new_pick_id or obj.id], {'state': 'assigned'}, context=context)
1699
2598
# TODO which behavior
1700
2599
data_obj = self.pool.get('ir.model.data')
1701
2600
view_id = data_obj.get_object_reference(cr, uid, 'stock', 'view_picking_out_form')
1702
2601
view_id = view_id and view_id[1] or False
1703
# display newly created picking ticket
2602
context.update({'picking_type': 'delivery_order', 'view_id': view_id})
1704
2603
return {'name':_("Delivery Orders"),
1705
2604
'view_mode': 'form,tree',
1706
2605
'view_id': [view_id],
1707
2606
'view_type': 'form',
1708
2607
'res_model': 'stock.picking',
2608
'res_id': new_pick_id or obj.id,
1710
2609
'type': 'ir.actions.act_window',
1711
2610
'target': 'crush',
2614
def convert_to_pick(self, cr, uid, ids, context=None):
2616
Change simple OUTs to draft Picking Tickets
2618
context = context or {}
2621
move_obj = self.pool.get('stock.move')
2622
data_obj = self.pool.get('ir.model.data')
2626
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
2627
view_id = view_id and view_id[1] or False
2629
for out in self.browse(cr, uid, ids, context=context):
2630
if out.state in ('cancel', 'done'):
2631
raise osv.except_osv(_('Error'), _('You cannot convert %s delivery orders') % (out.state == 'cancel' and _('Canceled') or _('Done')))
2633
# log a message concerning the conversion
2634
new_name = self.pool.get('ir.sequence').get(cr, uid, 'picking.ticket')
2636
# change subtype and name
2637
default_vals = {'name': new_name,
2638
'subtype': 'picking',
2639
'converted_to_standard': False,
2641
'sequence_id': self.create_sequence(cr, uid, {'name':new_name,
2644
'padding':2}, context=context)
2647
self.write(cr, uid, [out.id], default_vals, context=context)
2648
wf_service = netsvc.LocalService("workflow")
2649
wf_service.trg_validate(uid, 'stock.picking', out.id, 'convert_to_picking_ticket', cr)
2650
# we force availability
2652
self.log(cr, uid, out.id, _('The Delivery order (%s) has been converted to draft Picking Ticket (%s).') % (out.name, new_name), context={'view_id': view_id, 'picking_type': 'picking'})
2654
for move in out.move_lines:
2655
move_to_update.append(move.id)
2657
pack_loc_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'stock_location_packing')[1]
2658
move_obj.write(cr, uid, move_to_update, {'location_dest_id': pack_loc_id}, context=context)
2660
context.update({'picking_type': 'picking'})
2661
return {'name': _('Picking Tickets'),
2662
'view_mode': 'form,tree',
2663
'view_id': [view_id],
2664
'view_type': 'form',
2665
'res_model': 'stock.picking',
2667
'type': 'ir.actions.act_window',
2671
def do_partial_out(self, cr, uid, wizard_ids, context=None):
2673
Process the stock picking from selected stock moves
2675
BE CAREFUL: the wizard_ids parameters is the IDs of the outgoing.delivery.processor objects,
2676
not those of stock.picking objects
2678
return self.do_partial(cr, uid, wizard_ids, 'outgoing.delivery.processor', context)
2680
def do_partial(self, cr, uid, wizard_ids, proc_model=False, context=None):
2682
Process the stock picking from selected stock moves
2684
BE CAREFUL: the wizard_ids parameters is the IDs of the internal.picking.processor objects,
2685
not those of stock.picking objects
2688
proc_obj = self.pool.get('internal.picking.processor')
2689
picking_obj = self.pool.get('stock.picking')
2690
sequence_obj = self.pool.get('ir.sequence')
2691
uom_obj = self.pool.get('product.uom')
2692
move_obj = self.pool.get('stock.move')
2693
wf_service = netsvc.LocalService("workflow")
2698
proc_obj = self.pool.get(proc_model)
2703
if isinstance(wizard_ids, (int, long)):
2704
wizard_ids = [wizard_ids]
2706
for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
2707
picking = wizard.picking_id
2708
new_picking_id = False
2709
processed_moves = []
2712
for line in wizard.move_ids:
2716
if move.id not in move_data:
2717
move_data.setdefault(move.id, {
2718
'original_qty': move.product_qty,
2719
'processed_qty': 0.00,
2723
if line.quantity <= 0.00:
2726
orig_qty = move.product_qty
2727
if move.original_qty_partial and move.original_qty_partial != -1:
2728
orig_qty = move.original_qty_partial
2730
if line.uom_id.id != move.product_uom.id:
2731
quantity = uom_obj._compute_qty(cr, uid, line.uom_id.id, line.quantity, move.product_uom.id)
2733
quantity = line.quantity
2735
move_data[move.id]['processed_qty'] += quantity
2738
'line_number': move.line_number,
2739
'product_qty': line.quantity,
2740
'product_uos_qty': line.quantity,
2741
'state': 'assigned',
2742
'move_dest_id': False,
2743
'price_unit': move.price_unit,
2744
'processed_stock_move': True,
2745
'prodlot_id': line.prodlot_id and line.prodlot_id.id or False,
2746
'asset_id': line.asset_id and line.asset_id.id or False,
2747
'composition_list_id': line.composition_list_id and line.composition_list_id.id or False,
2748
'original_qty_partial': orig_qty,
2751
if quantity < move.product_qty and move_data[move.id]['original_qty'] > move_data[move.id]['processed_qty']:
2753
new_move_id = move_obj.copy(cr, uid, move.id, values, context=context)
2754
processed_moves.append(new_move_id)
2755
# Update the original move
2757
'product_qty': move.product_qty - quantity,
2758
'product_uos_qty': move.product_qty - quantity,
2760
move_obj.write(cr, uid, [move.id], wr_vals, context=context)
2762
# Update the original move
2763
move_obj.write(cr, uid, [move.id], values, context=context)
2764
processed_moves.append(move.id)
2766
need_new_picking = False
2767
for move_vals in move_data.values():
2768
if move_vals['original_qty'] != move_vals['processed_qty']:
2769
need_new_picking = True
2772
if need_new_picking:
2774
'name': sequence_obj.get(cr, uid, 'stock.picking.%s' % (picking.type)),
2778
new_picking_id = picking_obj.copy(cr, uid, picking.id, cp_vals, context=context)
2779
move_obj.write(cr, uid, processed_moves, {'picking_id': new_picking_id}, context=context)
2781
# At first we confirm the new picking (if necessary)
2783
self.write(cr, uid, [picking.id], {'backorder_id': new_picking_id}, context=context)
2784
# Claim specific code
2785
self._claim_registration(cr, uid, wizard, new_picking_id, context=context)
2786
# We confirm the new picking after its name was possibly modified by custom code - so the link message (top message) is correct
2787
wf_service.trg_validate(uid, 'stock.picking', new_picking_id, 'button_confirm', cr)
2788
# Then we finish the good picking
2789
if not wizard.register_a_claim or (wizard.register_a_claim and wizard.claim_type != 'return'):
2790
self.action_move(cr, uid, [new_picking_id])
2791
wf_service.trg_validate(uid, 'stock.picking', new_picking_id, 'button_done', cr)
2792
# UF-1617: Hook a method to create the sync messages for some extra objects: batch number, asset once the OUT/partial is done
2793
self._hook_create_sync_messages(cr, uid, new_picking_id, context)
2795
wf_service.trg_write(uid, 'stock.picking', picking.id, cr)
2796
delivered_pack_id = new_picking_id
2798
# Claim specific code
2799
self._claim_registration(cr, uid, wizard, picking.id, context=context)
2800
if not wizard.register_a_claim or (wizard.register_a_claim and wizard.claim_type != 'return'):
2801
self.action_move(cr, uid, [picking.id])
2802
wf_service.trg_validate(uid, 'stock.picking', picking.id, 'button_done', cr)
2803
# UF-1617: Hook a method to create the sync messages for some extra objects: batch number, asset once the OUT/partial is done
2804
self._hook_create_sync_messages(cr, uid, [picking.id], context)
2806
delivered_pack_id = picking.id
2808
# UF-1617: set the delivered_pack_id (new or original) to become already_shipped
2809
self.write(cr, uid, [delivered_pack_id], {'already_shipped': True})
2811
delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
2812
res[picking.id] = {'delivered_picking': delivered_pack.id or False}
1714
2816
def create_picking(self, cr, uid, ids, context=None):
1716
open the wizard to create (partial) picking tickets
1718
# we need the context for the wizard switch
2818
Open the wizard to create (partial) picking tickets
2821
proc_obj = self.pool.get('create.picking.processor')
2823
if isinstance(ids, (int, long)):
2826
processor_id = proc_obj.create(cr, uid, {'picking_id': ids[0]}, context=context)
2827
proc_obj.create_lines(cr, uid, processor_id, context=context)
2830
'type': 'ir.actions.act_window',
2831
'res_model': proc_obj._name,
2832
'res_id': processor_id,
2833
'view_type': 'form',
2834
'view_mode': 'form',
2838
def do_create_picking(self, cr, uid, wizard_ids, context=None):
2840
Create the picking ticket from selected stock moves
2842
BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
2843
not those of stock.picking objects
2846
move_obj = self.pool.get('stock.move')
2847
uom_obj = self.pool.get('product.uom')
2848
cp_proc_obj = self.pool.get('create.picking.processor')
1719
2850
if context is None:
1723
name = _("Create Picking Ticket")
1724
model = 'create.picking'
1726
wiz_obj = self.pool.get('wizard')
1727
# open the selected wizard
1728
return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
1730
def do_create_picking(self, cr, uid, ids, context=None):
1732
create the picking ticket from selected stock moves
1734
assert context, 'context is not defined'
1735
assert 'partial_datas' in context, 'partial datas not present in context'
1736
partial_datas = context['partial_datas']
1739
move_obj = self.pool.get('stock.move')
1741
for pick in self.browse(cr, uid, ids, context=context):
1742
# create the new picking object
1743
# a sequence for each draft picking ticket is used for the picking ticket
1744
sequence = pick.sequence_id
1745
ticket_number = sequence.get_id(test='id', context=context)
1746
new_pick_id = self.copy(cr, uid, pick.id, {'name': pick.name + '-' + ticket_number,
1747
'backorder_id': pick.id,
1748
'move_lines': []}, context=dict(context, allow_copy=True,))
1749
# create stock moves corresponding to partial datas
2853
if isinstance(wizard_ids, (int, long)):
2854
wizard_ids = [wizard_ids]
2857
raise osv.except_osv(
2858
_('Processing Error'),
2859
_('No data to process !')
2862
for wizard in cp_proc_obj.browse(cr, uid, wizard_ids, context=context):
2863
picking = wizard.picking_id
2867
# Create the new picking object
2868
# A sequence for each draft picking ticket is used for the picking ticket
2869
sequence = picking.sequence_id
2870
ticket_number = sequence.get_id(code_or_id='id', context=context)
2873
'name': '%s-%s' % (picking.name or 'NoName/000', ticket_number),
2874
'backorder_id': picking.id,
2878
tmp_allow_copy = context.get('allow_copy')
2884
new_picking_id = self.copy(cr, uid, picking.id, copy_data, context=context)
2885
context['allow_copy'] = tmp_allow_copy
2887
# Create stock moves corresponding to processing lines
1750
2888
# for now, each new line from the wizard corresponds to a new stock.move
1751
2889
# it could be interesting to regroup according to production lot/asset id
1752
move_ids = partial_datas[pick.id].keys()
1753
for move in move_obj.browse(cr, uid, move_ids, context=context):
1757
initial_qty = move.product_qty
1758
for partial in partial_datas[pick.id][move.id]:
1760
partial['product_id'] == move.product_id.id
1761
partial['product_uom'] == move.product_uom.id
1763
count = count + partial['product_qty']
1764
# copy the stock move and set the quantity
1765
new_move = move_obj.copy(cr, uid, move.id, {'picking_id': new_pick_id,
1766
'product_qty': partial['product_qty'],
1767
'prodlot_id': partial['prodlot_id'],
1768
'asset_id': partial['asset_id'],
1769
'backmove_id': move.id}, context=context)
1770
# decrement the initial move, cannot be less than zero
1771
initial_qty = max(initial_qty - count, 0)
1772
move_obj.write(cr, uid, [move.id], {'product_qty': initial_qty}, context=context)
1774
# confirm the new picking ticket
2890
for line in wizard.move_ids:
2891
move_data.setdefault(line.move_id.id, {
2892
'initial_qty': line.move_id.product_qty,
2893
'processed_qty': 0.00,
2896
if line.uom_id.id != line.move_id.product_uom.id:
2897
processed_qty = uom_obj._compute_qty(cr, uid, line.uom_id.id, line.quantity, line.move_id.product_uom.id)
2899
processed_qty = line.quantity
2901
move_data[line.move_id.id]['processed_qty'] += processed_qty
2903
# Copy the stock move and set the quantity
2905
'picking_id': new_picking_id,
2906
'product_qty': line.quantity,
2907
'product_uom': line.uom_id.id,
2908
'product_uos_qty': line.quantity,
2909
'product_uos': line.uom_id.id,
2910
'prodlot_id': line.prodlot_id and line.prodlot_id.id,
2911
'asset_id': line.asset_id and line.asset_id.id,
2912
'composition_list_id': line.composition_list_id and line.composition_list_id.id,
2914
'backmove_id': line.move_id.id,
2916
context['keepLineNumber'] = True
2917
move_obj.copy(cr, uid, line.move_id.id, cp_values, context=context)
2918
context['keepLineNumber'] = False
2921
# Update initial stock moves
2922
for move_id, move_vals in move_data.iteritems():
2923
initial_qty = max(move_vals['initial_qty'] - move_vals['processed_qty'], 0.00)
2925
'product_qty': initial_qty,
2926
'proudct_uos_qty': initial_qty,
2927
'processed_stock_move': True,
2929
context['keepLineNumber'] = True
2930
move_obj.write(cr, uid, [move_id], wr_vals, context=context)
2931
context['keepLineNumber'] = False
2934
# Confirm the new picking ticket
1775
2935
wf_service = netsvc.LocalService("workflow")
1776
wf_service.trg_validate(uid, 'stock.picking', new_pick_id, 'button_confirm', cr)
1777
# we force availability
1778
self.force_assign(cr, uid, [new_pick_id])
1780
# TODO which behavior
1781
#return {'type': 'ir.actions.act_window_close'}
1782
data_obj = self.pool.get('ir.model.data')
1783
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
1784
view_id = view_id and view_id[1] or False
1785
# display newly created picking ticket
2936
wf_service.trg_validate(uid, 'stock.picking', new_picking_id, 'button_confirm', cr)
2937
# We force availability
2938
self.force_assign(cr, uid, [new_picking_id])
2940
# Just to avoid an error on kit test because view_picking_ticket_form is not still loaded when test is ran
2941
msf_outgoing = self.pool.get('ir.module.module').search(cr, uid, [('name', '=', 'msf_outgoing'), ('state', '=', 'installed')], context=context)
2942
if not msf_outgoing:
2945
data_obj = self.pool.get('ir.model.data')
2946
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
2947
view_id = view_id and view_id[1] or False
2949
context.update({'picking_type': 'picking_ticket', 'picking_screen': True})
1786
2951
return {'name':_("Picking Ticket"),
1787
2952
'view_mode': 'form,tree',
1788
2953
'view_id': [view_id],
1789
2954
'view_type': 'form',
1790
2955
'res_model': 'stock.picking',
2956
'res_id': new_picking_id,
1792
2957
'type': 'ir.actions.act_window',
1793
2958
'target': 'crush',
1796
2962
def validate_picking(self, cr, uid, ids, context=None):
1798
validate the picking ticket
1800
# we need the context for the wizard switch
2964
Open the wizard to validate the picking ticket
2967
proc_obj = self.pool.get('validate.picking.processor')
2969
if isinstance(ids, (int, long)):
2972
processor_id = proc_obj.create(cr, uid, {'picking_id': ids[0]}, context=context)
2973
proc_obj.create_lines(cr, uid, processor_id, context=context)
2976
'type': 'ir.actions.act_window',
2977
'res_model': proc_obj._name,
2978
'res_id': processor_id,
2979
'view_type': 'form',
2980
'view_mode': 'form',
2984
def do_validate_picking(self, cr, uid, wizard_ids, context=None):
2986
Validate the picking ticket from selected stock moves
2988
Move here the logic of validate picking
2990
BE CAREFUL: the wizard_ids parameters is the IDs of the validate.picking.processor objects,
2991
not those of stock.picking objects
2994
date_tools = self.pool.get('date.tools')
2995
move_obj = self.pool.get('stock.move')
2996
uom_obj = self.pool.get('product.uom')
2997
proc_obj = self.pool.get('validate.picking.processor')
1801
2999
if context is None:
1805
name = _("Validate Picking Ticket")
1806
model = 'create.picking'
1808
wiz_obj = self.pool.get('wizard')
1809
# open the selected wizard
1810
return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
1812
def do_validate_picking(self, cr, uid, ids, context=None):
1814
validate the picking ticket from selected stock moves
1816
move here the logic of validate picking
1817
available for picking loop
1819
assert context, 'context is not defined'
1820
assert 'partial_datas' in context, 'partial datas not present in context'
1821
partial_datas = context['partial_datas']
1824
move_obj = self.pool.get('stock.move')
1825
# create picking object
1826
create_picking_obj = self.pool.get('create.picking')
1828
for pick in self.browse(cr, uid, ids, context=context):
1829
# create stock moves corresponding to partial datas
1830
move_ids = partial_datas[pick.id].keys()
1831
for move in move_obj.browse(cr, uid, move_ids, context=context):
1834
# flag to update the first move - if split was performed during the validation, new stock moves are created
1837
initial_qty = move.product_qty
1838
for partial in partial_datas[pick.id][move.id]:
1840
partial['product_id'] == move.product_id.id
1841
partial['product_uom'] == move.product_uom.id
1843
count = count + partial['product_qty']
1846
# update existing move
1847
move_obj.write(cr, uid, [move.id], {'product_qty': partial['product_qty'],
1848
'prodlot_id': partial['prodlot_id'],
1849
'asset_id': partial['asset_id']}, context=context)
1851
# split happend during the validation
1852
# copy the stock move and set the quantity
1853
new_move = move_obj.copy(cr, uid, move.id, {'state': 'assigned',
1854
'product_qty': partial['product_qty'],
1855
'prodlot_id': partial['prodlot_id'],
1856
'asset_id': partial['asset_id']}, context=context)
1857
# decrement the initial move, cannot be less than zero
1858
diff_qty = initial_qty - count
1859
# the quantity after the validation does not correspond to the picking ticket quantity
1860
# the difference is written back to draft picking ticket
1861
# is positive if some qty was removed during the validation -> draft qty is increased
1862
# is negative if some qty was added during the validation -> draft qty is decreased
1864
backorder_id = pick.backorder_id.id
1865
assert backorder_id, 'No backorder defined.'
1866
original_moves = move_obj.search(cr, uid, [('picking_id', '=', backorder_id),
1867
('product_id', '=', move.product_id.id),
1868
('product_uom', '=', move.product_uom.id)])
1869
# original move from the draft picking ticket which will be updated
1870
original_move = move.backmove_id
1871
assert len(original_moves) == 1, 'No corresponding stock_move have been found in draft picking ticket for product %s and UOM %s'%(move.product_id.name, move.product_uom.name)
1872
backorder_qty = move_obj.read(cr, uid, [original_move.id], ['product_qty'], context=context)[0]['product_qty']
1873
backorder_qty = max(backorder_qty + diff_qty, 0)
1874
move_obj.write(cr, uid, [original_move.id], {'product_qty': backorder_qty}, context=context)
1876
# create the new ppl object
1877
ppl_number = pick.name.split("/")[1]
1878
# we want the copy to keep the production lot reference from picking ticket to pre-packing list
1879
new_ppl_id = self.copy(cr, uid, pick.id, {'name': 'PPL/' + ppl_number,
1881
'previous_step_id': pick.id,
1882
'backorder_id': False}, context=dict(context, keep_prodlot=True, allow_copy=True,))
3002
if isinstance(wizard_ids, (int, long)):
3003
wizard_ids = [wizard_ids]
3006
raise osv.except_osv(
3007
_('Processing Error'),
3008
_('No data to process !'),
3011
db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
3012
today = time.strftime(db_date_format)
3014
for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
3015
picking = wizard.picking_id
3019
# Create the new ppl object
3020
ppl_number = picking.name.split("/")[1]
3021
# We want the copy to keep the batch number reference from picking ticket to pre-packing list
3023
'name': 'PPL/%s' % ppl_number,
3025
'previous_step_id': picking.id,
3026
'backorder_id': False,
3030
'keep_prodlot': True,
3032
'keepLineNumber': True,
3034
new_ppl_id = self.copy(cr, uid, picking.id, cp_vals, context=context)
1883
3035
new_ppl = self.browse(cr, uid, new_ppl_id, context=context)
1884
# update locations of stock moves - if the move quantity is equal to zero, the stock move is removed
1885
for move in new_ppl.move_lines:
1886
if move.product_qty:
1887
move_obj.write(cr, uid, [move.id], {'initial_location': move.location_id.id,
1888
'location_id': move.location_dest_id.id,
1889
'location_dest_id': new_ppl.warehouse_id.lot_dispatch_id.id}, context=context)
1891
move_obj.unlink(cr, uid, [move.id], context=context)
3037
'keep_prodlot': False,
3038
'allow_copy': False,
3039
'keepLineNumber': False,
3042
# For each processed lines, save the processed quantity to update the draft picking ticket
3043
# and create a new line on PPL
3044
for line in wizard.move_ids:
3047
orig_qty = line.move_id.product_qty
3048
if line.move_id.original_qty_partial and line.move_id.original_qty_partial != -1:
3049
orig_qty = line.move_id.original_qty_partial
3051
if line.move_id.id not in move_data:
3052
move_data.setdefault(line.move_id.id, {
3053
'initial_qty': line.move_id.product_qty,
3054
'processed_qty': 0.00,
3055
'move': line.move_id,
3059
if line.uom_id.id != line.move_id.product_uom.id:
3060
processed_qty = uom_obj._compute_qty(cr, uid, line.uom_id.id, line.quantity, line.move_id.product_uom.id)
3062
processed_qty = line.quantity
3064
move_data[line.move_id.id]['processed_qty'] += processed_qty
3067
'product_qty': line.quantity,
3068
'product_uos_qty': line.quantity,
3069
'product_uom': line.uom_id.id,
3070
'product_uos': line.uom_id.id,
3071
'prodlot_id': line.prodlot_id and line.prodlot_id.id,
3072
'line_number': line.line_number,
3073
'asset_id': line.asset_id and line.asset_id.id,
3074
'composition_list_id': line.composition_list_id and line.composition_list_id.id,
3075
'original_qty_partial': orig_qty,
3078
# Update or create the validate picking ticket line
3080
move_obj.write(cr, uid, [line.move_id.id], values, context=context)
3082
# Split happened during the validation
3083
# Copy the stock move and set the quantity
3084
values['state'] = 'assigned'
3086
'keepLineNumber': True,
3087
'non_stock_noupdate': True,
3089
move_obj.copy(cr, uid, line.move_id.id, values, context=context)
3091
'keepLineNumber': False,
3092
'non_stock_noupdate': False,
3096
'picking_id': new_ppl_id,
3097
'initial_location': line.move_id.location_id.id,
3098
'location_id': line.move_id.location_dest_id.id,
3099
'location_dest_id': new_ppl.warehouse_id.lot_dispatch_id.id,
3101
'date_expected': today,
3104
'keepLineNumber': True,
3105
'non_stock_noupdate': True,
3107
move_obj.copy(cr, uid, line.move_id.id, values, context=context)
3109
'keepLineNumber': False,
3110
'non_stock_noupdate': False,
3114
# For each move, check if there is remaining quantity
3115
for move_vals in move_data.itervalues():
3116
# The quantity after the validation does not correspond to the picking ticket quantity
3117
# The difference is written back to draft picking ticket
3118
# Is positive if some quantity was removed during the validation -> draft quantity is increased
3119
# Is negative if some quantity was added during the validation -> draft quantity is decreased
3120
diff_qty = move_vals['initial_qty'] - move_vals['processed_qty']
3121
if diff_qty != 0.00:
3122
# Original move from the draft picking ticket which will be updated
3123
original_move_id = move_vals['move'].backmove_id.id
3124
original_vals = move_obj.browse(cr, uid, original_move_id, context=context)
3125
if original_vals.product_uom.id != move_vals['move'].product_uom.id:
3126
diff_qty = uom_obj._compute_qty(cr, uid, move_vals['move'].product_uom.id, diff_qty, original_vals.product_uom.id)
3127
backorder_qty = max(original_vals.product_qty + diff_qty, 0)
3128
if backorder_qty != 0.00:
3129
move_obj.write(cr, uid, [original_move_id], {'product_qty': backorder_qty}, context=context)
1893
3131
wf_service = netsvc.LocalService("workflow")
1894
3132
wf_service.trg_validate(uid, 'stock.picking', new_ppl_id, 'button_confirm', cr)
1895
3133
# simulate check assign button, as stock move must be available
1896
3134
self.force_assign(cr, uid, [new_ppl_id])
1897
3135
# trigger standard workflow for validated picking ticket
1898
self.action_move(cr, uid, [pick.id])
1899
wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
3136
self.action_move(cr, uid, [picking.id])
3137
wf_service.trg_validate(uid, 'stock.picking', picking.id, 'button_done', cr)
1901
3139
# if the flow type is in quick mode, we perform the ppl steps automatically
1902
if pick.flow_type == 'quick':
1903
create_picking_obj.quick_mode(cr, uid, new_ppl, context=context)
1905
# TODO which behavior
1906
#return {'type': 'ir.actions.act_window_close'}
3140
if picking.flow_type == 'quick' and new_ppl:
3141
return self.quick_mode(cr, uid, new_ppl.id, context=context)
1907
3143
data_obj = self.pool.get('ir.model.data')
1908
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
3144
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')
1909
3145
view_id = view_id and view_id[1] or False
1910
# display newly created picking ticket
1911
return {'name':_("Picking Ticket"),
3146
context.update({'picking_type': 'picking_ticket', 'ppl_screen': True})
3148
return {'name':_("Pre-Packing List"),
1912
3149
'view_mode': 'form,tree',
1913
3150
'view_id': [view_id],
1914
3151
'view_type': 'form',
1915
3152
'res_model': 'stock.picking',
3153
'res_id': new_ppl and new_ppl.id or False,
1917
3154
'type': 'ir.actions.act_window',
1918
3155
'target': 'crush',
3159
def quick_mode(self, cr, uid, ids, context=None):
3161
Perform the PPL steps automatically
3165
proc_obj = self.pool.get('ppl.processor')
3170
if not isinstance(ids, (int, long)):
3174
raise osv.except_osv(
3175
_('Processing Error'),
3176
_('No data to process !'),
3179
wizard_id = self.ppl(cr, uid, ids, context=context)['res_id']
3180
proc_obj.do_ppl_step1(cr, uid, wizard_id, context=context)
3181
return proc_obj.do_ppl_step2(cr, uid, wizard_id, context=context)
1921
3183
def ppl(self, cr, uid, ids, context=None):
1923
pack the ppl - open the ppl step1 wizard
1925
# we need the context for the wizard switch
3185
Open the wizard to process the step 1 of the PPL
3188
proc_obj = self.pool.get('ppl.processor')
3189
data_obj = self.pool.get('ir.model.data')
3191
if isinstance(ids, (int, long)):
3194
processor_id = proc_obj.create(cr, uid, {'picking_id': ids[0]}, context=context)
3195
proc_obj.create_lines(cr, uid, processor_id, context=context)
3197
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'ppl_processor_step1_form_view')[1]
3200
'name': _('PPL Information - step 1'),
3201
'type': 'ir.actions.act_window',
3202
'res_model': proc_obj._name,
3203
'res_id': processor_id,
3204
'view_id': [view_id],
3205
'view_type': 'form',
3206
'view_mode': 'form',
3210
def do_ppl_step1(self, cr, uid, wizard_ids, context=None):
3212
Open the wizard to process the step 2 of the PPL
3214
BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
3215
not those of stock.picking objects
3218
proc_obj = self.pool.get('ppl.processor')
3219
proc_line_obj = self.pool.get('ppl.move.processor')
3220
family_obj = self.pool.get('ppl.family.processor')
3222
if isinstance(wizard_ids, (int, long)):
3223
wizard_ids = [wizard_ids]
3226
raise osv.except_osv(
3227
_('Processing Error'),
3228
_('No data to process !'),
3231
# Create the different pack families according to values in stock moves
3232
for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
3235
for line in wizard.move_ids:
3236
key = 'f%st%s' % (line.from_pack, line.to_pack)
3237
families_data.setdefault(key, {
3238
'wizard_id': wizard.id,
3240
'from_pack': line.from_pack,
3241
'to_pack': line.to_pack,
3244
families_data[key]['move_ids'].append(line.id)
3246
for family_data in families_data.values():
3247
move_ids = family_data.get('move_ids', [])
3248
if 'move_ids' in family_data:
3249
del family_data['move_ids']
3251
fam_id = family_obj.create(cr, uid, family_data)
3254
proc_line_obj.write(cr, uid, move_ids, {'pack_id': fam_id}, context=context)
3256
return self.ppl_step2(cr, uid, wizard_ids, context)
3258
def ppl_step2(self, cr, uid, wizard_ids, context=None):
3260
Open the wizard of the second step of PPL processing
3262
BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
3263
not those of stock.picking objects
3266
proc_obj = self.pool.get('ppl.processor')
3267
data_obj = self.pool.get('ir.model.data')
3269
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'ppl_processor_step2_form_view')[1]
3272
'name': _('PPL Information - step 2'),
3273
'type': 'ir.actions.act_window',
3274
'res_model': proc_obj._name,
3275
'res_id': wizard_ids[0],
3276
'view_id': [view_id],
3277
'view_type': 'form',
3278
'view_mode': 'form',
3283
def do_ppl_step2(self, cr, uid, wizard_ids, context=None):
3285
Create the Pack families and the shipment
3287
BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
3288
not those of stock.picking objects
3291
proc_obj = self.pool.get('ppl.processor')
3292
move_obj = self.pool.get('stock.move')
3293
uom_obj = self.pool.get('product.uom')
3294
data_obj = self.pool.get('ir.model.data')
3295
wf_service = netsvc.LocalService("workflow")
1926
3297
if context is None:
1930
name = _("PPL Information - step1")
1931
model = 'create.picking'
1933
wiz_obj = self.pool.get('wizard')
1934
# open the selected wizard
1935
return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
1937
def do_ppl1(self, cr, uid, ids, context=None):
1939
- receives generated data from ppl in context
1940
- call action to ppl2 step with partial_datas_ppl1 in context
1941
- ids are the picking ids
1943
# we need the context for the wizard switch
1944
assert context, 'No context defined'
1947
name = _("PPL Information - step2")
1948
model = 'create.picking'
1950
wiz_obj = self.pool.get('wizard')
1951
# open the selected wizard
1952
return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
1954
def do_ppl2(self, cr, uid, ids, context=None):
1956
finalize the ppl logic
1959
assert context, 'context not defined'
1960
assert 'partial_datas_ppl1' in context, 'partial_datas_ppl1 no defined in context'
1962
move_obj = self.pool.get('stock.move')
1963
wf_service = netsvc.LocalService("workflow")
1965
partial_datas_ppl = context['partial_datas_ppl1']
1966
# picking ids from ids must be equal to picking ids from partial datas
1967
assert set(ids) == set(partial_datas_ppl.keys()), 'picking ids from ids and partial do not match'
1969
# update existing stock moves - create new one if split occurred
1971
for pick in self.browse(cr, uid, ids, context=context):
1972
# integrity check on move_ids - moves ids from picking and partial must be the same
1973
# dont take into account done moves, which represents returned products
1974
from_pick = [move.id for move in pick.move_lines if move.state in ('confirmed', 'assigned')]
1976
# the list of updated stock.moves
1977
# if a stock.move is updated, the next time a new move is created
1980
# browse returns a list of browse object in the same order as from_partial
1981
browse_moves = move_obj.browse(cr, uid, from_pick, context=context)
1982
moves = dict(zip(from_pick, browse_moves))
1984
for from_pack in partial_datas_ppl[pick.id]:
1985
for to_pack in partial_datas_ppl[pick.id][from_pack]:
1986
for move in partial_datas_ppl[pick.id][from_pack][to_pack]:
1988
from_partial.append(move)
1989
for partial in partial_datas_ppl[pick.id][from_pack][to_pack][move]:
1990
# {'asset_id': False, 'weight': False, 'product_id': 77, 'product_uom': 1, 'pack_type': False, 'length': False, 'to_pack': 1, 'height': False, 'from_pack': 1, 'prodlot_id': False, 'qty_per_pack': 18.0, 'product_qty': 18.0, 'width': False, 'move_id': 179}
1992
partial['product_id'] == moves[move].product_id.id
1993
partial['asset_id'] == moves[move].asset_id.id
1994
partial['product_uom'] == moves[move].product_uom.id
1995
partial['prodlot_id'] == moves[move].prodlot_id.id
1996
# dictionary of new values, used for creation or update
1997
# - qty_per_pack is a function at stock move level
1998
fields = ['product_qty', 'from_pack', 'to_pack', 'pack_type', 'length', 'width', 'height', 'weight']
1999
values = dict(zip(fields, [partial["%s"%x] for x in fields]))
2002
# if already updated, we create a new stock.move
2003
updated[move]['partial_qty'] += partial['product_qty']
2004
# force state to 'assigned'
2005
values.update(state='assigned')
2006
# copy stock.move with new product_qty, qty_per_pack. from_pack, to_pack, pack_type, length, width, height, weight
2007
move_obj.copy(cr, uid, move, values, context=context)
2009
# update the existing stock move
2010
updated[move] = {'initial': moves[move].product_qty, 'partial_qty': partial['product_qty']}
2011
move_obj.write(cr, uid, [move], values, context=context)
2013
# integrity check - all moves are treated and no more
2014
assert set(from_pick) == set(from_partial), 'move_ids are not equal pick:%s - partial:%s'%(set(from_pick), set(from_partial))
2015
# quantities are right
2016
assert all([updated[m]['initial'] == updated[m]['partial_qty'] for m in updated.keys()]), 'initial quantity is not equal to the sum of partial quantities (%s).'%(updated)
2017
# copy to 'packing' stock.picking
2018
# draft shipment is automatically created or updated if a shipment already
2019
pack_number = pick.name.split("/")[1]
2020
new_packing_id = self.copy(cr, uid, pick.id, {'name': 'PACK/' + pack_number,
2021
'subtype': 'packing',
2022
'previous_step_id': pick.id,
2023
'backorder_id': False,
2024
'shipment_id': False}, context=dict(context, keep_prodlot=True, allow_copy=True,))
2026
self.write(cr, uid, [new_packing_id], {'origin': pick.origin}, context=context)
2027
# update locations of stock moves and state as the picking stay at 'draft' state.
2028
# if return move have been done in previous ppl step, we remove the corresponding copied move (criteria: qty_per_pack == 0)
2029
new_packing = self.browse(cr, uid, new_packing_id, context=context)
2030
for move in new_packing.move_lines:
2031
if move.qty_per_pack == 0:
2032
move_obj.unlink(cr, uid, [move.id], context=context)
2034
move.write({'state': 'assigned',
2035
'location_id': new_packing.warehouse_id.lot_dispatch_id.id,
2036
'location_dest_id': new_packing.warehouse_id.lot_distribution_id.id}, context=context)
2038
# trigger standard workflow
2039
self.action_move(cr, uid, [pick.id])
2040
wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
2042
# TODO which behavior
2043
#return {'type': 'ir.actions.act_window_close'}
2044
data_obj = self.pool.get('ir.model.data')
2045
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')
3300
if isinstance(wizard_ids, (int, long)):
3301
wizard_ids = [wizard_ids]
3304
raise osv.except_osv(
3305
_('Processing Error'),
3306
_('No data to process '),
3309
for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
3310
picking = wizard.picking_id
3314
# Create the new packing
3315
# Copy to 'packing' stock.picking
3316
# Draft shipment is automatically created or updated if a shipment already exists
3317
pack_number = picking.name.split("/")[1]
3320
'name': 'PACK/' + pack_number,
3321
'subtype': 'packing',
3322
'previous_step_id': picking.id,
3323
'backorder_id': False,
3324
'shipment_id': False,
3325
'origin': picking.origin,
3329
# Change the context for copy
3331
'keep_prodlot': True,
3332
'keepLineNumber': True,
3336
# Create the packing with pack_values and the updated context
3337
new_packing_id = self.copy(cr, uid, picking.id, pack_values, context=context)
3339
# Reset context values
3341
'keep_prodlot': False,
3342
'keepLineNumber': False,
3343
'allow_copy': False,
3346
# Set default values for packing move creation
3348
'picking_id': new_packing_id,
3349
'state': 'assigned',
3350
'location_id': picking.warehouse_id.lot_dispatch_id.id,
3351
'location_dest_id': picking.warehouse_id.lot_distribution_id.id,
3354
# Create the stock moves in the packing
3355
for family in wizard.family_ids:
3356
for line in family.move_ids:
3357
move_to_copy = line.move_id.id
3359
if line.uom_id.id != line.move_id.product_uom.id:
3360
processed_qty = uom_obj._compute_qty(cr, uid, line.uom_id.id, line.quantity, line.move_id.product_uom.id)
3362
processed_qty = line.quantity
3365
'product_qty': line.quantity,
3366
'from_pack': family.from_pack,
3367
'to_pack': family.to_pack,
3368
'pack_type': family.pack_type and family.pack_type.id or False,
3369
'length': family.length,
3370
'width': family.width,
3371
'height': family.height,
3372
'weight': family.weight,
3375
if line.move_id.id not in moves_data:
3376
moves_data.setdefault(line.move_id.id, {
3377
'line_number': line.move_id.line_number,
3378
'initial_qty': line.move_id.product_qty,
3379
'processed_qty': processed_qty,
3381
move_obj.write(cr, uid, [line.move_id.id], values, context=context)
3384
# If already updated, we create a new stock move
3385
moves_data[line.move_id.id]['processed_qty'] += processed_qty
3386
# Force state to 'assigned'
3387
values['state'] = 'assigned'
3390
'keepLineNumber': True,
3391
'non_stock_noupdate': True,
3393
move_to_copy = move_obj.copy(cr, uid, line.move_id.id, values, context=context)
3395
'keepLineNumber': False,
3396
'non_stock_noupdate': False,
3399
# Create a move line in the Packing
3401
'keepLineNumber': True,
3402
'non_stock_noupdate': True,
3404
move_obj.copy(cr, uid, move_to_copy, pack_move_data, context=context)
3406
'keepLineNumber': False,
3407
'non_stock_noupdate': False,
3410
# Check quantities integrity status
3411
for m_data in moves_data.values():
3412
if m_data['initial_qty'] != m_data['processed_qty']:
3413
raise osv.except_osv(
3414
_('Processing Error'),
3415
_('Line %(line_number)s: The sum of processed quantities %(processed_qty)s '\
3416
'is not equal to the initial quantity of the stock move %(initial_qty)s.') % m_data
3419
# Trigger standard workflow on PPL
3420
self.action_move(cr, uid, [picking.id])
3421
wf_service.trg_validate(uid, 'stock.picking', picking.id, 'button_done', cr)
3423
shipment_id = new_packing_id and self.read(cr, uid, new_packing_id, ['shipment_id'])['shipment_id'][0] or False
3425
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_shipment_form')
2046
3426
view_id = view_id and view_id[1] or False
2047
# display newly created picking ticket
2048
return {'name':_("Pre-Packing List"),
3427
return {'name':_("Shipment"),
2049
3428
'view_mode': 'form,tree',
2050
3429
'view_id': [view_id],
2051
3430
'view_type': 'form',
2052
'res_model': 'stock.picking',
3431
'res_model': 'shipment',
3432
'res_id': shipment_id,
2054
3433
'type': 'ir.actions.act_window',
2055
3434
'target': 'crush',
2058
def return_products(self, cr, uid, ids, context=None):
2060
open the return products wizard
2062
# we need the context
3438
def ppl_return(self, cr, uid, ids, context=None):
3440
Open wizard to return products from a PPL
3443
proc_obj = self.pool.get('return.ppl.processor')
3445
if isinstance(ids, (int, long)):
3448
processor_id = proc_obj.create(cr, uid, {'picking_id': ids[0]}, context=context)
3449
proc_obj.create_lines(cr, uid, processor_id, context=context)
3452
'name': _('Return products'),
3453
'type': 'ir.actions.act_window',
3454
'res_model': proc_obj._name,
3455
'res_id': processor_id,
3456
'view_type': 'form',
3457
'view_mode': 'form',
3462
def do_return_ppl(self, cr, uid, wizard_ids, context=None):
3464
Returns products from PPL to the draft picking ticket
3466
BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
3467
not those of stock.picking objects
3470
proc_obj = self.pool.get('return.ppl.processor')
3471
move_obj = self.pool.get('stock.move')
3472
data_obj = self.pool.get('ir.model.data')
3473
wf_service = netsvc.LocalService("workflow")
2063
3475
if context is None:
2067
name = _("Return Products")
2068
model = 'create.picking'
2069
step = 'returnproducts'
2070
wiz_obj = self.pool.get('wizard')
2071
# open the selected wizard
2072
return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
2074
def do_return_products(self, cr, uid, ids, context=None):
2077
- update the draft picking ticket
2078
- create the back move
2081
assert context, 'context not defined'
2082
assert 'partial_datas' in context, 'partial_datas no defined in context'
2083
partial_datas = context['partial_datas']
2085
move_obj = self.pool.get('stock.move')
2086
wf_service = netsvc.LocalService("workflow")
2088
for picking in self.browse(cr, uid, ids, context=context):
2090
# corresponding draft picking ticket
3478
if isinstance(wizard_ids, (int, long)):
3479
wizard_ids = [wizard_ids]
3481
for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
3482
picking = wizard.picking_id
2091
3483
draft_picking_id = picking.previous_step_id.backorder_id.id
2093
for move in move_obj.browse(cr, uid, partial_datas[picking.id].keys(), context=context):
2094
# we browse the updated moves (return qty > 0 is checked during data generation)
2096
data = partial_datas[picking.id][move.id]
2099
return_qty = data['qty_to_return']
2100
# initial qty is decremented
2101
initial_qty = move.product_qty
2102
initial_qty = max(initial_qty - return_qty, 0)
2103
values = {'product_qty': initial_qty}
3485
for line in wizard.move_ids:
3486
return_qty = line.quantity
3487
initial_qty = max(line.move_id.product_qty - return_qty, 0)
3490
'product_qty': initial_qty,
2105
3493
if not initial_qty:
2106
# if all products are sent back to stock, the move state is cancel - done for now, ideologic question, wahouuu!
2107
#values.update({'state': 'cancel'})
2108
values.update({'state': 'done'})
2109
move_obj.write(cr, uid, [move.id], values, context=context)
2111
# create a back move with the quantity to return to the good location
2112
# the good location is stored in the 'initial_location' field
2113
move_obj.copy(cr, uid, move.id, {'product_qty': return_qty,
2114
'location_dest_id': move.initial_location.id,
2117
# increase the draft move with the move quantity
2118
draft_move_id = move.backmove_id.id
3495
If all products of the move are sent back to draft picking ticket,
3496
the move state is done
3498
move_values['state'] = 'done'
3500
move_obj.write(cr, uid, [line.move_id.id], move_values, context=context)
3503
Create a back move with the quantity to return to the good location.
3504
Good location is stored in the 'initial_location' field
3507
'product_qty': return_qty,
3508
'location_dest_id': line.move_id.initial_location.id,
3511
context['keepLineNumber'] = True
3512
move_obj.copy(cr, uid, line.move_id.id, return_values, context=context)
3513
context['keepLineNumber'] = False
3515
# Increase the draft move with the returned quantity
3516
draft_move_id = line.move_id.backmove_id.id
2119
3517
draft_initial_qty = move_obj.read(cr, uid, [draft_move_id], ['product_qty'], context=context)[0]['product_qty']
2120
3518
draft_initial_qty += return_qty
2121
3519
move_obj.write(cr, uid, [draft_move_id], {'product_qty': draft_initial_qty}, context=context)
2123
# log the increase action - display the picking ticket view form
2124
# TODO refactoring needed
2125
obj_data = self.pool.get('ir.model.data')
2126
res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')[1]
2127
self.log(cr, uid, picking.id, _("Products from Pre-Packing List (%s) have been returned to stock."%picking.name), context={'view_id': res,})
2128
res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
2129
self.log(cr, uid, draft_picking_id, _("The corresponding Draft Picking Ticket (%s) has been updated."%picking.previous_step_id.backorder_id.name), context={'view_id': res,})
2130
# if all moves are done or canceled, the ppl is canceled
2132
for move in picking.move_lines:
2133
if move.state in ('assigned'):
3523
# Log message for PPL
3524
ppl_view = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')[1]
3525
context['view_id'] = ppl_view
3526
log_message = _('Products from Pre-Packing List (%s) have been returned to stock.') % (picking.name,)
3527
self.log(cr, uid, picking.id, log_message, context=context)
3529
# Log message for draft picking ticket
3530
pick_view = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
3532
'view_id': pick_view,
3533
'picking_type': 'picking_ticket',
3535
log_message = _('The corresponding Draft Picking Ticket (%s) has been updated.') % (picking.previous_step_id.backorder_id.name,)
3536
self.log(cr, uid, draft_picking_id, log_message, context=context)
3538
# If all moves are done or canceled, the PPL is canceled
3539
cancel_ppl = move_obj.search(cr, uid, [('picking_id', '=', picking.id), ('state', '!=', 'assigned')], count=True, context=context)
2137
# we dont want the back move (done) to be canceled - so we dont use the original cancel workflow state because
2138
# action_cancel() from stock_picking would be called, this would cancel the done stock_moves
2139
# instead we move to the new return_cancel workflow state which simply set the stock_picking state to 'cancel'
2140
# TODO THIS DOESNT WORK - still done state - replace with trigger for now
2141
#wf_service.trg_validate(uid, 'stock.picking', picking.id, 'return_cancel', cr)
3543
we dont want the back move (done) to be canceled - so we dont use the original cancel workflow state because
3544
action_cancel() from stock_picking would be called, this would cancel the done stock_moves
3545
instead we move to the new return_cancel workflow state which simply set the stock_picking state to 'cancel'
3546
TODO THIS DOESNT WORK - still done state - replace with trigger for now
3547
wf_service.trg_validate(uid, 'stock.picking', picking.id, 'return_cancel', cr)
2142
3549
wf_service.trg_write(uid, 'stock.picking', picking.id, cr)
2144
# TODO which behavior
2145
#return {'type': 'ir.actions.act_window_close'}
2146
data_obj = self.pool.get('ir.model.data')
2147
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')
3551
view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
2148
3552
view_id = view_id and view_id[1] or False
2149
# display newly created picking ticket
3553
context['picking_type'] = 'picking_ticket'
3555
context.update({'picking_type': 'picking_ticket'})
2151
'name':_("Pre-Packing List"),
3557
'name':_("Picking Ticket"),
2152
3558
'view_mode': 'form,tree',
2153
3559
'view_id': [view_id],
2154
3560
'view_type': 'form',
2155
3561
'res_model': 'stock.picking',
2156
'res_id': picking.id,
3562
'res_id': draft_picking_id ,
2157
3563
'type': 'ir.actions.act_window',
2158
3564
'target': 'crush',
2161
3568
def action_cancel(self, cr, uid, ids, context=None):
2163
3570
override cancel state action from the workflow
2165
3572
- depending on the subtype and state of the stock.picking object
2166
3573
the behavior will be different
2168
3575
Cancel button is active for the picking object:
2169
3576
- subtype: 'picking'
2170
3577
Cancel button is active for the shipment object:
2171
3578
- subtype: 'packing'
2173
3580
state is not taken into account as picking is canceled before
2175
3582
if context is None:
2177
3584
move_obj = self.pool.get('stock.move')
2178
3585
obj_data = self.pool.get('ir.model.data')
2180
3587
# check the state of the picking
2181
3588
for picking in self.browse(cr, uid, ids, context=context):
2182
# if draft and all qty are still there, we can cancel it without further checks
3589
# if draft and shipment is in progress, we cannot cancel
2183
3590
if picking.subtype == 'picking' and picking.state in ('draft',):
2184
for move in picking.move_lines:
2185
if move.product_qty != move.sale_line_id.product_uom_qty:
2186
raise osv.except_osv(_('Warning !'), _('The shipment process has already started! Return products to stock from ppl and shipment and try to cancel again.'))
3591
if self.has_picking_ticket_in_progress(cr, uid, [picking.id], context=context)[picking.id]:
3592
raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try to cancel again.'))
2187
3593
return super(stock_picking, self).action_cancel(cr, uid, ids, context=context)
2188
3594
# if not draft or qty does not match, the shipping is already in progress
2189
3595
if picking.subtype == 'picking' and picking.state in ('done',):
2190
3596
raise osv.except_osv(_('Warning !'), _('The shipment process is completed and cannot be canceled!'))
2192
3598
# first call to super method, so if some checks fail won't perform other actions anyway
2193
3599
# call super - picking is canceled
2194
3600
super(stock_picking, self).action_cancel(cr, uid, ids, context=context)
2196
3602
for picking in self.browse(cr, uid, ids, context=context):
2198
3604
if picking.subtype == 'picking':
2199
3605
# for each picking
2200
3606
# get the draft picking
2201
3607
draft_picking_id = picking.backorder_id.id
2203
3609
# for each move from picking ticket - could be split moves
2204
3610
for move in picking.move_lines:
2205
3611
# find the corresponding move in draft
2206
3612
draft_move = move.backmove_id
2207
# increase the draft move with the move quantity
2208
initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
2209
initial_qty += move.product_qty
2210
move_obj.write(cr, uid, [draft_move.id], {'product_qty': initial_qty}, context=context)
2211
# log the increase action
2212
# TODO refactoring needed
2213
obj_data = self.pool.get('ir.model.data')
2214
res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
2215
self.log(cr, uid, draft_picking_id, _("The corresponding Draft Picking Ticket (%s) has been updated."%picking.backorder_id.name), context={'view_id': res,})
3614
# increase the draft move with the move quantity
3615
initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
3616
initial_qty += move.product_qty
3617
move_obj.write(cr, uid, [draft_move.id], {'product_qty': initial_qty}, context=context)
3618
# log the increase action
3619
# TODO refactoring needed
3620
obj_data = self.pool.get('ir.model.data')
3621
res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
3622
context.update({'view_id': res, 'picking_type': 'picking_ticket'})
3623
self.log(cr, uid, draft_picking_id, _("The corresponding Draft Picking Ticket (%s) has been updated.") % (picking.backorder_id.name,), context=context)
2217
3625
if picking.subtype == 'packing':
2218
# for each packing we get the draft packing
2219
draft_packing_id = picking.backorder_id.id
2221
3627
# for each move from the packing
2222
3628
for move in picking.move_lines:
2223
3629
# corresponding draft move from draft **packing** object
2443
3896
'virtual_available': fields.function(_product_available, method=True, type='float', string='Virtual Stock', help="Future stock for this product according to the selected locations or all internal if none have been selected. Computed as: Real Stock - Outgoing + Incoming.", multi='qty_available', digits_compute=dp.get_precision('Product UoM')),
2444
3897
'qty_per_pack': fields.function(_vals_get, method=True, type='float', string='Qty p.p', multi='get_vals',),
2445
'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
2446
'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', multi='get_vals',),
2447
'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X',), # old_multi get_vals
3898
'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', digits_compute=dp.get_precision('Picking Price'), multi='get_vals',),
3899
'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', digits_compute=dp.get_precision('Picking Price'), multi='get_vals',),
3900
'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X',), # old_multi get_vals
2448
3901
'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
2449
3902
'is_dangerous_good': fields.function(_vals_get, method=True, type='boolean', string='Dangerous Good', multi='get_vals',),
2450
3903
'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals',),
2451
3904
'is_narcotic': fields.function(_vals_get, method=True, type='boolean', string='Narcotic', multi='get_vals',),
2452
'sale_order_line_number': fields.function(_vals_get, method=True, type='integer', string='Sale Order Line Number', multi='get_vals_X',), # old_multi get_vals
3905
'sale_order_line_number': fields.function(_vals_get, method=True, type='integer', string='Sale Order Line Number', multi='get_vals_X',), # old_multi get_vals
3906
# Fields used for domain
3907
'location_virtual_id': fields.many2one('stock.location', string='Virtual location'),
3908
'location_output_id': fields.many2one('stock.location', string='Output location'),
3909
'invoice_line_id': fields.many2one('account.invoice.line', string='Invoice line'),
3910
'pt_created': fields.boolean(string='PT created'),
3913
def copy(self, cr, uid, copy_id, values=None, context=None):
3920
if not 'pt_created' in values:
3921
values['pt_created'] = False
3923
return super(stock_move, self).copy(cr, uid, copy_id, values, context=context)
3925
def action_cancel(self, cr, uid, ids, context=None):
3927
Confirm or check the procurement order associated to the stock move
3929
pol_obj = self.pool.get('purchase.order.line')
3930
sol_obj = self.pool.get('sale.order.line')
3931
uom_obj = self.pool.get('product.uom')
3933
for move in self.browse(cr, uid, ids, context=context):
3935
A stock move can be re-sourced but there are some conditions
3937
Re-sourcing checking :
3938
1) The move should be attached to a picking
3939
2) The move should have the flag 'has_to_be_resourced' set
3940
3) The move shouldn't be already canceled
3941
4) The move should be linked to a purchase order line or a field
3944
if not move.picking_id:
3947
if not move.has_to_be_resourced and not move.picking_id.has_to_be_resourced:
3950
if move.state == 'cancel':
3953
pick_type = move.picking_id.type
3954
pick_subtype = move.picking_id.subtype
3955
pick_state = move.picking_id.state
3956
subtype_ok = pick_type == 'out' and (pick_subtype == 'standard' or (pick_subtype == 'picking' and pick_state == 'draft'))
3958
if pick_type == 'in' and move.purchase_line_id:
3959
sol_ids = pol_obj.get_sol_ids_from_pol_ids(cr, uid, [move.purchase_line_id.id], context=context)
3960
for sol in sol_obj.browse(cr, uid, sol_ids, context=context):
3961
diff_qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, sol.product_uom.id)
3962
if move.has_to_be_resourced or move.picking_id.has_to_be_resourced:
3963
sol_obj.add_resource_line(cr, uid, sol.id, False, diff_qty, context=context)
3964
sol_obj.update_or_cancel_line(cr, uid, sol.id, diff_qty, context=context)
3965
elif move.sale_line_id and (pick_type == 'internal' or (pick_type == 'out' and subtype_ok)):
3966
diff_qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.sale_line_id.product_uom.id)
3967
if move.has_to_be_resourced or move.picking_id.has_to_be_resourced:
3968
sol_obj.add_resource_line(cr, uid, move.sale_line_id.id, False, diff_qty, context=context)
3969
sol_obj.update_or_cancel_line(cr, uid, move.sale_line_id.id, diff_qty, context=context)
3971
res = super(stock_move, self).action_cancel(cr, uid, ids, context=context)
3973
wf_service = netsvc.LocalService("workflow")
3975
proc_obj = self.pool.get('procurement.order')
3976
proc_ids = proc_obj.search(cr, uid, [('move_id', 'in', ids)], context=context)
3977
for proc in proc_obj.browse(cr, uid, proc_ids, context=context):
3978
if proc.state == 'draft':
3979
wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_confirm', cr)
3981
# wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_check', cr)
3985
def update_linked_documents(self, cr, uid, ids, new_id, context=None):
3987
Update the linked documents of a stock move to another one
3989
context = context or {}
3990
ids = isinstance(ids, (int, long)) and [ids] or ids
3993
proc_ids = self.pool.get('procurement.order').search(cr, uid, [('move_id', '=', move_id)], context=context)
3995
self.pool.get('procurement.order').write(cr, uid, proc_ids, {'move_id': new_id}, context=context)
3997
pol_ids = self.pool.get('purchase.order.line').search(cr, uid, [('move_dest_id', '=', move_id)], context=context)
3999
self.pool.get('purchase.order.line').write(cr, uid, pol_ids, {'move_dest_id': new_id}, context=context)
4001
move_dest_ids = self.search(cr, uid, [('move_dest_id', '=', move_id)], context=context)
4003
self.write(cr, uid, move_dest_ids, {'move_dest_id': new_id}, context=context)
4005
backmove_ids = self.search(cr, uid, [('backmove_id', '=', move_id)], context=context)
4007
self.write(cr, uid, backmove_ids, {'backmove_id': new_id}, context=context)
4009
pack_backmove_ids = self.search(cr, uid, [('backmove_packing_id', '=', move_id)], context=context)
4010
if pack_backmove_ids:
4011
self.write(cr, uid, pack_backmove_ids, {'backmove_packing_id': new_id}, context=context)
2458
class sale_order(osv.osv):
2460
re-override to modify behavior for outgoing workflow
2462
_inherit = 'sale.order'
2463
_name = 'sale.order'
2465
def _hook_ship_create_stock_move(self, cr, uid, ids, context=None, *args, **kwargs):
2467
Please copy this to your module's method also.
2468
This hook belongs to the action_ship_create method from sale>sale.py
2470
- allow to modify the data for stock move creation
2472
move_data = super(sale_order, self)._hook_ship_create_stock_move(cr, uid, ids, context=context, *args, **kwargs)
2473
order = kwargs['order']
2474
# first go to packing location
2475
packing_id = order.shop_id.warehouse_id.lot_packing_id.id
2476
move_data['location_dest_id'] = packing_id
2477
move_data['state'] = 'confirmed'
2480
def _hook_ship_create_stock_picking(self, cr, uid, ids, context=None, *args, **kwargs):
2482
Please copy this to your module's method also.
2483
This hook belongs to the action_ship_create method from sale>sale.py
2485
- allow to modify the data for stock picking creation
2487
picking_data = super(sale_order, self)._hook_ship_create_stock_picking(cr, uid, ids, context=context, *args, **kwargs)
2488
order = kwargs['order']
2489
# use the name according to picking ticket sequence
2490
pick_name = self.pool.get('ir.sequence').get(cr, uid, 'picking.ticket')
2491
picking_data['name'] = pick_name
2492
picking_data['state'] = 'draft'
2493
picking_data['subtype'] = 'picking'
2494
picking_data['flow_type'] = 'full'
2495
picking_data['backorder_id'] = False
2496
picking_data['warehouse_id'] = order.shop_id.warehouse_id.id
2500
def _hook_ship_create_execute_picking_workflow(self, cr, uid, ids, context=None, *args, **kwargs):
2502
Please copy this to your module's method also.
2503
This hook belongs to the action_ship_create method from sale>sale.py
2505
- allow to avoid the stock picking workflow execution
2506
- trigger the logging message for the created picking, as it stays in draft state and no call to action_confirm is performed
2507
for the moment within the msf_outgoing logic
2509
cond = super(sale_order, self)._hook_ship_create_execute_picking_workflow(cr, uid, ids, context=context, *args, **kwargs)
2510
cond = cond and False
2512
# diplay creation message for draft picking ticket
2513
picking_id = kwargs['picking_id']
2514
picking_obj = self.pool.get('stock.picking')
2516
picking_obj.log_picking(cr, uid, [picking_id], context=context)
4019
class pack_family_memory(osv.osv):
4021
dynamic memory object for pack families
4023
_name = 'pack.family.memory'
4026
cr.execute('''create or replace view pack_family_memory as (
4029
p.shipment_id as shipment_id,
4031
array_agg(m.id) as move_lines,
4032
min(from_pack) as from_pack,
4033
case when to_pack=0 then 0 else to_pack-min(from_pack)+1 end as num_of_packs,
4034
p.sale_id as sale_order_id,
4035
case when p.subtype = 'ppl' then p.id else p.previous_step_id end as ppl_id,
4036
min(m.length) as length,
4037
min(m.width) as width,
4038
min(m.height) as height,
4039
min(m.weight) as weight,
4040
min(m.state) as state,
4041
min(m.location_id) as location_id,
4042
min(m.location_dest_id) as location_dest_id,
4043
min(m.pack_type) as pack_type,
4044
p.id as draft_packing_id,
4045
p.description_ppl as description_ppl,
4046
'_name'::varchar(5) as name,
4047
min(pl.currency_id) as currency_id,
4048
sum(sol.price_unit * m.product_qty) as total_amount
4049
from stock_picking p
4050
inner join stock_move m on m.picking_id = p.id and m.state != 'cancel' and m.product_qty > 0
4051
left join sale_order so on so.id = p.sale_id
4052
left join sale_order_line sol on sol.id = m.sale_line_id
4053
left join product_pricelist pl on pl.id = so.pricelist_id
4054
where p.shipment_id is not null
4055
group by p.shipment_id, p.description_ppl, to_pack, sale_id, p.subtype, p.id, p.previous_step_id
4059
def _vals_get(self, cr, uid, ids, fields, arg, context=None):
4061
get functional values
4064
compute_moves = not fields or 'move_lines' in fields
4065
for pf_memory in self.browse(cr, uid, ids, context=context):
4068
'total_weight': 0.0,
4069
'total_volume': 0.0,
4072
values['move_lines'] = []
4073
num_of_packs = pf_memory.num_of_packs
4075
values['amount'] = pf_memory.total_amount / num_of_packs
4076
values['total_weight'] = pf_memory.weight * num_of_packs
4077
values['total_volume'] = (pf_memory.length * pf_memory.width * pf_memory.height * num_of_packs) / 1000.0
4079
result[pf_memory.id] = values
4081
if compute_moves and ids:
4082
if isinstance(ids, (int, long)):
4085
cr.execute('select id, move_lines from ' + self._table + ' where id in %s', (tuple(ids),))
4086
for q_result in cr.fetchall():
4087
result[q_result[0]]['move_lines'] = q_result[1] or []
4091
'name': fields.char(string='Reference', size=1024),
4092
'shipment_id': fields.many2one('shipment', string='Shipment'),
4093
'draft_packing_id': fields.many2one('stock.picking', string="Draft Packing Ref"),
4094
'sale_order_id': fields.many2one('sale.order', string="Sale Order Ref"),
4095
'ppl_id': fields.many2one('stock.picking', string="PPL Ref"),
4096
'from_pack': fields.integer(string='From p.'),
4097
'to_pack': fields.integer(string='To p.'),
4098
'pack_type': fields.many2one('pack.type', string='Pack Type'),
4099
'length' : fields.float(digits=(16, 2), string='Length [cm]'),
4100
'width' : fields.float(digits=(16, 2), string='Width [cm]'),
4101
'height' : fields.float(digits=(16, 2), string='Height [cm]'),
4102
'weight' : fields.float(digits=(16, 2), string='Weight p.p [kg]'),
4104
'move_lines': fields.function(_vals_get, method=True, type='one2many', relation='stock.move', string='Stock Moves', multi='get_vals',),
4105
'state': fields.selection(selection=[
4107
('assigned', 'Available'),
4108
('stock_return', 'Returned to Stock'),
4109
('ship_return', 'Returned from Shipment'),
4110
('cancel', 'Cancelled'),
4111
('done', 'Closed'), ], string='State'),
4112
'location_id': fields.many2one('stock.location', string='Src Loc.'),
4113
'location_dest_id': fields.many2one('stock.location', string='Dest. Loc.'),
4114
'total_amount': fields.float('Total Amount'),
4115
'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', multi='get_vals',),
4116
'currency_id': fields.many2one('res.currency', string='Currency'),
4117
'num_of_packs': fields.integer('#Packs'),
4118
'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
4119
'total_volume': fields.function(_vals_get, method=True, type='float', string=u'Total Volume[dm³]', multi='get_vals',),
4120
'description_ppl': fields.char('Description', size=256),
4124
'shipment_id': False,
4125
'draft_packing_id': False,
4128
pack_family_memory()
4131
class procurement_order(osv.osv):
4133
procurement order workflow
4135
_inherit = 'procurement.order'
4137
def _hook_check_mts_on_message(self, cr, uid, context=None, *args, **kwargs):
4139
Please copy this to your module's method also.
4140
This hook belongs to the _check_make_to_stock_product method from procurement>procurement.py>procurement.order
4142
- allow to modify the message written back to procurement order
4144
message = super(procurement_order, self)._hook_check_mts_on_message(cr, uid, context=context, *args, **kwargs)
4145
procurement = kwargs['procurement']
4146
if procurement.move_id.picking_id.state == 'draft' and procurement.move_id.picking_id.subtype == 'picking':
4147
message = _("Shipment Process in Progress.")