22
22
from osv import fields, osv
23
23
from tools.translate import _
26
import decimal_precision as dp
27
from msf_outgoing import INTEGRITY_STATUS_SELECTION
30
class create_picking_processor(osv.osv):
32
Create picking processing wizard
34
_name = 'create.picking.processor'
35
_inherit = 'internal.picking.processor'
36
_description = 'Wizard to process the first step of the Pick/Pack/Ship'
39
'move_ids': fields.one2many(
40
'create.picking.move.processor',
49
def do_create_picking(self, cr, uid, ids, context=None):
51
Made some integrity checks and launch create_picking method of the stock.picking object
54
picking_obj = self.pool.get('stock.picking')
56
wizard_brw_list = self.browse(cr, uid, ids, context=context)
58
self.integrity_check_quantity(cr, uid, wizard_brw_list, context=context)
59
self.integrity_check_prodlot(cr, uid, wizard_brw_list, context=context)
60
# call stock_picking method which returns action call
61
return picking_obj.do_create_picking(cr, uid, ids, context=context)
63
create_picking_processor()
66
class create_picking_move_processor(osv.osv):
68
Create picking moves processing wizard
70
_name = 'create.picking.move.processor'
71
_inherit = 'internal.move.processor'
72
_description = 'Wizard lines for create picking processor'
74
def _get_move_info(self, cr, uid, ids, field_name, args, context=None):
75
return super(create_picking_move_processor, self)._get_move_info(cr, uid, ids, field_name, args, context=context)
77
def _get_product_info(self, cr, uid, ids, field_name, args, context=None):
78
return super(create_picking_move_processor, self)._get_product_info(cr, uid, ids, field_name, args, context=context)
80
def _get_integrity_status(self, cr, uid, ids, field_name, args, context=None):
81
return super(create_picking_move_processor, self)._get_integrity_status(cr, uid, ids, field_name, args, context=context)
85
'wizard_id': fields.many2one(
86
'create.picking.processor',
93
'ordered_product_id': fields.function(
96
string='Ordered product',
98
relation='product.product',
100
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
103
help="Expected product to receive",
106
'ordered_uom_id': fields.function(
109
string='Ordered UoM',
111
relation='product.uom',
113
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
116
help="Expected UoM to receive",
119
'ordered_uom_category': fields.function(
122
string='Ordered UoM category',
124
relation='product.uom.categ',
126
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
129
help="Category of the expected UoM to receive",
132
'location_id': fields.function(
137
relation='stock.location',
139
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
142
help="Source location of the move",
145
'location_supplier_customer_mem_out': fields.function(
148
string='Location Supplier Customer',
151
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
157
'integrity_status': fields.function(
158
_get_integrity_status,
162
selection=INTEGRITY_STATUS_SELECTION,
164
'create.picking.move.processor': (
165
lambda self, cr, uid, ids, c=None: ids,
166
['product_id', 'wizard_id', 'quantity', 'asset_id', 'prodlot_id', 'expiry_date'],
171
help="Integrity status (e.g: check if a batch is set for a line with a batch mandatory product...)",
173
'type_check': fields.function(
176
string='Picking Type Check',
180
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
183
help="Return the type of the picking",
186
'lot_check': fields.function(
192
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
195
multi='product_info',
196
help="A batch number is required on this line",
198
'exp_check': fields.function(
204
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
207
multi='product_info',
208
help="An expiry date is required on this line",
210
'asset_check': fields.function(
216
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
219
multi='product_info',
220
help="An asset is required on this line",
222
'kit_check': fields.function(
228
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
231
multi='product_info',
232
help="A kit is required on this line",
234
'kc_check': fields.function(
240
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
243
multi='product_info',
244
help="Ticked if the product is a Heat Sensitive Item",
246
'ssl_check': fields.function(
252
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
255
multi='product_info',
256
help="Ticked if the product is a Short Shelf Life product",
258
'dg_check': fields.function(
264
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
267
multi='product_info',
268
help="Ticked if the product is a Dangerous Good",
270
'np_check': fields.function(
276
'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
279
multi='product_info',
280
help="Ticked if the product is a Narcotic",
284
create_picking_move_processor()
287
27
class create_picking(osv.osv_memory):
288
28
_name = "create.picking"
294
34
'product_moves_families' : fields.one2many('stock.move.memory.families', 'wizard_id', 'Pack Families'),
295
35
'product_moves_returnproducts': fields.one2many('stock.move.memory.returnproducts', 'wizard_id', 'Return Products')
298
def copy_all(self, cr, uid, ids, context=None):
299
create = self.browse(cr, uid, ids[0], context=context)
300
if create.product_moves_picking:
301
for move in create.product_moves_picking:
302
self.pool.get('stock.move.memory.picking').write(cr, uid, [move.id], {'quantity': move.ordered_quantity})
303
if create.product_moves_ppl:
304
for move in create.product_moves_ppl:
305
self.pool.get('stock.move.memory.ppl').write(cr, uid, [move.id], {'quantity': move.ordered_quantity})
306
if create.product_moves_families:
307
for move in create.product_moves_families:
308
self.pool.get('stock.move.memory.families').write(cr, uid, [move.id], {'quantity': move.ordered_quantity})
309
if create.product_moves_returnproducts:
310
for move in create.product_moves_returnproducts:
311
self.pool.get('stock.move.memory.returnproducts').write(cr, uid, [move.id], {'quantity': move.ordered_quantity})
312
return self.pool.get('wizard').open_wizard(cr, uid, [ids[0]], type='update', context=context)
314
38
def default_get(self, cr, uid, fields, context=None):
315
39
""" To get default values for the object.
316
40
@param self: The object pointer.
323
47
if context is None:
326
50
# we need the step info
327
51
assert 'step' in context, 'Step not defined in context'
328
52
step = context['step']
330
54
pick_obj = self.pool.get('stock.picking')
331
55
res = super(create_picking, self).default_get(cr, uid, fields, context=context)
332
56
picking_ids = context.get('active_ids', [])
333
57
if not picking_ids:
337
61
if step in ('create', 'validate', 'ppl1', 'returnproducts'):
338
62
# memory moves wizards
339
63
# data generated from stock.moves
340
64
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
341
result.extend(self.__create_partial_picking_memory(pick, step, context=context))
65
result.extend(self.__create_partial_picking_memory(pick, context=context))
342
66
elif step in ('ppl2'):
343
67
# pack families wizard
344
68
# data generated from previous wizard data
345
69
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
346
70
result.extend(self.__create_pack_families_memory(pick, context=context))
348
72
if 'product_moves_picking' in fields and step in ('create', 'validate'):
349
73
res.update({'product_moves_picking': result})
351
75
if 'product_moves_ppl' in fields and step in ('ppl1'):
352
76
res.update({'product_moves_ppl': result})
354
78
if 'product_moves_returnproducts' in fields and step in ('returnproducts'):
355
79
res.update({'product_moves_returnproducts': result})
357
81
if 'product_moves_families' in fields and step in ('ppl2'):
358
82
res.update({'product_moves_families': result})
360
84
if 'date' in fields:
361
85
res.update({'date': time.strftime('%Y-%m-%d %H:%M:%S')})
365
def __create_partial_picking_memory(self, pick, step, context=None):
89
def __create_partial_picking_memory(self, pick, context=None):
367
91
generates the memory objects data depending on wizard step
369
93
- wizard_id seems to be filled automatically
371
95
assert context, 'No context defined'
372
96
assert 'step' in context, 'No step defined in context'
373
97
step = context['step']
375
99
# list for the current pick object
377
101
for move in pick.move_lines:
378
if move.state in ('done', 'cancel', 'confirmed', 'draft') or move.product_qty == 0.00:
102
if move.state in ('done', 'cancel'):
381
'line_number': move.line_number,
382
105
'product_id' : move.product_id.id,
383
'asset_id': move.asset_id.id,
384
'composition_list_id': move.composition_list_id.id,
385
'ordered_quantity' : move.product_qty,
386
'uom_ordered': move.product_uom.id,
387
'product_uom' : move.product_uom.id,
388
'prodlot_id' : move.prodlot_id.id,
106
'asset_id': move.asset_id.id,
107
'quantity' : move.product_qty,
108
'product_uom' : move.product_uom.id,
109
'prodlot_id' : move.prodlot_id.id,
389
110
'move_id' : move.id,
390
111
# specific management rules
391
112
'expiry_date': move.expired_date,
394
move_memory['quantity'] = move.product_qty
396
115
# the first wizard of ppl, we set default values as everything is packed in one pack
397
116
# if step == 'ppl1':
398
117
# move_memory.update({'qty_per_pack': move.product_qty, 'from_pack': 1, 'to_pack': 1})
399
118
# append the created dict
400
119
result.append(move_memory)
402
121
# return the list of dictionaries
405
124
def __create_pack_families_memory(self, pick, context=None):
407
126
generates the memory objects data depending on wizard step
409
128
- wizard_id seems to be filled automatically
411
130
assert context, 'No context defined'
491
200
# refactoring is needed here !
492
201
if picking_subtype == 'picking':
493
202
if step == 'create':
494
button = ('do_create_picking', _('Create Picking'))
203
button = ('do_create_picking', 'Create Picking')
495
204
elif step == 'validate':
496
button = ('do_validate_picking', _('Validate Picking'))
205
button = ('do_validate_picking', 'Validate Picking')
497
206
# ppl, two wizard steps
498
207
elif picking_subtype == 'ppl':
499
208
if step == 'ppl1':
500
button = ('do_ppl1', _('Next'))
209
button = ('do_ppl1', 'Next')
501
210
if step == 'ppl2':
502
button = ('do_ppl2', _('Validate PPL'))
211
button = ('do_ppl2', 'Validate PPL')
503
212
if step == 'returnproducts':
504
button = ('do_return_products', _('Return'))
213
button = ('do_return_products', 'Return')
507
216
button = ('undefined', 'Undefined')
509
218
_moves_arch_lst += """
510
219
<separator string="" colspan="4" />
511
220
<label string="" colspan="2"/>
512
221
<group col="4" colspan="2">
513
222
<button icon='gtk-cancel' special="cancel"
514
string="%s" />""" % _('_Cancel')
223
string="_Cancel" />"""
516
225
if step == 'ppl2':
517
226
_moves_arch_lst += """
518
<button name="back_ppl1" string="%s"
519
colspan="1" type="object" icon="gtk-go-back" />""" % _('Previous')
227
<button name="back_ppl1" string="previous"
228
colspan="1" type="object" icon="gtk-go-back" />"""
230
elif step == 'returnproducts':
231
_moves_arch_lst += """
232
<button name="select_all" string="Select All"
233
colspan="1" type="object" icon="terp_stock_symbol-selection" />
234
<button name="deselect_all" string="Deselect All"
235
colspan="1" type="object" icon="terp_stock_symbol-selection" />"""
522
237
_moves_arch_lst += """
523
238
<button name="%s" string="%s"
524
239
colspan="1" type="object" icon="gtk-go-forward" />
528
243
result['arch'] = _moves_arch_lst
529
244
result['fields'] = _moves_fields
530
245
# add messages from specific management rules
531
246
result = self.pool.get('stock.partial.picking').add_message(cr, uid, result, context=context)
534
249
def select_all(self, cr, uid, ids, context=None):
536
251
select all buttons, write max qty in each line
538
should be modified for more generic way, with something like:
540
fields = self.fields_get(cr, uid, context=context)
541
# loop through fields, if one2many, we set the values for all lines
542
for key in fields.keys():
543
type = fields[key]['type']
544
if type in ['one2many']:
545
lines = getattr(wiz, key)
547
line.write({'quantity': line.initial_qty}, context=context)
549
the problem is that in the different wizard we use different fields for
550
selected qty (quantity, qty_to_return, ...). This should be unified as well
551
to allow previous idea.
554
picking_ids = context['active_ids']
555
253
for wiz in self.browse(cr, uid, ids, context=context):
556
254
for line in wiz.product_moves_picking:
557
255
# get the qty from the corresponding stock move
558
original_qty = line.ordered_quantity
559
line.write({'quantity':original_qty, }, context=context)
256
original_qty = line.move_id.product_qty
257
line.write({'quantity':original_qty,}, context=context)
560
258
for line in wiz.product_moves_returnproducts:
561
line.write({'qty_to_return':line.ordered_quantity, }, context=context)
562
# update the current wizard
563
return self.pool.get('wizard').open_wizard(cr, uid, picking_ids, type='update', context=context)
259
line.write({'qty_to_return':line.quantity,}, context=context)
262
'name': context.get('wizard_name'),
266
'res_model': context.get('model'),
268
'type': 'ir.actions.act_window',
565
275
def deselect_all(self, cr, uid, ids, context=None):
567
277
deselect all buttons, write 0 qty in each line
570
picking_ids = context['active_ids']
571
279
for wiz in self.browse(cr, uid, ids, context=context):
572
280
for line in wiz.product_moves_picking:
573
line.write({'quantity':0.0, }, context=context)
281
line.write({'quantity':0.0,}, context=context)
574
282
for line in wiz.product_moves_returnproducts:
575
line.write({'qty_to_return':0.0, }, context=context)
576
# update the current wizard
577
return self.pool.get('wizard').open_wizard(cr, uid, picking_ids, type='update', context=context)
283
line.write({'qty_to_return':0.0,}, context=context)
286
'name': context.get('wizard_name'),
290
'res_model': context.get('model'),
292
'type': 'ir.actions.act_window',
579
299
def generate_data_from_partial(self, cr, uid, ids, context=None):
581
301
data is located in product_moves_ppl
583
303
we generate the data structure from the first ppl wizard (ppl1)
586
306
{pick_id: {from_pack: {to_pack: {move_id: [{partial},]}}}}
588
308
data are indexed by pack_id, then by pack_family information (from_pack/to_pack)
589
309
and finally by move_id. Move_id indexing is added because within one
590
310
pack sequence we can find the same move_id multiple time thanks to split function.
592
312
with partial beeing the info for one stock.move.memory.ppl
594
314
# integrity check
595
315
assert context, 'no context, method call is wrong'
596
316
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
598
318
pick_obj = self.pool.get('stock.picking')
599
319
# partial data from wizard
600
320
partial = self.browse(cr, uid, ids[0], context=context)
602
322
partial_datas_ppl1 = {}
605
325
picking_ids = context['active_ids']
606
326
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
613
333
partial_datas_ppl1[pick.id] \
614
334
.setdefault(move.from_pack, {}) \
615
335
.setdefault(move.to_pack, {}) \
616
.setdefault(move.move_id.id, []).append({'memory_move_id': move.id,
336
.setdefault(move.move_id.id, []).append({
617
337
'product_id': move.product_id.id,
618
'product_qty': move.ordered_quantity,
338
'product_qty': move.quantity,
619
339
'product_uom': move.product_uom.id,
620
340
'prodlot_id': move.prodlot_id.id,
621
341
'asset_id': move.asset_id.id,
622
'line_number': move.line_number,
623
'composition_list_id': move.composition_list_id.id,
624
342
'move_id': move.move_id.id,
625
343
'qty_per_pack': move.qty_per_pack,
626
344
'from_pack': move.from_pack,
627
345
'to_pack': move.to_pack,
630
348
return partial_datas_ppl1
632
350
def update_data_from_partial(self, cr, uid, ids, context=None):
634
352
update the list corresponding to moves for each sequence with ppl2 information
636
354
generated structure from step ppl1 wizard is updated with step ppl2 wizard,
637
355
the partial dictionaries are updated with pack_family related information
640
358
{pick_id: {from_pack: {to_pack: {move_id: [{partial},]}}}}
642
360
assert context, 'no context defined'
643
361
assert 'partial_datas_ppl1' in context, 'partial_datas_ppl1 not in context'
644
363
pick_obj = self.pool.get('stock.picking')
645
364
family_obj = self.pool.get('stock.move.memory.families')
646
365
# partial data from wizard
658
378
# find corresponding sequence info
659
379
family_ids = family_obj.search(cr, uid, [('wizard_id', '=', ids[0]), ('from_pack', '=', from_pack), ('to_pack', '=', to_pack)], context=context)
660
380
# only one line should match
661
assert len(family_ids) == 1, 'No the good number of families : %i' % len(family_ids)
381
assert len(family_ids) == 1, 'No the good number of families : %i'%len(family_ids)
662
382
family = family_obj.read(cr, uid, family_ids, ['pack_type', 'length', 'width', 'height', 'weight'], context=context)[0]
664
family['memory_move_id'] = family.pop('id')
665
385
for move in partial_datas_ppl1[picking_id][from_pack][to_pack]:
666
386
for partial in partial_datas_ppl1[picking_id][from_pack][to_pack][move]:
667
387
partial.update(family)
669
def set_integrity_status(self, cr, uid, ids, field_name, status='empty', context=None):
671
for all moves set the status to ok (default value) or other if specified
673
for wiz in self.browse(cr, uid, ids, context=context):
674
for memory_move in getattr(wiz, field_name):
675
memory_move.write({'integrity_status': status, }, context=context)
677
def set_integrity_status_for_empty_moves(self, cr, uid, ids, field_name, status='empty_picking', context=None):
679
for all moves set the status to empty_picking if move qty is 0
681
deprecated - not used, by default the validation is empty not ok
683
for wiz in self.browse(cr, uid, ids, context=context):
684
for memory_move in getattr(wiz, field_name):
685
if not memory_move.quantity:
686
memory_move.write({'integrity_status': status, }, context=context)
688
def integrity_check_create_picking(self, cr, uid, ids, data, context=None):
690
integrity check on create picking data
691
- rule #1: no negative values (<0)
692
- rule #2: at least one positive value (>0)
696
memory_move_obj = self.pool.get('stock.move.memory.picking')
698
for picking_data in data.values():
699
# total sum not including negative values
701
# flag to detect negative values
702
negative_value = False
703
for move_data in picking_data.values():
704
for list_data in move_data:
705
# rule #1: quantity check
706
if list_data['product_qty'] < 0.0:
707
# a negative value has been selected, update the memory line
708
# update the new value for integrity check with 'negative' value (selection field)
709
negative_value = True
710
memory_move_obj.write(cr, uid, [list_data['memory_move_id']], {'integrity_status': 'negative', }, context=context)
712
# rule #2: no empty picking
713
sum_qty += list_data['product_qty']
714
# if error, return False
715
if not sum_qty or negative_value:
719
def integrity_check_prodlot(self, cr, uid, ids, data, validate=True, context=None):
722
- rule #1 a batch management product needs a standard production lot ***ONLY AT PICKING VALIDATION STAGE
723
- rule #2 a expiry date product needs an internal production lot ***ONLY AT PICKING VALIDATION STAGE
724
- rule #3 a not lot managed product does not allow production lot
725
- rule #4 a batch management product does not allow internal production lot
726
- rule #5 a expiry date product does not allow standard production lot
728
- the production lot is mandatory only if it is the validation stage
730
prod_obj = self.pool.get('product.product')
731
uom_obj = self.pool.get('product.uom')
732
memory_move_obj = self.pool.get('stock.move.memory.picking')
733
lot_obj = self.pool.get('stock.production.lot')
734
# flag to detect missing prodlot
736
# has prodlot but should not
737
lot_not_needed = False
738
# wrong production lot type
739
wrong_lot_type = False
741
for picking_data in data.values():
742
prodlot_integrity = {}
743
for move_data in picking_data.values():
744
for list_data in move_data:
745
# product id must exist
746
prod_id = list_data['product_id']
747
prod = prod_obj.browse(cr, uid, prod_id, context=context)
748
# a production lot is defined, corresponding checks
749
if list_data['prodlot_id']:
750
if list_data['location_id']:
751
context.update({'location_id': list_data['location_id']})
752
lot = lot_obj.browse(cr, uid, list_data['prodlot_id'], context=context)
753
# a prod lot is defined, the product must be either perishable or batch_management
754
if not (prod.perishable or prod.batch_management):
755
# rule #3: should not have production lot
756
lot_not_needed = True
757
memory_move_obj.write(cr, uid, [list_data['memory_move_id']], {'integrity_status': 'no_lot_needed', }, context=context)
758
# rule #5: perishable -> the prod lot must be of type 'internal'
759
if prod.perishable and not prod.batch_management and lot.type != 'internal':
760
wrong_lot_type = True
761
memory_move_obj.write(cr, uid, [list_data['memory_move_id']], {'integrity_status': 'wrong_lot_type_need_internal', }, context=context)
762
# rule #4: batch_management -> the prod lot must be of type 'standard'
763
if prod.batch_management and lot.type != 'standard':
764
wrong_lot_type = True
765
memory_move_obj.write(cr, uid, [list_data['memory_move_id']], {'integrity_status': 'wrong_lot_type_need_standard', }, context=context)
767
if list_data['location_id']:
768
loc_id = list_data['location_id']
769
# Add a check on prodlot quantity
770
if lot.id not in prodlot_integrity:
771
prodlot_integrity.update({lot.id: {}})
772
if loc_id not in prodlot_integrity[lot.id]:
773
prodlot_integrity[lot.id].update({loc_id: 0.00})
774
product_qty = uom_obj._compute_qty(cr, uid, list_data['product_uom'], list_data['product_qty'], lot.product_id.uom_id.id)
775
prodlot_integrity[lot.id][loc_id] += product_qty
777
if lot.stock_available < product_qty:
778
uom = self.pool.get('product.uom').browse(cr, uid, list_data['product_uom']).name
779
raise osv.except_osv(_('Processing Error'), \
780
_('Processing quantity %d %s for %s is larger than the available quantity in Batch Number %s (%d) !')\
781
% (list_data['product_qty'], uom, prod.name, \
782
lot.name, lot.stock_available))
784
# only mandatory at validation stage
786
# no production lot defined, corresponding checks
787
# rule #1 a batch management product needs a standard production lot
788
if prod.batch_management:
790
memory_move_obj.write(cr, uid, [list_data['memory_move_id']], {'integrity_status': 'missing_lot', }, context=context)
791
# rule #2 a expiry date product needs an internal production lot
792
elif prod.perishable:
794
memory_move_obj.write(cr, uid, [list_data['memory_move_id']], {'integrity_status': 'missing_date', }, context=context)
796
# Check prodlot qty integrity
797
for prodlot in prodlot_integrity:
798
for location in prodlot_integrity[prodlot]:
799
tmp_prodlot = lot_obj.browse(cr, uid, prodlot, context={'location_id': location})
800
prodlot_qty = tmp_prodlot.stock_available
801
if prodlot_qty < prodlot_integrity[prodlot][location]:
802
raise osv.except_osv(_('Processing Error'), \
803
_('Processing quantity %d for %s is larger than the available quantity in Batch Number %s (%d) !')\
804
% (prodlot_integrity[prodlot][location], tmp_prodlot.product_id.name, tmp_prodlot.name, \
807
# if error, return False
808
if missing_lot or lot_not_needed or wrong_lot_type:
812
def do_create_picking_first_hook(self, cr, uid, context, *args, **kwargs):
814
add hook to do_create_picking: This hook's first aim was to complete the module msf_cross_docking
816
partial_datas = kwargs.get('partial_datas')
817
assert partial_datas, 'partial_datas missing'
821
389
def do_create_picking(self, cr, uid, ids, context=None):
823
391
create the picking ticket from selected stock moves
824
392
-> only related to 'out' type stock.picking
826
394
- transform data from wizard
828
396
# integrity check
850
416
for move in memory_moves_list:
851
417
# !!! only take into account if the quantity is greater than 0 !!!
852
418
if move.quantity:
853
total_qty += move.quantity
854
partial_datas[pick.id].setdefault(move.move_id.id, []).append({'memory_move_id': move.id,
855
'product_id': move.product_id.id,
419
partial_datas[pick.id].setdefault(move.move_id.id, []).append({'product_id': move.product_id.id,
856
420
'product_qty': move.quantity,
857
421
'product_uom': move.product_uom.id,
858
422
'prodlot_id': move.prodlot_id.id,
859
'line_number': move.line_number,
860
'location_id': move.location_id.id,
861
423
'asset_id': move.asset_id.id,
862
'composition_list_id': move.composition_list_id.id,
864
# override : add hook call
865
partial_datas = self.do_create_picking_first_hook(cr, uid, context, move=move, partial_datas=partial_datas)
866
if not total_qty and not context.get('yml_test'):
867
raise osv.except_osv(_('Processing Error'), _("You have to enter the quantities you want to process before processing the move"))
868
# reset the integrity status of all lines
869
self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
870
# integrity check on wizard data - quantities
871
quantity_check = self.integrity_check_create_picking(cr, uid, ids, partial_datas, context=context)
872
# prodlot - in separate method because is not checked at picking ticket creation
873
prodlot_check = self.integrity_check_prodlot(cr, uid, ids, partial_datas, validate=False, context=context)
874
if not quantity_check or not prodlot_check:
875
# the windows must be updated to trigger tree colors - so no raise
876
return self.pool.get('wizard').open_wizard(cr, uid, picking_ids, type='update', context=context)
877
425
# call stock_picking method which returns action call
878
426
return pick_obj.do_create_picking(cr, uid, picking_ids, context=dict(context, partial_datas=partial_datas))
880
428
def quick_mode(self, cr, uid, ppl, context=None):
882
430
we do the quick mode, the ppl step is performed automatically
884
432
assert context, 'missing Context'
886
434
moves_ppl_obj = self.pool.get('stock.move.memory.ppl')
888
436
# set the corresponding ppl object
889
437
context['active_ids'] = [ppl.id]
892
440
context['step'] = 'ppl1'
893
441
# create a create_picking object for ppl1
929
468
picking_ids = context['active_ids']
930
469
# partial data from wizard
931
470
partial = self.browse(cr, uid, ids[0], context=context)
932
# name of the wizard field for moves (one2many)
933
field_name = 'product_moves_picking'
935
472
pick_obj = self.pool.get('stock.picking')
936
473
move_obj = self.pool.get('stock.move')
939
476
partial_datas = {}
941
478
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
943
479
# for each picking
944
480
partial_datas[pick.id] = {}
945
481
# out moves for delivery
946
482
memory_moves_list = partial.product_moves_picking
947
483
# organize data according to move id
948
484
for move in memory_moves_list:
949
total_qty += move.quantity
950
partial_datas[pick.id].setdefault(move.move_id.id, []).append({'memory_move_id': move.id,
951
'product_id': move.product_id.id,
486
partial_datas[pick.id].setdefault(move.move_id.id, []).append({'product_id': move.product_id.id,
952
487
'product_qty': move.quantity,
953
488
'product_uom': move.product_uom.id,
954
489
'prodlot_id': move.prodlot_id.id,
955
'location_id': move.location_id.id,
956
'line_number': move.line_number,
957
490
'asset_id': move.asset_id.id,
958
'composition_list_id': move.composition_list_id.id,
960
# override : add hook call
961
partial_datas = self.do_validate_picking_first_hook(cr, uid, context, move=move, partial_datas=partial_datas)
963
raise osv.except_osv(_('Processing Error'), _("You have to enter the quantities you want to process before processing the move"))
964
# reset the integrity status of all lines
965
self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
966
# integrity check on wizard data - quantities
967
quantity_check = self.integrity_check_create_picking(cr, uid, ids, partial_datas, context=context)
968
# prodlot - in separate method because is not checked at picking ticket creation
969
prodlot_check = self.integrity_check_prodlot(cr, uid, ids, partial_datas, context=context)
970
if not quantity_check or not prodlot_check:
971
# the windows must be updated to trigger tree colors
972
return self.pool.get('wizard').open_wizard(cr, uid, picking_ids, type='update', context=context)
973
493
# call stock_picking method which returns action call
974
494
return pick_obj.do_validate_picking(cr, uid, picking_ids, context=dict(context, partial_datas=partial_datas))
976
def integrity_check_return_products(self, cr, uid, ids, data, context=None):
978
integrity check on create picking data
979
- #1 no negative values (<0)
980
- #2 at least one positive one (>0)
981
- #3 no more than available quantity
985
memory_move_obj = self.pool.get('stock.move.memory.returnproducts')
496
def integrity_check(self, cr, uid, ids, data, context=None):
498
integrity check on shipment data
987
500
for picking_data in data.values():
988
# total sum not including negative values
990
# flag to detect negative values
991
negative_value = False
992
# flag to detect excessive return quantity
994
501
for move_data in picking_data.values():
996
if move_data['qty_to_return'] < 0.0:
997
# a negative value has been selected, update the memory line
998
# update the new value for integrity check with 'negative' value (selection field)
999
negative_value = True
1000
memory_move_obj.write(cr, uid, [move_data['memory_move_id']], {'integrity_status': 'negative', }, context=context)
1001
elif move_data['qty_to_return'] > move_data['product_qty']:
1002
# cannot return more products than available
1004
memory_move_obj.write(cr, uid, [move_data['memory_move_id']], {'integrity_status': 'return_qty_too_much', }, context=context)
1006
sum_qty += move_data['qty_to_return']
1007
# if error, return False
1008
if not sum_qty or negative_value or too_much:
502
if move_data.get('qty_to_return', False):
1012
507
def do_return_products(self, cr, uid, ids, context=None):
1014
509
process data and call do_return_products from stock picking
1017
512
{picking_id: {move_id: {data}}}
1019
514
# integrity check
1020
515
assert context, 'no context, method call is wrong'
1021
516
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
1023
518
pick_obj = self.pool.get('stock.picking')
1024
519
move_obj = self.pool.get('stock.move')
1025
520
# partial data from wizard
1026
521
partial = self.browse(cr, uid, ids[0], context=context)
1027
522
partial_datas = {}
1028
# name of the wizard field for moves (one2many)
1029
field_name = 'product_moves_returnproducts'
1032
525
picking_ids = context['active_ids']
1033
526
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
1038
531
# organize data according to move id
1039
532
for move in memory_moves_list:
1040
533
if move.qty_to_return:
1041
partial_datas[pick.id][move.move_id.id] = {'memory_move_id': move.id,
1042
'product_id': move.product_id.id,
1043
'product_qty': move.ordered_quantity,
534
partial_datas[pick.id][move.move_id.id] = {'product_id': move.product_id.id,
535
'asset_id': move.asset_id.id,
536
'product_qty': move.quantity,
1044
537
'product_uom': move.product_uom.id,
1045
538
'prodlot_id': move.prodlot_id.id,
1046
'asset_id': move.asset_id.id,
1047
'line_number': move.line_number,
1048
'composition_list_id': move.composition_list_id.id,
1049
539
'qty_to_return': move.qty_to_return,
1052
# reset the integrity status of all lines
1053
self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
1054
# integrity check on wizard data - quantities -> no prodlot check as the screen is readonly
1055
quantity_check = self.integrity_check_return_products(cr, uid, ids, partial_datas, context=context)
1056
if not quantity_check:
1057
# the windows must be updated to trigger tree colors
1058
return self.pool.get('wizard').open_wizard(cr, uid, picking_ids, type='update', context=context)
542
# integrity check on wizard data
543
if not self.integrity_check(cr, uid, ids, partial_datas, context=context):
544
raise osv.except_osv(_('Warning !'), _('You must select something to return!'))
1059
546
return pick_obj.do_return_products(cr, uid, picking_ids, context=dict(context, partial_datas=partial_datas))
1061
def integrity_check_sequences(self, cr, uid, ids, data, context=None):
1063
integrity check on ppl1 data for sequence validation
1064
- #1 first from value must be 1
1065
- #2 sequence can share the exact same from/to value
1066
- #3 the numbering must be a monotonically increasing function
1067
- #4 there must be no gap within sequence
1072
417: [{'memory_move_id': 1, 'asset_id': False, 'from_pack': 1, 'prodlot_id': 28, 'qty_per_pack': 10.0,'product_id': 68, 'product_uom': 1, 'product_qty': 10.0, 'to_pack': 1, 'move_id': 417}],
1073
418: [{'memory_move_id': 2, 'asset_id': False, 'from_pack': 1, 'prodlot_id': 30, 'qty_per_pack': 20.0, 'product_id': 69, 'product_uom': 1, 'product_qty': 20.0, 'to_pack': 1, 'move_id': 418}]
1076
memory_move_obj = self.pool.get('stock.move.memory.ppl')
1078
for picking_data in data.values():
1079
# list of sequences for each picking
1081
# flag for detecting missing 1 initial from
1083
# flag for detecting to value smaller than from value
1084
to_samller_than_from = False
1085
# flag for detecting overlapping sequences
1087
# flag for detecting gap
1089
# gather the sequences
1090
for from_data in picking_data.values():
1091
for to_data in from_data.values():
1092
for move_data in to_data.values():
1093
for partial in move_data:
1094
# we have to treat all partial (split) data for each move as many sequence can exists for the same move
1095
# [0]: FROM PACK / [1]: TO PACK / [2]: MEMORY MOVE ID
1096
sequences.append((partial['from_pack'], partial['to_pack'], partial['memory_move_id']))
1097
# if no data, we return False
1100
# sort the sequences according to from value
1101
sequences = sorted(sequences, key=lambda seq: seq[0])
1103
# rule #1, the first from value must be equal to 1
1104
if sequences[0][0] != 1:
1106
memory_move_obj.write(cr, uid, [sequences[0][2]], {'integrity_status': 'missing_1', }, context=context)
1107
# go through the list of sequences applying the rules
1108
for i in range(len(sequences)):
1110
# rules 2-3 applies from second element
1112
# previsous sequence
1113
seqb = sequences[i - 1]
1114
# rule #2: if from[i] == from[i-1] -> to[i] == to[i-1]
1115
if (seq[0] == seqb[0]) and not (seq[1] == seqb[1]):
1117
memory_move_obj.write(cr, uid, [seq[2]], {'integrity_status': 'overlap', }, context=context)
1118
# rule #3: if from[i] != from[i-1] -> from[i] == to[i-1]+1
1119
if (seq[0] != seqb[0]) and not (seq[0] == seqb[1] + 1):
1120
if seq[0] < seqb[1] + 1:
1122
memory_move_obj.write(cr, uid, [seq[2]], {'integrity_status': 'overlap', }, context=context)
1123
if seq[0] > seqb[1] + 1:
1125
memory_move_obj.write(cr, uid, [seq[2]], {'integrity_status': 'gap', }, context=context)
1126
# rule #4: to[i] >= from[i]
1127
if not (seq[1] >= seq[0]):
1128
to_samller_than_from = True
1129
memory_move_obj.write(cr, uid, [seq[2]], {'integrity_status': 'to_smaller_than_from', }, context=context)
1130
# if error, return False
1131
if missing_1 or to_samller_than_from or overlap or gap:
1135
548
def do_ppl1(self, cr, uid, ids, context=None):
1140
553
# integrity check
1141
554
assert context, 'no context, method call is wrong'
1142
555
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
1144
557
pick_obj = self.pool.get('stock.picking')
1145
# name of the wizard field for moves (one2many)
1146
field_name = 'product_moves_ppl'
1149
559
picking_ids = context['active_ids']
1150
560
# generate data structure
1151
561
partial_datas_ppl1 = self.generate_data_from_partial(cr, uid, ids, context=context)
1153
# reset the integrity status of all lines
1154
self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
1155
# integrity check on wizard data - sequence -> no prodlot check as the screen is readonly
1156
sequence_check = self.integrity_check_sequences(cr, uid, ids, partial_datas_ppl1, context=context)
1157
if not sequence_check:
1158
# the windows must be updated to trigger tree colors
1159
return self.pool.get('wizard').open_wizard(cr, uid, picking_ids, type='update', context=context)
1161
562
# call stock_picking method which returns action call
1162
563
return pick_obj.do_ppl1(cr, uid, picking_ids, context=dict(context, partial_datas_ppl1=partial_datas_ppl1))
1164
565
def back_ppl1(self, cr, uid, ids, context=None):
1166
567
call back ppl1 step wizard
1168
569
# we need the context for the wizard switch
1169
570
assert context, 'no context defined'
1171
572
wiz_obj = self.pool.get('wizard')
1173
574
# no data for type 'back'
1174
575
return wiz_obj.open_wizard(cr, uid, context['active_ids'], type='back', context=context)
1176
577
def integrity_check_weight(self, cr, uid, ids, data, context=None):
1178
integrity check on ppl2 data for weight validation
1179
- weight must exist if not quick flow type
1183
417: [{'memory_move_id': 1, 'asset_id': False, 'from_pack': 1, 'prodlot_id': 28, 'qty_per_pack': 10.0,'product_id': 68, 'product_uom': 1, 'product_qty': 10.0, 'to_pack': 1, 'move_id': 417}],
1184
418: [{'memory_move_id': 2, 'asset_id': False, 'from_pack': 1, 'prodlot_id': 30, 'qty_per_pack': 20.0, 'product_id': 69, 'product_uom': 1, 'product_qty': 20.0, 'to_pack': 1, 'move_id': 418}]
579
integrity check on ppl2 data for weight
581
dict: {189L: {1: {1: {439: [{'asset_id': False, 'weight': False, 'product_id': 246, 'product_uom': 1,
582
'pack_type': False, 'length': False, 'to_pack': 1, 'height': False, 'from_pack': 1, 'prodlot_id': False,
583
'qty_per_pack': 1.0, 'product_qty': 1.0, 'width': False, 'move_id': 439}]}}}}
1187
memory_move_obj = self.pool.get('stock.move.memory.families')
1188
585
move_obj = self.pool.get('stock.move')
1189
586
for picking_data in data.values():
1190
# flag for missing weight
1191
missing_weight = False
1192
587
for from_data in picking_data.values():
1193
588
for to_data in from_data.values():
1194
589
for move_data in to_data.values():
1195
for partial in move_data:
1196
if partial['weight'] <= 0:
1197
move = move_obj.browse(cr, uid, partial['move_id'], context=context)
590
for data in move_data:
591
if not data.get('weight', False):
592
move = move_obj.browse(cr, uid, data.get('move_id'), context=context)
1198
593
flow_type = move.picking_id.flow_type
1199
594
if flow_type != 'quick':
1200
missing_weight = True
1201
memory_move_obj.write(cr, uid, [partial['memory_move_id']], {'integrity_status': 'missing_weight', }, context=context)
1202
# return false if weight is missing
1207
599
def do_ppl2(self, cr, uid, ids, context=None):
1209
601
- update partial_datas_ppl1
1212
604
# integrity check
1213
605
assert context, 'no context, method call is wrong'
1214
606
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
1215
608
pick_obj = self.pool.get('stock.picking')
1216
# name of the wizard field for moves (one2many)
1217
field_name = 'product_moves_families'
1219
610
picking_ids = context['active_ids']
1220
611
# update data structure
1221
612
self.update_data_from_partial(cr, uid, ids, context=context)
1222
613
# integrity check on wizard data
1223
614
partial_datas_ppl1 = context['partial_datas_ppl1']
1224
# reset the integrity status of all lines
1225
self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
1226
# integrity check on wizard data - sequence -> no prodlot check as the screen is readonly
1227
weight_check = self.integrity_check_weight(cr, uid, ids, partial_datas_ppl1, context=context)
1228
if not weight_check:
1229
# the windows must be updated to trigger tree colors
1230
return self.pool.get('wizard').open_wizard(cr, uid, picking_ids, type='update', context=context)
615
if not self.integrity_check_weight(cr, uid, ids, partial_datas_ppl1, context=context):
616
raise osv.except_osv(_('Warning !'), _('You must specify a weight for each pack family!'))
1231
617
# call stock_picking method which returns action call
1232
618
return pick_obj.do_ppl2(cr, uid, picking_ids, context=context)