~unifield-team/unifield-wm/us-826

« back to all changes in this revision

Viewing changes to msf_outgoing/wizard/create_picking.py

  • Committer: Quentin THEURET
  • Date: 2011-12-12 08:02:59 UTC
  • mto: This revision was merged to the branch mainline in revision 724.
  • Revision ID: qt@tempo-consulting.fr-20111212080259-oul1f0g37hcpubyc
UF-641 [ADD] Added the empty purchase_followup module

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
from osv import fields, osv
23
23
from tools.translate import _
24
24
import time
25
 
 
26
 
import decimal_precision as dp
27
 
from msf_outgoing import INTEGRITY_STATUS_SELECTION
28
 
 
29
 
 
30
 
class create_picking_processor(osv.osv):
31
 
    """
32
 
    Create picking processing wizard
33
 
    """
34
 
    _name = 'create.picking.processor'
35
 
    _inherit = 'internal.picking.processor'
36
 
    _description = 'Wizard to process the first step of the Pick/Pack/Ship'
37
 
 
38
 
    _columns = {
39
 
        'move_ids': fields.one2many(
40
 
            'create.picking.move.processor',
41
 
            'wizard_id',
42
 
            string='Moves',
43
 
        ),
44
 
    }
45
 
 
46
 
    """
47
 
    Model methods
48
 
    """
49
 
    def do_create_picking(self, cr, uid, ids, context=None):
50
 
        """
51
 
        Made some integrity checks and launch create_picking method of the stock.picking object
52
 
        """
53
 
        # Objects
54
 
        picking_obj = self.pool.get('stock.picking')
55
 
 
56
 
        wizard_brw_list = self.browse(cr, uid, ids, context=context)
57
 
 
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)
62
 
 
63
 
create_picking_processor()
64
 
 
65
 
 
66
 
class create_picking_move_processor(osv.osv):
67
 
    """
68
 
    Create picking moves processing wizard
69
 
    """
70
 
    _name = 'create.picking.move.processor'
71
 
    _inherit = 'internal.move.processor'
72
 
    _description = 'Wizard lines for create picking processor'
73
 
 
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)
76
 
 
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)
79
 
 
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)
82
 
 
83
 
    _columns = {
84
 
        # Parent wizard
85
 
        'wizard_id': fields.many2one(
86
 
            'create.picking.processor',
87
 
            string='Wizard',
88
 
            required=True,
89
 
            readonly=True,
90
 
            select=True,
91
 
            ondelete='cascade',
92
 
        ),
93
 
        'ordered_product_id': fields.function(
94
 
            _get_move_info,
95
 
            method=True,
96
 
            string='Ordered product',
97
 
            type='many2one',
98
 
            relation='product.product',
99
 
            store={
100
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
101
 
            },
102
 
            readonly=True,
103
 
            help="Expected product to receive",
104
 
            multi='move_info',
105
 
        ),
106
 
        'ordered_uom_id': fields.function(
107
 
            _get_move_info,
108
 
            method=True,
109
 
            string='Ordered UoM',
110
 
            type='many2one',
111
 
            relation='product.uom',
112
 
            store={
113
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
114
 
            },
115
 
            readonly=True,
116
 
            help="Expected UoM to receive",
117
 
            multi='move_info',
118
 
        ),
119
 
        'ordered_uom_category': fields.function(
120
 
            _get_move_info,
121
 
            method=True,
122
 
            string='Ordered UoM category',
123
 
            type='many2one',
124
 
            relation='product.uom.categ',
125
 
            store={
126
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
127
 
            },
128
 
            readonly=True,
129
 
            help="Category of the expected UoM to receive",
130
 
            multi='move_info'
131
 
        ),
132
 
        'location_id': fields.function(
133
 
            _get_move_info,
134
 
            method=True,
135
 
            string='Location',
136
 
            type='many2one',
137
 
            relation='stock.location',
138
 
            store={
139
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
140
 
            },
141
 
            readonly=True,
142
 
            help="Source location of the move",
143
 
            multi='move_info'
144
 
        ),
145
 
        'location_supplier_customer_mem_out': fields.function(
146
 
            _get_move_info,
147
 
            method=True,
148
 
            string='Location Supplier Customer',
149
 
            type='boolean',
150
 
            store={
151
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
152
 
            },
153
 
            readonly=True,
154
 
            multi='move_info',
155
 
            help="",
156
 
        ),
157
 
        'integrity_status': fields.function(
158
 
            _get_integrity_status,
159
 
            method=True,
160
 
            string='',
161
 
            type='selection',
162
 
            selection=INTEGRITY_STATUS_SELECTION,
163
 
            store={
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'],
167
 
                    20
168
 
                ),
169
 
            },
170
 
            readonly=True,
171
 
            help="Integrity status (e.g: check if a batch is set for a line with a batch mandatory product...)",
172
 
        ),
