254
271
{shipment_id: {draft_packing_id: {from_pack: {to_pack: {[partial,]}]}}}}
274
{'selected_weight': {'function': '_vals_get', 'digits': (16, 2), 'fnct_inv': False, 'string': 'Selected Weight [kg]', 'fnct_inv_arg': False, 'readonly': 1, 'fnct_search': False, 'func_obj': False, 'type': 'float', 'store': False, 'func_method': True},
275
'weight': {'digits': (16, 2), 'selectable': True, 'type': 'float', 'string': 'Weight p.p [kg]'},
276
'pack_type': {'domain': [], 'string': 'Pack Type', 'relation': 'pack.type', 'context': {}, 'selectable': True, 'type': 'many2one'},
277
'ppl_id': {'domain': [], 'string': 'PPL Ref', 'relation': 'stock.picking', 'context': {}, 'selectable': True, 'type': 'many2one'},
278
'draft_packing_id': {'domain': [], 'string': 'Draft Packing Ref', 'relation': 'stock.picking', 'context': {}, 'selectable': True, 'type': 'many2one'},
279
'wizard_id': {'domain': [], 'string': 'Wizard', 'relation': 'stock.partial.move', 'context': {}, 'selectable': True, 'type': 'many2one'},
280
'height': {'digits': (16, 2), 'selectable': True, 'type': 'float', 'string': 'Height [cm]'},
281
'from_pack': {'selectable': True, 'type': 'integer', 'string': 'From p.'},
282
'length': {'digits': (16, 2), 'selectable': True, 'type': 'float', 'string': 'Length [cm]'},
283
'to_pack': {'selectable': True, 'type': 'integer', 'string': 'To p.'},
284
'integrity_status': {'selectable': True, 'readonly': True, 'selection': [('empty', ''), ('ok', u'Ok'), ('negative', u'Negative Value'), ('missing_lot', u'Production Lot is Missing'), ('missing_date', u'Expiry Date is Missing'), ('no_lot_needed', u'No Production Lot/Expiry Date Needed'), ('wrong_lot_type', u'Wrong Production Lot Type'), ('wrong_lot_type_need_internal', u'Need Expiry Date (Internal) not Production Lot (Standard)'), ('wrong_lot_type_need_standard', u'Need Production Lot (Standard) not Expiry Date (Internal)'), ('empty_picking', u'Empty Picking Ticket'), ('missing_1', u'The first sequence must start with 1'), ('to_smaller_than_from', u'To value must be greater or equal to From value'), ('overlap', u'The sequence overlaps previous one'), ('gap', u'A gap exist in the sequence'), ('missing_weight', u'Weight is Missing')], 'type': 'selection', 'string': ' '},
285
'num_of_packs': {'function': '_vals_get', 'digits': (16, 2), 'fnct_inv': False, 'string': '#Packs', 'fnct_inv_arg': False, 'readonly': 1, 'fnct_search': False, 'func_obj': False, 'type': 'integer', 'store': False, 'func_method': True},
286
'selected_number': {'selectable': True, 'type': 'integer', 'string': 'Selected Number'},
287
'width': {'digits': (16, 2), 'selectable': True, 'type': 'float', 'string': 'Width [cm]'},
288
'sale_order_id': {'domain': [], 'string': 'Sale Order Ref', 'relation': 'sale.order', 'context': {}, 'selectable': True, 'type': 'many2one'}}
256
290
# integrity check
257
291
assert context, 'no context, method call is wrong'
303
346
return partial_datas_shipment
305
def integrity_check_create_shipment(self, cr, uid, ids, data, context=None):
307
integrity check on shipment data
348
def integrity_check_packs(self, cr, uid, ids, data, model_name, context=None):
350
integrity check on create shipment data
351
- #1 no negative values (<0)
352
- #2 at least one positive one (>0)
353
- #3 no more than available quantity #packs
355
{12: {176: {1: {1: [{'selected_weight': 0.0, 'weight': 0.0, 'pack_type': False, 'ppl_id': 175, 'draft_packing_id': 176, 'wizard_id': 1, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': '1', 'selected_number': 1, 'width': 0.0, 'sale_order_id': False}]}}}}
359
memory_move_obj = self.pool.get(model_name)
309
361
for shipment_data in data.values():
362
# total sum not including negative values
364
# flag to detect negative values
365
negative_value = False
366
# flag to detect excessive return quantity
310
368
for packing_data in shipment_data.values():
311
for from_pack_data in packing_data.values():
312
for to_pack_data in from_pack_data.values():
313
for partial in to_pack_data:
314
if partial.get('selected_number', False):
369
for from_data in packing_data.values():
370
for to_data in from_data.values():
371
for partial in to_data:
373
if partial['selected_number'] < 0.0:
374
# a negative value has been selected, update the memory line
375
# update the new value for integrity check with 'negative' value (selection field)
376
negative_value = True
377
memory_move_obj.write(cr, uid, [partial['memory_move_id']], {'integrity_status': 'negative'}, context=context)
378
elif partial['selected_number'] > int(partial['num_of_packs']):
379
# cannot return more products than available
381
memory_move_obj.write(cr, uid, [partial['memory_move_id']], {'integrity_status': 'return_qty_too_much',}, context=context)
383
sum_qty += partial['selected_number']
385
# if error, return False
386
if not sum_qty or negative_value or too_much:
390
def set_integrity_status(self, cr, uid, ids, field_name, status='empty', context=None):
392
for all moves set the status to ok (default value) or other if specified
394
for wiz in self.browse(cr, uid, ids, context=context):
395
for memory_move in getattr(wiz, field_name):
396
memory_move.write({'integrity_status': status,}, context=context)
398
def create_additionalitems(self, cr, uid, ids, context=None):
399
shipment_ids = context['active_ids']
400
additional_items_dict = {'additional_items_ids': []}
401
for shipment_wizard in self.read(cr, uid, ids, ['product_moves_shipment_additionalitems'], context):
402
additionalitems_ids = shipment_wizard['product_moves_shipment_additionalitems']
403
for additionalitem in self.pool.get('stock.move.memory.shipment.additionalitems').read(cr, uid, additionalitems_ids):
404
additionalitem.pop('wizard_id')
405
additionalitem['picking_id'] = additionalitem.get('picking_id', False) and additionalitem.get('picking_id', False)[0]
406
uom = additionalitem.get('uom', False)
407
if isinstance(uom, (int, long)):
409
additionalitem['uom'] = uom and uom[0]
410
additionalitem['shipment_id'] = shipment_ids[0]
411
additional_items_dict['additional_items_ids'].append((0, 0, additionalitem))
412
context.update(additional_items_dict)
319
415
def do_create_shipment(self, cr, uid, ids, context=None):
324
420
assert context, 'no context, method call is wrong'
325
421
assert 'active_ids' in context, 'No shipment ids in context. Action call is wrong'
423
context.update(self.create_additionalitems(cr, uid, ids, context))
425
# context.update(self.update_additionalitems(cr, uid, ids, context))
327
427
ship_obj = self.pool.get('shipment')
428
# name of the wizard field for moves (one2many)
429
field_name = 'product_moves_shipment_create'
329
431
shipment_ids = context['active_ids']
330
# generate data structure - selected_number must be non zero to be taken into accound
432
# generate data structure - selected_number must be non zero to be taken into account
331
433
partial_datas_shipment = self.generate_data_from_partial(cr, uid, ids, conditions=['selected_number'], context=context)
332
# integrity check on wizard data
333
if not self.integrity_check_create_shipment(cr, uid, ids, partial_datas_shipment, context=context):
334
raise osv.except_osv(_('Warning !'), _('You must at least select one pack to ship!'))
435
# reset the integrity status of all lines
436
self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
437
# integrity check on wizard data - sequence -> no prodlot check as the screen is readonly
438
packs_check = self.integrity_check_packs(cr, uid, ids, partial_datas_shipment, model_name='stock.move.memory.shipment.create', context=context)
440
# for not blocking yml test with the raise I use 'yml_test' in context
441
if not context.get('yml_test'):
442
# the windows must be updated to trigger tree colors
443
self.pool.get('wizard').open_wizard(cr, uid, shipment_ids, type='update', context=context)
444
raise osv.except_osv(_('Processing Error'), _("You have to enter the quantities you want to process before processing the move"))
445
# the windows must be updated to trigger tree colors
447
return self.pool.get('wizard').open_wizard(cr, uid, shipment_ids, type='update', context=context)
335
448
# call stock_picking method which returns action call
336
449
return ship_obj.do_create_shipment(cr, uid, shipment_ids, context=dict(context, partial_datas_shipment=partial_datas_shipment))
338
def integrity_check_return_packs(self, cr, uid, ids, data, context=None):
340
integrity check on shipment data
342
for shipment_data in data.values():
343
for packing_data in shipment_data.values():
344
for from_pack_data in packing_data.values():
345
for to_pack_data in from_pack_data.values():
346
for partial in to_pack_data:
347
if partial.get('selected_number', False):
352
451
def do_return_packs(self, cr, uid, ids, context=None):
354
453
gather data from wizard pass it to the do_return_packs method of shipment class
358
457
assert 'active_ids' in context, 'No shipment ids in context. Action call is wrong'
360
459
ship_obj = self.pool.get('shipment')
460
# name of the wizard field for moves (one2many)
461
field_name = 'product_moves_shipment_returnpacks'
362
463
shipment_ids = context['active_ids']
363
464
# generate data structure - selected_number must be non zero to be taken into account
364
465
partial_datas = self.generate_data_from_partial(cr, uid, ids, conditions=['selected_number'], context=context)
365
# integrity check on wizard data
366
if not self.integrity_check_return_packs(cr, uid, ids, partial_datas, context=context):
367
raise osv.except_osv(_('Warning !'), _('You must at least select one pack to return!'))
467
# reset the integrity status of all lines
468
self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
469
# integrity check on wizard data - sequence -> no prodlot check as the screen is readonly
470
packs_check = self.integrity_check_packs(cr, uid, ids, partial_datas, model_name='stock.move.memory.shipment.returnpacks', context=context)
472
# the windows must be updated to trigger tree colors
473
return self.pool.get('wizard').open_wizard(cr, uid, shipment_ids, type='update', context=context)
368
474
# call stock_picking method which returns action call
369
475
return ship_obj.do_return_packs(cr, uid, shipment_ids, context=dict(context, partial_datas=partial_datas))
371
477
def integrity_check_return_packs_from_shipment(self, cr, uid, ids, data, context=None):
373
479
integrity check on shipment data
480
(sfrom = selected from, sto = selected to)
482
- rule #1: sfrom <= sto // integrity of selected sequence
483
- rule #2: (sfrom >= from) and (sto <= to) // in the initial range
484
- rule #3: sfrom[i] > sto[i-1] for i>0 // no overlapping, unique sequence
487
{240: {1: {1: [{'selected_weight': 33.0, 'memory_move_id': 39, 'return_from': 1, 'weight': 33.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 1, 'width': 0.0, 'sale_order_id': 61}, {'selected_weight': 33.0, 'memory_move_id': 50, 'return_from': 1, 'weight': 33.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 1, 'width': 0.0, 'sale_order_id': 61}]},
488
2: {30: [{'selected_weight': 638.0, 'memory_move_id': 40, 'return_from': 2, 'weight': 22.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 30, 'integrity_status': 'empty', 'num_of_packs': 29, 'selected_number': 29, 'return_to': 30, 'width': 0.0, 'sale_order_id': 61}, {'selected_weight': 638.0, 'memory_move_id': 51, 'return_from': 2, 'weight': 22.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 30, 'integrity_status': 'empty', 'num_of_packs': 29, 'selected_number': 29, 'return_to': 30, 'width': 0.0, 'sale_order_id': 61}, {'selected_weight': 638.0, 'memory_move_id': 52, 'return_from': 2, 'weight': 22.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 30, 'integrity_status': 'empty', 'num_of_packs': 29, 'selected_number': 29, 'return_to': 30, 'width': 0.0, 'sale_order_id': 61}]},
489
31: {32: [{'selected_weight': 22.0, 'memory_move_id': 41, 'return_from': 31, 'weight': 11.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 31, 'length': 0.0, 'to_pack': 32, 'integrity_status': 'empty', 'num_of_packs': 2, 'selected_number': 2, 'return_to': 32, 'width': 0.0, 'sale_order_id': 61}]}},
490
241: {8: {8: [{'selected_weight': 5.0, 'memory_move_id': 42, 'return_from': 8, 'weight': 5.0, 'pack_type': False, 'ppl_id': 225, 'draft_packing_id': 241, 'wizard_id': 5, 'height': 0.0, 'from_pack': 8, 'length': 0.0, 'to_pack': 8, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 8, 'width': 0.0, 'sale_order_id': 61}]}, 1: {1: [{'selected_weight': 3.0, 'memory_move_id': 43, 'return_from': 1, 'weight': 3.0, 'pack_type': False, 'ppl_id': 225, 'draft_packing_id': 241, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 1, 'width': 0.0, 'sale_order_id': 61}]}, 2: {7: [{'selected_weight': 24.0, 'memory_move_id': 44, 'return_from': 2, 'weight': 4.0, 'pack_type': False, 'ppl_id': 225, 'draft_packing_id': 241, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 7, 'integrity_status': 'empty', 'num_of_packs': 6, 'selected_number': 6, 'return_to': 7, 'width': 0.0, 'sale_order_id': 61}]}},
491
238: {16: {16: [{'selected_weight': 22.0, 'memory_move_id': 45, 'return_from': 16, 'weight': 22.0, 'pack_type': False, 'ppl_id': 231, 'draft_packing_id': 238, 'wizard_id': 5, 'height': 0.0, 'from_pack': 16, 'length': 0.0, 'to_pack': 16, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 16, 'width': 0.0, 'sale_order_id': 62}]}, 1: {10: [{'selected_weight': 440.0, 'memory_move_id': 46, 'return_from': 1, 'weight': 44.0, 'pack_type': False, 'ppl_id': 231, 'draft_packing_id': 238, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 10, 'integrity_status': 'empty', 'num_of_packs': 10, 'selected_number': 10, 'return_to': 10, 'width': 0.0, 'sale_order_id': 62}]}, 11: {15: [{'selected_weight': 165.0, 'memory_move_id': 47, 'return_from': 11, 'weight': 33.0, 'pack_type': False, 'ppl_id': 231, 'draft_packing_id': 238, 'wizard_id': 5, 'height': 0.0, 'from_pack': 11, 'length': 0.0, 'to_pack': 15, 'integrity_status': 'empty', 'num_of_packs': 5, 'selected_number': 5, 'return_to': 15, 'width': 0.0, 'sale_order_id': 62}]}},
492
239: {1: {1: [{'selected_weight': 22.0, 'memory_move_id': 48, 'return_from': 1, 'weight': 22.0, 'pack_type': False, 'ppl_id': 230, 'draft_packing_id': 239, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 1, 'width': 0.0, 'sale_order_id': 62}]}, 2: {2: [{'selected_weight': 33.0, 'memory_move_id': 49, 'return_from': 2, 'weight': 33.0, 'pack_type': False, 'ppl_id': 230, 'draft_packing_id': 239, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 2, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 2, 'width': 0.0, 'sale_order_id': 62}]}}}}
494
memory_move_obj = self.pool.get('stock.move.memory.shipment.returnpacksfromshipment')
375
495
for shipment_data in data.values():
496
# counter for detecting empty return
497
number_of_sequences = 0
498
# flag for detecting to value smaller than from value
499
to_samller_than_from = False
500
# flag for detecting overlapping sequences
502
# flag for detecting out of range selection
376
504
for packing_data in shipment_data.values():
505
# list of sequences for each picking - sequences must be treated separately for each packing
507
# gather the sequences for this packing - ppl (one packing corresponds to one ppl)
377
508
for from_pack_data in packing_data.values():
378
509
for to_pack_data in from_pack_data.values():
379
510
for partial in to_pack_data:
380
if partial.get('return_from', False) and partial.get('return_to', False):
511
# we have to treat all partial (split) data for each ppl as many sequence can exists for the same ppl
512
# rule #1: sfrom <= sto // integrity of selected sequence
513
if not (partial['return_from'] <= partial['return_to']):
514
to_samller_than_from = True
515
memory_move_obj.write(cr, uid, [partial['memory_move_id']], {'integrity_status': 'to_smaller_than_from',}, context=context)
516
# rule #2: (sfrom >= from) and (sto <= to) // in the initial range
517
elif not (partial['return_from'] >= partial['from_pack'] and partial['return_to'] <= partial['to_pack']):
519
memory_move_obj.write(cr, uid, [partial['memory_move_id']], {'integrity_status': 'seq_out_of_range',}, context=context)
521
# [0]: selected FROM PACK / [1]: selected TO PACK / [2]: MEMORY MOVE ID
522
sequences.append((partial['return_from'], partial['return_to'], partial['memory_move_id']))
523
# increase the number of valid sequences
524
number_of_sequences += len(sequences)
525
# sort the sequences according to from value
526
sequences = sorted(sequences, key=lambda seq: seq[0])
527
# go through the list of sequences applying the rules
528
for i in range(len(sequences)):
530
# rules 3 applies from second element
533
seqb = sequences[i-1]
534
# rule #3: sfrom[i] > sto[i-1] for i>0 // no overlapping, unique sequence
535
if not (seq[0] > seqb[1]):
537
memory_move_obj.write(cr, uid, [seq[2]], {'integrity_status': 'overlap',}, context=context)
539
# if error, return False
540
if not number_of_sequences or to_samller_than_from or overlap or out_of_range:
385
545
def do_return_packs_from_shipment(self, cr, uid, ids, context=None):
391
551
assert 'active_ids' in context, 'No shipment ids in context. Action call is wrong'
393
553
ship_obj = self.pool.get('shipment')
554
# name of the wizard field for moves (one2many)
555
field_name = 'product_moves_shipment_returnpacksfromshipment'
395
557
shipment_ids = context['active_ids']
396
558
# generate data structure - return_from and return_to must be non zero
559
# TODO: there is a problem with (0,3) for example as it does not take part to data
560
# the list is therefore empty, and no error message is displayed by the integrity check
561
# to be modified along with delete lines policy implementation
562
# as a (temporary?) fix, all conditions must be true at the same time to be skipped (0,0) is skipped, (0,3) isn't
397
563
partial_datas = self.generate_data_from_partial(cr, uid, ids, conditions=['return_from', 'return_to'], context=context)
398
# integrity check on wizard data
399
if not self.integrity_check_return_packs_from_shipment(cr, uid, ids, partial_datas, context=context):
400
raise osv.except_osv(_('Warning !'), _('You must at least select one pack to return!'))
565
# reset the integrity status of all lines
566
self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
567
# integrity check on wizard data - sequence -> no prodlot check as the screen is readonly
568
sequence_check = self.integrity_check_return_packs_from_shipment(cr, uid, ids, partial_datas, context=context)
569
if not sequence_check:
570
# the windows must be updated to trigger tree colors
571
return self.pool.get('wizard').open_wizard(cr, uid, shipment_ids, type='update', context=context)
401
572
# call stock_picking method which returns action call
402
573
return ship_obj.do_return_packs_from_shipment(cr, uid, shipment_ids, context=dict(context, partial_datas=partial_datas))
405
576
shipment_wizard()
579
class memory_additionalitems(osv.osv_memory):
581
view corresponding to additionalitems
585
_name = "memory.additionalitems"
586
_description="Additional Items"
588
_columns = {'name': fields.char(string='Additional Item', size=1024, required=True),
589
'quantity': fields.float(digits=(16,2), string='Quantity', required=True),
590
'uom': fields.many2one('product.uom', string='UOM', required=True),
591
'comment': fields.char(string='Comment', size=1024),
592
'volume': fields.float(digits=(16,2), string='Volume[dm³]'),
593
'weight': fields.float(digits=(16,2), string='Weight[kg]', required=True),
594
'picking_id': fields.many2one('stock.picking', 'PPL', readonly=True),
595
'additional_item_id': fields.many2one('shipment.additionalitems', 'Additional item id', readonly=True),
598
memory_additionalitems()
601
class stock_move_memory_shipment_additionalitems(osv.osv_memory):
603
view corresponding to additionalitems
607
_inherit = "memory.additionalitems"
608
_name = 'stock.move.memory.shipment.additionalitems'
609
_description="Additional Items"
611
'wizard_id' : fields.many2one('shipment.wizard', string="Wizard"),
613
stock_move_memory_shipment_additionalitems()