173
 
        'type_check': fields.function(
174
 
            _get_move_info,
175
 
            method=True,
176
 
            string='Picking Type Check',
177
 
            type='char',
178
 
            size=32,
179
 
            store={
180
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['move_id'], 20),
181
 
            },
182
 
            readonly=True,
183
 
            help="Return the type of the picking",
184
 
            multi='move_info',
185
 
        ),
186
 
        'lot_check': fields.function(
187
 
            _get_product_info,
188
 
            method=True,
189
 
            string='B.Num',
190
 
            type='boolean',
191
 
            store={
192
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
193
 
            },
194
 
            readonly=True,
195
 
            multi='product_info',
196
 
            help="A batch number is required on this line",
197
 
        ),
198
 
        'exp_check': fields.function(
199
 
            _get_product_info,
200
 
            method=True,
201
 
            string='Exp.',
202
 
            type='boolean',
203
 
            store={
204
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
205
 
            },
206
 
            readonly=True,
207
 
            multi='product_info',
208
 
            help="An expiry date is required on this line",
209
 
        ),
210
 
        'asset_check': fields.function(
211
 
            _get_product_info,
212
 
            method=True,
213
 
            string='Asset',
214
 
            type='boolean',
215
 
            store={
216
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
217
 
            },
218
 
            readonly=True,
219
 
            multi='product_info',
220
 
            help="An asset is required on this line",
221
 
        ),
222
 
        'kit_check': fields.function(
223
 
            _get_product_info,
224
 
            method=True,
225
 
            string='Kit',
226
 
            type='boolean',
227
 
            store={
228
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
229
 
            },
230
 
            readonly=True,
231
 
            multi='product_info',
232
 
            help="A kit is required on this line",
233
 
        ),
234
 
        'kc_check': fields.function(
235
 
            _get_product_info,
236
 
            method=True,
237
 
            string='KC',
238
 
            type='boolean',
239
 
            store={
240
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
241
 
            },
242
 
            readonly=True,
243
 
            multi='product_info',
244
 
            help="Ticked if the product is a Heat Sensitive Item",
245
 
        ),
246
 
        'ssl_check': fields.function(
247
 
            _get_product_info,
248
 
            method=True,
249
 
            string='SSL',
250
 
            type='boolean',
251
 
            store={
252
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
253
 
            },
254
 
            readonly=True,
255
 
            multi='product_info',
256
 
            help="Ticked if the product is a Short Shelf Life product",
257
 
        ),
258
 
        'dg_check': fields.function(
259
 
            _get_product_info,
260
 
            method=True,
261
 
            string='DG',
262
 
            type='boolean',
263
 
            store={
264
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
265
 
            },
266
 
            readonly=True,
267
 
            multi='product_info',
268
 
            help="Ticked if the product is a Dangerous Good",
269
 
        ),
270
 
        'np_check': fields.function(
271
 
            _get_product_info,
272
 
            method=True,
273
 
            string='NP',
274
 
            type='boolean',
275
 
            store={
276
 
                'create.picking.move.processor': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20),
277
 
            },
278
 
            readonly=True,
279
 
            multi='product_info',
280
 
            help="Ticked if the product is a Narcotic",
281
 
        ),
282
 
    }
283
 
 
284
 
create_picking_move_processor()
285
 
 
 
25
import netsvc
286
26
 
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')
296
36
     }
297
 
 
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)
313
 
 
 
37
    
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.
322
46
        """
323
47
        if context is None:
324
48
            context = {}
325
 
 
 
49
        
326
50
        # we need the step info
327
51
        assert 'step' in context, 'Step not defined in context'
328
52
        step = context['step']
329
 
 
 
53
        
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:
334
58
            return res
335
 
 
 
59
        
336
60
        result = []
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))
347
 
 
 
71
                
348
72
        if 'product_moves_picking' in fields and step in ('create', 'validate'):
349
73
            res.update({'product_moves_picking': result})
350
 
 
 
74
            
351
75
        if 'product_moves_ppl' in fields and step in ('ppl1'):
352
76
            res.update({'product_moves_ppl': result})
353
 
 
 
77
            
354
78
        if 'product_moves_returnproducts' in fields and step in ('returnproducts'):
355
79
            res.update({'product_moves_returnproducts': result})
356
 
 
 
80
            
357
81
        if 'product_moves_families' in fields and step in ('ppl2'):
358
82
            res.update({'product_moves_families': result})
359
 
 
 
83
            
360
84
        if 'date' in fields:
361
85
            res.update({'date': time.strftime('%Y-%m-%d %H:%M:%S')})
362
 
 
 
86
            
363
87
        return res
364
 
 
365
 
    def __create_partial_picking_memory(self, pick, step, context=None):
 
88
    
 
89
    def __create_partial_picking_memory(self, pick, context=None):
366
90
        '''
367
91
        generates the memory objects data depending on wizard step
368
 
 
 
92
        
369
93
        - wizard_id seems to be filled automatically
370
94
        '''
371
95
        assert context, 'No context defined'
372
96
        assert 'step' in context, 'No step defined in context'
373
97
        step = context['step']
374
 
 
 
98
        
375
99
        # list for the current pick object
376
100
        result = []
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'):
379
103
                continue
380
104
            move_memory = {
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,
392
113
                }
393
 
            if step == 'ppl1':
394
 
                move_memory['quantity'] = move.product_qty
395
 
 
 
114
            
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)
401
 
 
 
120
        
402
121
        # return the list of dictionaries
403
122
        return result
404
 
 
 
123
    
405
124
    def __create_pack_families_memory(self, pick, context=None):
406
125
        '''
407
126
        generates the memory objects data depending on wizard step
408
 
 
 
127
        
409
128
        - wizard_id seems to be filled automatically
410
129
        '''
411
130
        assert context, 'No context defined'
413
132
        step = context['step']
414
133
        assert 'partial_datas_ppl1' in context, 'No partial data from step1'
415
134
        partial_datas_ppl1 = context['partial_datas_ppl1']
416
 
 
 
135
        
417
136
        # list for the current pick object
418
137
        result = []
419
138
        from_packs = partial_datas_ppl1[pick.id].keys()
423
142
            for to_pack in partial_datas_ppl1[pick.id][from_pack]:
424
143
                family_memory = {
425
144
                                 'from_pack': from_pack,
426
 
                                 'to_pack': to_pack, }
427
 
 
 
145
                                 'to_pack': to_pack,}
 
146
            
428
147
                # append the created dict
429
148
                result.append(family_memory)
430
 
 
 
149
        
431
150
        # return the list of dictionaries
432
151
        return result
433
 
 
 
152
    
434
153
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
435
154
        '''
436
155
        generates the xml view
448
167
        if not picking_ids:
449
168
            # not called through an action (e.g. buildbot), return the default.
450
169
            return result
451
 
 
 
170
        
452
171
        # get picking subtype
453
172
        for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
454
173
            picking_subtype = pick.subtype
455
 
 
 
174
            
456
175
        # select field to display
457
176
        if picking_subtype == 'picking':
458
177
            field = 'picking'
463
182
                field = 'families'
464
183
            elif step == 'returnproducts':
465
184
                field = 'returnproducts'
466
 
 
467
 
        _moves_arch_lst = """<form string="%s">""" % (_('Process Document'),)
468
 
 
469
 
        if step in ['create', 'validate', 'returnproducts']:
470
 
            _moves_arch_lst += """
471
 
                <button name="select_all" string="%s"
472
 
                    colspan="1" type="object"  icon="gtk-jump-to" />
473
 
                <button name="deselect_all" string="%s"
474
 
                    colspan="1" type="object" icon="gtk-undo" />""" % (_('Copy all'), _('Clear all'))
475
 
 
476
 
        _moves_arch_lst += """
477
 
                <field name="date" invisible="1"/>
478
 
                <separator colspan="4" string="%s"/>
479
 
                <field name="product_moves_%s" colspan="4" nolabel="1" mode="tree,form"></field>
480
 
                """ % (_('Products'), field)
481
 
 
 
185
        
 
186
        _moves_arch_lst = """<form string="%s">
 
187
                        <field name="date" invisible="1"/>
 
188
                        <separator colspan="4" string="%s"/>
 
189
                        <field name="product_moves_%s" colspan="4" nolabel="1" mode="tree,form"></field>
 
190
                        """ % (_('Process Document'), _('Products'), field)
482
191
        _moves_fields = result['fields']
483
192
 
484
193
        # add field related to picking type only
485
194
        _moves_fields.update({
486
 
                            'product_moves_' + field: {'relation': 'stock.move.memory.' + field, 'type' : 'one2many', 'string' : 'Product Moves'},
 
195
                            'product_moves_' + field: {'relation': 'stock.move.memory.' + field, 'type' : 'one2many', 'string' : 'Product Moves'}, 
487
196
                            })
488
197
 
489
198
        # specify the button according to the screen
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'))
505
 
 
 
213
                button = ('do_return_products', 'Return')
 
214
                    
506
215
        else:
507
216
            button = ('undefined', 'Undefined')
508
 
 
 
217
                
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')
515
 
 
 
223
                    string="_Cancel" />"""
 
224
                    
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')
520
 
 
521
 
 
 
227
                <button name="back_ppl1" string="previous"
 
228
                    colspan="1" type="object" icon="gtk-go-back" />"""
 
229
                    
 
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" />"""
 
236
                    
522
237
        _moves_arch_lst += """
523
238
                <button name="%s" string="%s"
524
239
                    colspan="1" type="object" icon="gtk-go-forward" />
525
240
            </group>
526
 
        </form>""" % button
527
 
 
 
241
        </form>"""%button
 
242
        
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)
532
247
        return result
533
 
 
 
248
    
534
249
    def select_all(self, cr, uid, ids, context=None):
535
250
        '''
536
251
        select all buttons, write max qty in each line
537
 
 
538
 
        should be modified for more generic way, with something like:
539
 
 
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)
546
 
                for line in lines:
547
 
                    line.write({'quantity': line.initial_qty}, context=context)
548
 
 
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.
552
252
        '''
553
 
        # picking ids
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)
564
 
 
 
259
                line.write({'qty_to_return':line.quantity,}, context=context)
 
260
        
 
261
        return {
 
262
                'name': context.get('wizard_name'),
 
263
                'view_mode': 'form',
 
264
                'view_id': False,
 
265
                'view_type': 'form',
 
266
                'res_model': context.get('model'),
 
267
                'res_id': ids[0],
 
268
                'type': 'ir.actions.act_window',
 
269
                'nodestroy': True,
 
270
                'target': 'new',
 
271
                'domain': '[]',
 
272
                'context': context,
 
273
                }
 
274
        
565
275
    def deselect_all(self, cr, uid, ids, context=None):
566
276
        '''
567
277
        deselect all buttons, write 0 qty in each line
568
278
        '''
569
 
        # picking ids
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)
578
 
 
 
283
                line.write({'qty_to_return':0.0,}, context=context)
 
284
        
 
285
        return {
 
286
                'name': context.get('wizard_name'),
 
287
                'view_mode': 'form',
 
288
                'view_id': False,
 
289
                'view_type': 'form',
 
290
                'res_model': context.get('model'),
 
291
                'res_id': ids[0],
 
292
                'type': 'ir.actions.act_window',
 
293
                'nodestroy': True,
 
294
                'target': 'new',
 
295
                'domain': '[]',
 
296
                'context': context,
 
297
                }
 
298
    
579
299
    def generate_data_from_partial(self, cr, uid, ids, context=None):
580
300
        '''
581
301
        data is located in product_moves_ppl
582
 
 
 
302
        
583
303
        we generate the data structure from the first ppl wizard (ppl1)
584
 
 
 
304
        
585
305
        structure :
586
306
        {pick_id: {from_pack: {to_pack: {move_id: [{partial},]}}}}
587
 
 
 
307
        
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.
591
 
 
 
311
        
592
312
        with partial beeing the info for one stock.move.memory.ppl
593
313
        '''
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'
597
 
 
 
317
        
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)
601
321
        # returned datas
602
322
        partial_datas_ppl1 = {}
603
 
 
 
323
        
604
324
        # picking ids
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,
628
346
                                                             })
629
 
 
 
347
                
630
348
        return partial_datas_ppl1
631
 
 
 
349
    
632
350
    def update_data_from_partial(self, cr, uid, ids, context=None):
633
351
        '''
634
352
        update the list corresponding to moves for each sequence with ppl2 information
635
 
 
 
353
        
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
638
 
 
 
356
        
639
357
        structure :
640
358
        {pick_id: {from_pack: {to_pack: {move_id: [{partial},]}}}}
641
359
        '''
642
360
        assert context, 'no context defined'
643
361
        assert 'partial_datas_ppl1' in context, 'partial_datas_ppl1 not in context'
 
362
        
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
649
368
        memory_families_list = partial.product_moves_families
650
369
        # returned datas
651
370
        partial_datas_ppl1 = context['partial_datas_ppl1']
 
371
        
652
372
        # picking ids
653
373
        picking_ids = context['active_ids']
654
374
        for picking_id in picking_ids:
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]
663
383
                    # remove id key
664
 
                    family['memory_move_id'] = family.pop('id')
 
384
                    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)
668
 
 
669
 
    def set_integrity_status(self, cr, uid, ids, field_name, status='empty', context=None):
670
 
        '''
671
 
        for all moves set the status to ok (default value) or other if specified
672
 
        '''
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)
676
 
 
677
 
    def set_integrity_status_for_empty_moves(self, cr, uid, ids, field_name, status='empty_picking', context=None):
678
 
        '''
679
 
        for all moves set the status to empty_picking if move qty is 0
680
 
 
681
 
        deprecated - not used, by default the validation is empty not ok
682
 
        '''
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)
687
 
 
688
 
    def integrity_check_create_picking(self, cr, uid, ids, data, context=None):
689
 
        '''
690
 
        integrity check on create picking data
691
 
        - rule #1: no negative values (<0)
692
 
        - rule #2: at least one positive value (>0)
693
 
 
694
 
        return True/False
695
 
        '''
696
 
        memory_move_obj = self.pool.get('stock.move.memory.picking')
697
 
        # validate the data
698
 
        for picking_data in data.values():
699
 
            # total sum not including negative values
700
 
            sum_qty = 0
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)
711
 
                    else:
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:
716
 
                return False
717
 
        return True
718
 
 
719
 
    def integrity_check_prodlot(self, cr, uid, ids, data, validate=True, context=None):
720
 
        '''
721
 
        check production lot
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
727
 
 
728
 
        - the production lot is mandatory only if it is the validation stage
729
 
        '''
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
735
 
        missing_lot = False
736
 
        # has prodlot but should not
737
 
        lot_not_needed = False
738
 
        # wrong production lot type
739
 
        wrong_lot_type = False
740
 
        # validate the data
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)
766
 
 
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
776
 
 
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))
783
 
 
784
 
                    # only mandatory at validation stage
785
 
                    elif validate:
786
 
                        # no production lot defined, corresponding checks
787
 
                        # rule #1 a batch management product needs a standard production lot
788
 
                        if prod.batch_management:
789
 
                            missing_lot = True
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:
793
 
                            missing_lot = True
794
 
                            memory_move_obj.write(cr, uid, [list_data['memory_move_id']], {'integrity_status': 'missing_date', }, context=context)
795
 
 
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, \
805
 
                            prodlot_qty))
806
 
 
807
 
        # if error, return False
808
 
        if missing_lot or lot_not_needed or wrong_lot_type:
809
 
            return False
810
 
        return True
811
 
 
812
 
    def do_create_picking_first_hook(self, cr, uid, context, *args, **kwargs):
813
 
        '''
814
 
        add hook to do_create_picking: This hook's first aim was to complete the module msf_cross_docking
815
 
        '''
816
 
        partial_datas = kwargs.get('partial_datas')
817
 
        assert partial_datas, 'partial_datas missing'
818
 
 
819
 
        return partial_datas
820
 
 
 
388
        
821
389
    def do_create_picking(self, cr, uid, ids, context=None):
822
390
        '''
823
391
        create the picking ticket from selected stock moves
824
392
        -> only related to 'out' type stock.picking
825
 
 
 
393
        
826
394
        - transform data from wizard
827
395
        '''
828
396
        # integrity check
832
400
        picking_ids = context['active_ids']
833
401
        # partial data from wizard
834
402
        partial = self.browse(cr, uid, ids[0], context=context)
835
 
        # name of the wizard field for moves (one2many)
836
 
        field_name = 'product_moves_picking'
837
 
 
 
403
        
838
404
        pick_obj = self.pool.get('stock.picking')
839
405
        move_obj = self.pool.get('stock.move')
 
406
 
840
407
        # partial datas
841
408
        partial_datas = {}
842
 
 
 
409
        
843
410
        for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
844
 
            total_qty = 0
845
411
            # for each picking
846
412
            partial_datas[pick.id] = {}
847
413
            # out moves for delivery
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,
863
424
                                                                                   })
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))
879
 
 
 
427
        
880
428
    def quick_mode(self, cr, uid, ppl, context=None):
881
429
        '''
882
430
        we do the quick mode, the ppl step is performed automatically
883
431
        '''
884
432
        assert context, 'missing Context'
885
 
 
 
433
        
886
434
        moves_ppl_obj = self.pool.get('stock.move.memory.ppl')
887
 
 
 
435
        
888
436
        # set the corresponding ppl object
889
437
        context['active_ids'] = [ppl.id]
890
 
 
 
438
        
891
439
        # set the step
892
440
        context['step'] = 'ppl1'
893
441
        # create a create_picking object for ppl1
895
443
        # the default user values are used, they represent all packs in one pack sequence (pack family from:1, to:1)
896
444
        # with a quantity per pack equal to the quantity
897
445
        # these values are set in the create method of memory moves
898
 
 
 
446
        
899
447
        # ppl1
900
448
        # the wizard for ppl2 step is created here, the step is updated there also
901
449
        wizard_dic = self.do_ppl1(cr, uid, [wizard_ppl1], context=context)
902
450
        partial_datas_ppl1 = wizard_dic['context']['partial_datas_ppl1']
903
451
        wizard_ppl2 = wizard_dic['res_id']
904
452
        # the default user values are used, all the pack families values are set to zero, False (weight, height, ...)
905
 
 
 
453
        
906
454
        # ppl2
907
455
        self.do_ppl2(cr, uid, [wizard_ppl2], context=dict(context, partial_datas_ppl1=partial_datas_ppl1))
908
 
 
909
 
    def do_validate_picking_first_hook(self, cr, uid, context, *args, **kwargs):
910
 
        '''
911
 
        add hook to do_validate_picking: This hook's first aim was to complete the module msf_cross_docking
912
 
        '''
913
 
        partial_datas = kwargs.get('partial_datas')
914
 
        assert partial_datas, 'partial_datas missing'
915
 
 
916
 
        return partial_datas
917
 
 
 
456
        
918
457
    def do_validate_picking(self, cr, uid, ids, context=None):
919
458
        '''
920
459
        create the picking ticket from selected stock moves
921
460
        -> only related to 'out' type stock.picking
922
 
 
 
461
        
923
462
        - transform data from wizard
924
463
        '''
925
464
        # integrity check
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'
934
 
 
 
471
        
935
472
        pick_obj = self.pool.get('stock.picking')
936
473
        move_obj = self.pool.get('stock.move')
937
 
 
 
474
        
938
475
        # partial datas
939
476
        partial_datas = {}
940
 
 
 
477
        
941
478
        for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
942
 
            total_qty = 0.00
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,
 
485
                
 
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,
959
491
                                                                               })
960
 
                # override : add hook call
961
 
                partial_datas = self.do_validate_picking_first_hook(cr, uid, context, move=move, partial_datas=partial_datas)
962
 
            if not total_qty:
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)
 
492
            
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))
975
 
 
976
 
    def integrity_check_return_products(self, cr, uid, ids, data, context=None):
977
 
        '''
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
982
 
 
983
 
        return True/Fals
984
 
        '''
985
 
        memory_move_obj = self.pool.get('stock.move.memory.returnproducts')
986
 
        # validate the data
 
495
    
 
496
    def integrity_check(self, cr, uid, ids, data, context=None):
 
497
        '''
 
498
        integrity check on shipment data
 
499
        '''
987
500
        for picking_data in data.values():
988
 
            # total sum not including negative values
989
 
            sum_qty = 0
990
 
            # flag to detect negative values
991
 
            negative_value = False
992
 
            # flag to detect excessive return quantity
993
 
            too_much = False
994
501
            for move_data in picking_data.values():
995
 
                # quantity check
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
1003
 
                    too_much = True
1004
 
                    memory_move_obj.write(cr, uid, [move_data['memory_move_id']], {'integrity_status': 'return_qty_too_much', }, context=context)
1005
 
                else:
1006
 
                    sum_qty += move_data['qty_to_return']
1007
 
            # if error, return False
1008
 
            if not sum_qty or negative_value or too_much:
1009
 
                return False
1010
 
        return True
1011
 
 
 
502
                if move_data.get('qty_to_return', False):
 
503
                    return True
 
504
        
 
505
        return False
 
506
    
1012
507
    def do_return_products(self, cr, uid, ids, context=None):
1013
508
        '''
1014
509
        process data and call do_return_products from stock picking
1015
 
 
 
510
        
1016
511
        data structure:
1017
512
        {picking_id: {move_id: {data}}}
1018
513
        '''
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'
1022
 
 
 
517
        
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'
1030
 
 
 
523
        
1031
524
        # picking ids
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,
1050
540
                                                               }
1051
 
 
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)
 
541
                    
 
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!'))
 
545
        
1059
546
        return pick_obj.do_return_products(cr, uid, picking_ids, context=dict(context, partial_datas=partial_datas))
1060
 
 
1061
 
    def integrity_check_sequences(self, cr, uid, ids, data, context=None):
1062
 
        '''
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
1068
 
 
1069
 
        return True/False
1070
 
 
1071
 
        {145: {1: {1: {
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}]
1074
 
        }}}}
1075
 
        '''
1076
 
        memory_move_obj = self.pool.get('stock.move.memory.ppl')
1077
 
        # validate the data
1078
 
        for picking_data in data.values():
1079
 
            # list of sequences for each picking
1080
 
            sequences = []
1081
 
            # flag for detecting missing 1 initial from
1082
 
            missing_1 = False
1083
 
            # flag for detecting to value smaller than from value
1084
 
            to_samller_than_from = False
1085
 
            # flag for detecting overlapping sequences
1086
 
            overlap = False
1087
 
            # flag for detecting gap
1088
 
            gap = False
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
1098
 
            if not sequences:
1099
 
                return False
1100
 
            # sort the sequences according to from value
1101
 
            sequences = sorted(sequences, key=lambda seq: seq[0])
1102
 
 
1103
 
            # rule #1, the first from value must be equal to 1
1104
 
            if sequences[0][0] != 1:
1105
 
                missing_1 = True
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)):
1109
 
                seq = sequences[i]
1110
 
                # rules 2-3 applies from second element
1111
 
                if i > 0:
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]):
1116
 
                        overlap = True
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:
1121
 
                            overlap = True
1122
 
                            memory_move_obj.write(cr, uid, [seq[2]], {'integrity_status': 'overlap', }, context=context)
1123
 
                        if seq[0] > seqb[1] + 1:
1124
 
                            gap = True
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:
1132
 
                return False
1133
 
        return True
1134
 
 
 
547
        
1135
548
    def do_ppl1(self, cr, uid, ids, context=None):
1136
549
        '''
1137
550
        - generate data
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'
1143
 
        # objects
 
556
        
1144
557
        pick_obj = self.pool.get('stock.picking')
1145
 
        # name of the wizard field for moves (one2many)
1146
 
        field_name = 'product_moves_ppl'
1147
 
 
1148
558
        # picking ids
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)
1152
 
 
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)
1160
 
 
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))
1163
 
 
 
564
    
1164
565
    def back_ppl1(self, cr, uid, ids, context=None):
1165
566
        '''
1166
567
        call back ppl1 step wizard
1167
568
        '''
1168
569
        # we need the context for the wizard switch
1169
570
        assert context, 'no context defined'
1170
 
 
 
571
        
1171
572
        wiz_obj = self.pool.get('wizard')
1172
 
 
 
573
        
1173
574
        # no data for type 'back'
1174
575
        return wiz_obj.open_wizard(cr, uid, context['active_ids'], type='back', context=context)
1175
 
 
 
576
    
1176
577
    def integrity_check_weight(self, cr, uid, ids, data, context=None):
1177
578
        '''
1178
 
        integrity check on ppl2 data for weight validation
1179
 
        - weight must exist if not quick flow type
1180
 
        return True/False
1181
 
 
1182
 
        {145: {1: {1: {
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}]
1185
 
        }}}}
 
579
        integrity check on ppl2 data for weight
 
580
        
 
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}]}}}}
1186
584
        '''
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
1203
 
        if missing_weight:
1204
 
            return False
 
595
                                    return False
 
596
        
1205
597
        return True
1206
 
 
 
598
        
1207
599
    def do_ppl2(self, cr, uid, ids, context=None):
1208
600
        '''
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'
 
607
        
1215
608
        pick_obj = self.pool.get('stock.picking')
1216
 
        # name of the wizard field for moves (one2many)
1217
 
        field_name = 'product_moves_families'
1218
609
        # picking ids
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)
1233
619