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

« back to all changes in this revision

Viewing changes to msf_outgoing/wizard/shipment.py

  • Committer: Olivier DOSSMANN
  • Date: 2014-03-31 09:31:46 UTC
  • mto: This revision was merged to the branch mainline in revision 2086.
  • Revision ID: od@tempo-consulting.fr-20140331093146-tgvxnly1kc1hbv1s
UF-2171 [ADD] Analytic distribution reset button for recurring models

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
#
21
21
##############################################################################
22
22
 
23
 
from osv import fields, osv
 
23
from osv import osv
 
24
from osv import fields
24
25
from tools.translate import _
25
26
import time
26
 
import netsvc
 
27
 
 
28
 
27
29
 
28
30
class shipment_wizard(osv.osv_memory):
29
31
    _name = "shipment.wizard"
37
39
        'product_moves_shipment_create' : fields.one2many('stock.move.memory.shipment.create', 'wizard_id', 'Pack Families'),
38
40
        'product_moves_shipment_returnpacks' : fields.one2many('stock.move.memory.shipment.returnpacks', 'wizard_id', 'Pack Families'),
39
41
        'product_moves_shipment_returnpacksfromshipment' : fields.one2many('stock.move.memory.shipment.returnpacksfromshipment', 'wizard_id', 'Pack Families'),
 
42
        'product_moves_shipment_additionalitems' : fields.one2many('stock.move.memory.shipment.additionalitems', 'wizard_id', 'Additional Items'),
40
43
     }
 
44
#     todo
 
45
#    generic select all deselcted all based on fields_get
41
46
    
42
47
    def select_all(self, cr, uid, ids, context=None):
43
48
        '''
199
204
        step = context['step']
200
205
        
201
206
        _moves_arch_lst = """<form string="%s">
 
207
 
 
208
                        <button name="select_all" string="%s"
 
209
                            colspan="1" type="object" icon="gtk-jump-to" />
 
210
                        <button name="deselect_all" string="%s"
 
211
                            colspan="1" type="object"  icon="gtk-undo"/>
 
212
 
202
213
                        <field name="date" invisible="1"/>
203
214
                        <separator colspan="4" string="%s"/>
204
215
                        <field name="product_moves_shipment_%s" colspan="4" nolabel="1" mode="tree,form"></field>
205
 
                        """ % (_('Process Document'), _('Products'), step)
 
216
                        """ % (_('Process Document'),_('Copy all'), _('Clear all'),  _('Products'), step)
206
217
        _moves_fields = result['fields']
207
218
 
208
219
        # add field related to picking type only
212
223
 
213
224
        # specify the button according to the screen
214
225
        if step == 'create':
215
 
            button = ('do_create_shipment', 'Create Shipment')
 
226
            button = ('do_create_shipment', _('Create Shipment'))
216
227
            
217
228
        elif step == 'returnpacks':
218
 
            button = ('do_return_packs', 'Return Packs')
 
229
            button = ('do_return_packs', _('Return Packs'))
219
230
            
220
231
        elif step == 'returnpacksfromshipment':
221
 
            button = ('do_return_packs_from_shipment', 'Return Packs from Shipment')
 
232
            button = ('do_return_packs_from_shipment', _('Return Packs from Shipment'))
222
233
            
223
234
        else:
224
235
            button = ('undefined', 'Undefined: %s'%step)
229
240
                <group col="4" colspan="2">
230
241
                <button icon='gtk-cancel' special="cancel"
231
242
                    string="_Cancel" />
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" />
 
243
 
 
244
 
 
245
 
236
246
                <button name="%s" string="%s"
237
247
                    colspan="1" type="object" icon="gtk-go-forward" />
238
 
            </group>
239
 
        </form>"""%button
 
248
            </group>"""%button
 
249
        if step == 'create':
 
250
            _moves_fields.update({
 
251
                                'product_moves_shipment_additionalitems': {'relation': 'stock.move.memory.shipment.additionalitems', 'type' : 'one2many', 'string' : 'Additional Items'}, 
 
252
                                })
 
253
            _moves_arch_lst += """
 
254
            <field name="product_moves_shipment_additionalitems" colspan="4" nolabel="1" mode="tree,form" string="Additional Items"></field>
 
255
            """
 
256
        _moves_arch_lst += """</form>"""
240
257
        
241
258
        result['arch'] = _moves_arch_lst
242
259
        result['fields'] = _moves_fields
252
269
        
253
270
        structure :
254
271
        {shipment_id: {draft_packing_id: {from_pack: {to_pack: {[partial,]}]}}}}
 
272
        
 
273
        fields:
 
274
        {'selected_weight': {'function': '_vals_get', 'digits': (16, 2), 'fnct_inv': False, 'string': 'Selected Weight [kg]', 'fnct_inv_arg': False, 'readonly': 1, 'fnct_search': False, 'func_obj': False, 'type': 'float', 'store': False, 'func_method': True},
 
275
        'weight': {'digits': (16, 2), 'selectable': True, 'type': 'float', 'string': 'Weight p.p [kg]'},
 
276
        'pack_type': {'domain': [], 'string': 'Pack Type', 'relation': 'pack.type', 'context': {}, 'selectable': True, 'type': 'many2one'},
 
277
        'ppl_id': {'domain': [], 'string': 'PPL Ref', 'relation': 'stock.picking', 'context': {}, 'selectable': True, 'type': 'many2one'},
 
278
        'draft_packing_id': {'domain': [], 'string': 'Draft Packing Ref', 'relation': 'stock.picking', 'context': {}, 'selectable': True, 'type': 'many2one'},
 
279
        'wizard_id': {'domain': [], 'string': 'Wizard', 'relation': 'stock.partial.move', 'context': {}, 'selectable': True, 'type': 'many2one'},
 
280
        'height': {'digits': (16, 2), 'selectable': True, 'type': 'float', 'string': 'Height [cm]'},
 
281
        'from_pack': {'selectable': True, 'type': 'integer', 'string': 'From p.'},
 
282
        'length': {'digits': (16, 2), 'selectable': True, 'type': 'float', 'string': 'Length [cm]'},
 
283
        'to_pack': {'selectable': True, 'type': 'integer', 'string': 'To p.'},
 
284
        'integrity_status': {'selectable': True, 'readonly': True, 'selection': [('empty', ''), ('ok', u'Ok'), ('negative', u'Negative Value'), ('missing_lot', u'Production Lot is Missing'), ('missing_date', u'Expiry Date is Missing'), ('no_lot_needed', u'No Production Lot/Expiry Date Needed'), ('wrong_lot_type', u'Wrong Production Lot Type'), ('wrong_lot_type_need_internal', u'Need Expiry Date (Internal) not Production Lot (Standard)'), ('wrong_lot_type_need_standard', u'Need Production Lot (Standard) not Expiry Date (Internal)'), ('empty_picking', u'Empty Picking Ticket'), ('missing_1', u'The first sequence must start with 1'), ('to_smaller_than_from', u'To value must be greater or equal to From value'), ('overlap', u'The sequence overlaps previous one'), ('gap', u'A gap exist in the sequence'), ('missing_weight', u'Weight is Missing')], 'type': 'selection', 'string': ' '},
 
285
        'num_of_packs': {'function': '_vals_get', 'digits': (16, 2), 'fnct_inv': False, 'string': '#Packs', 'fnct_inv_arg': False, 'readonly': 1, 'fnct_search': False, 'func_obj': False, 'type': 'integer', 'store': False, 'func_method': True},
 
286
        'selected_number': {'selectable': True, 'type': 'integer', 'string': 'Selected Number'},
 
287
        'width': {'digits': (16, 2), 'selectable': True, 'type': 'float', 'string': 'Width [cm]'},
 
288
        'sale_order_id': {'domain': [], 'string': 'Sale Order Ref', 'relation': 'sale.order', 'context': {}, 'selectable': True, 'type': 'many2one'}}
255
289
        '''
256
290
        # integrity check
257
291
        assert context, 'no context, method call is wrong'
281
315
            for memory_move in pack_family_list:
282
316
                # all returns True if applied on an empty list
283
317
                # only take into account if packs have been selected
284
 
                if all([getattr(memory_move, cond) for cond in conditions]):
 
318
                if all([not getattr(memory_move, cond) for cond in conditions]):
 
319
                    # all conditions are True, the data is not taken into account
 
320
                    pass
 
321
                else:
285
322
                    # retrieve fields from object
286
323
                    fields = memory_move_obj.fields_get(cr, uid, context=context)
287
324
                    values = {}
 
325
                    # add the id of memory move
 
326
                    values.update({'memory_move_id': memory_move.id})
288
327
                    for key in fields.keys():
289
328
                        type= fields[key]['type']
290
329
                        if type not in ('one2many', 'many2one', 'one2one'):
295
334
                        else:
296
335
                            assert False, 'copy of %s value is not implemented'%type
297
336
 
 
337
                    # openerp bug, function with int type returns a string
 
338
                    if isinstance(values['num_of_packs'], str):
 
339
                        values['num_of_packs'] = int(values['num_of_packs'])
 
340
 
298
341
                    partial_datas_shipment[ship.id] \
299
342
                        .setdefault(memory_move.draft_packing_id.id, {}) \
300
343
                        .setdefault(memory_move.from_pack, {}) \
302
345
                
303
346
        return partial_datas_shipment
304
347
    
305
 
    def integrity_check_create_shipment(self, cr, uid, ids, data, context=None):
306
 
        '''
307
 
        integrity check on shipment data
308
 
        '''
 
348
    def integrity_check_packs(self, cr, uid, ids, data, model_name, context=None):
 
349
        '''
 
350
        integrity check on create shipment data
 
351
        - #1 no negative values (<0)
 
352
        - #2 at least one positive one (>0)
 
353
        - #3 no more than available quantity #packs
 
354
        
 
355
        {12: {176: {1: {1: [{'selected_weight': 0.0, 'weight': 0.0, 'pack_type': False, 'ppl_id': 175, 'draft_packing_id': 176, 'wizard_id': 1, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': '1', 'selected_number': 1, 'width': 0.0, 'sale_order_id': False}]}}}}
 
356
        
 
357
        return True/False
 
358
        '''
 
359
        memory_move_obj = self.pool.get(model_name)
 
360
        # validate the data
309
361
        for shipment_data in data.values():
 
362
            # total sum not including negative values
 
363
            sum_qty = 0
 
364
            # flag to detect negative values
 
365
            negative_value = False
 
366
            # flag to detect excessive return quantity
 
367
            too_much = False
310
368
            for packing_data in shipment_data.values():
311
 
                for from_pack_data in packing_data.values():
312
 
                    for to_pack_data in from_pack_data.values():
313
 
                        for partial in to_pack_data:
314
 
                            if partial.get('selected_number', False):
315
 
                                return True
316
 
        
317
 
        return False
 
369
                for from_data in packing_data.values():
 
370
                    for to_data in from_data.values():
 
371
                        for partial in to_data:
 
372
                            # quantity check
 
373
                            if partial['selected_number'] < 0.0:
 
374
                                # a negative value has been selected, update the memory line
 
375
                                # update the new value for integrity check with 'negative' value (selection field)
 
376
                                negative_value = True
 
377
                                memory_move_obj.write(cr, uid, [partial['memory_move_id']], {'integrity_status': 'negative'}, context=context)
 
378
                            elif partial['selected_number'] > int(partial['num_of_packs']):
 
379
                                # cannot return more products than available
 
380
                                too_much = True
 
381
                                memory_move_obj.write(cr, uid, [partial['memory_move_id']], {'integrity_status': 'return_qty_too_much',}, context=context)
 
382
                            else:
 
383
                                sum_qty += partial['selected_number']
 
384
                            
 
385
            # if error, return False
 
386
            if not sum_qty or negative_value or too_much:
 
387
                return False
 
388
        return True
 
389
    
 
390
    def set_integrity_status(self, cr, uid, ids, field_name, status='empty', context=None):
 
391
        '''
 
392
        for all moves set the status to ok (default value) or other if specified
 
393
        '''
 
394
        for wiz in self.browse(cr, uid, ids, context=context):
 
395
            for memory_move in getattr(wiz, field_name):
 
396
                memory_move.write({'integrity_status': status,}, context=context)
 
397
    
 
398
    def create_additionalitems(self, cr, uid, ids, context=None):
 
399
        shipment_ids = context['active_ids']
 
400
        additional_items_dict = {'additional_items_ids': []}
 
401
        for shipment_wizard in self.read(cr, uid, ids, ['product_moves_shipment_additionalitems'], context):
 
402
            additionalitems_ids = shipment_wizard['product_moves_shipment_additionalitems']
 
403
            for additionalitem in self.pool.get('stock.move.memory.shipment.additionalitems').read(cr, uid, additionalitems_ids):
 
404
                additionalitem.pop('wizard_id')
 
405
                additionalitem['picking_id'] = additionalitem.get('picking_id', False) and additionalitem.get('picking_id', False)[0]
 
406
                uom = additionalitem.get('uom', False)
 
407
                if isinstance(uom, (int, long)):
 
408
                    uom = [uom]
 
409
                additionalitem['uom'] = uom and uom[0]
 
410
                additionalitem['shipment_id'] = shipment_ids[0]
 
411
                additional_items_dict['additional_items_ids'].append((0, 0, additionalitem))
 
412
        context.update(additional_items_dict)
 
413
        return context
318
414
    
319
415
    def do_create_shipment(self, cr, uid, ids, context=None):
320
416
        '''
324
420
        assert context, 'no context, method call is wrong'
325
421
        assert 'active_ids' in context, 'No shipment ids in context. Action call is wrong'
326
422
        
 
423
        context.update(self.create_additionalitems(cr, uid, ids, context))
 
424
        
 
425
#        context.update(self.update_additionalitems(cr, uid, ids, context))
 
426
        
327
427
        ship_obj = self.pool.get('shipment')
 
428
        # name of the wizard field for moves (one2many)
 
429
        field_name = 'product_moves_shipment_create'
328
430
        # shipment ids
329
431
        shipment_ids = context['active_ids']
330
 
        # generate data structure - selected_number must be non zero to be taken into accound
 
432
        # generate data structure - selected_number must be non zero to be taken into account
331
433
        partial_datas_shipment = self.generate_data_from_partial(cr, uid, ids, conditions=['selected_number'], context=context)
332
 
        # integrity check on wizard data
333
 
        if not self.integrity_check_create_shipment(cr, uid, ids, partial_datas_shipment, context=context):
334
 
            raise osv.except_osv(_('Warning !'), _('You must at least select one pack to ship!'))
 
434
        
 
435
        # reset the integrity status of all lines
 
436
        self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
 
437
        # integrity check on wizard data - sequence -> no prodlot check as the screen is readonly
 
438
        packs_check = self.integrity_check_packs(cr, uid, ids, partial_datas_shipment, model_name='stock.move.memory.shipment.create', context=context)
 
439
        if not packs_check:
 
440
            # for not blocking yml test with the raise I use 'yml_test' in context
 
441
            if not context.get('yml_test'):
 
442
                # the windows must be updated to trigger tree colors
 
443
                self.pool.get('wizard').open_wizard(cr, uid, shipment_ids, type='update', context=context)
 
444
                raise osv.except_osv(_('Processing Error'), _("You have to enter the quantities you want to process before processing the move"))
 
445
            # the windows must be updated to trigger tree colors
 
446
            else:
 
447
                return self.pool.get('wizard').open_wizard(cr, uid, shipment_ids, type='update', context=context)
335
448
        # call stock_picking method which returns action call
336
449
        return ship_obj.do_create_shipment(cr, uid, shipment_ids, context=dict(context, partial_datas_shipment=partial_datas_shipment))
337
450
    
338
 
    def integrity_check_return_packs(self, cr, uid, ids, data, context=None):
339
 
        '''
340
 
        integrity check on shipment data
341
 
        '''
342
 
        for shipment_data in data.values():
343
 
            for packing_data in shipment_data.values():
344
 
                for from_pack_data in packing_data.values():
345
 
                    for to_pack_data in from_pack_data.values():
346
 
                        for partial in to_pack_data:
347
 
                            if partial.get('selected_number', False):
348
 
                                return True
349
 
        
350
 
        return False
351
 
    
352
451
    def do_return_packs(self, cr, uid, ids, context=None):
353
452
        '''
354
453
        gather data from wizard pass it to the do_return_packs method of shipment class
358
457
        assert 'active_ids' in context, 'No shipment ids in context. Action call is wrong'
359
458
        
360
459
        ship_obj = self.pool.get('shipment')
 
460
        # name of the wizard field for moves (one2many)
 
461
        field_name = 'product_moves_shipment_returnpacks'
361
462
        # shipment ids
362
463
        shipment_ids = context['active_ids']
363
464
        # generate data structure - selected_number must be non zero to be taken into account
364
465
        partial_datas = self.generate_data_from_partial(cr, uid, ids, conditions=['selected_number'], context=context)
365
 
        # integrity check on wizard data
366
 
        if not self.integrity_check_return_packs(cr, uid, ids, partial_datas, context=context):
367
 
            raise osv.except_osv(_('Warning !'), _('You must at least select one pack to return!'))
 
466
        
 
467
        # reset the integrity status of all lines
 
468
        self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
 
469
        # integrity check on wizard data - sequence -> no prodlot check as the screen is readonly
 
470
        packs_check = self.integrity_check_packs(cr, uid, ids, partial_datas, model_name='stock.move.memory.shipment.returnpacks', context=context)
 
471
        if not packs_check:
 
472
            # the windows must be updated to trigger tree colors
 
473
            return self.pool.get('wizard').open_wizard(cr, uid, shipment_ids, type='update', context=context)
368
474
        # call stock_picking method which returns action call
369
475
        return ship_obj.do_return_packs(cr, uid, shipment_ids, context=dict(context, partial_datas=partial_datas))
370
476
    
371
477
    def integrity_check_return_packs_from_shipment(self, cr, uid, ids, data, context=None):
372
478
        '''
373
479
        integrity check on shipment data
 
480
        (sfrom = selected from, sto = selected to)
 
481
        
 
482
        - rule #1: sfrom <= sto // integrity of selected sequence
 
483
        - rule #2: (sfrom >= from) and (sto <= to) // in the initial range
 
484
        - rule #3: sfrom[i] > sto[i-1] for i>0 // no overlapping, unique sequence
 
485
        
 
486
        {26: 
 
487
            {240: {1: {1: [{'selected_weight': 33.0, 'memory_move_id': 39, 'return_from': 1, 'weight': 33.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 1, 'width': 0.0, 'sale_order_id': 61}, {'selected_weight': 33.0, 'memory_move_id': 50, 'return_from': 1, 'weight': 33.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 1, 'width': 0.0, 'sale_order_id': 61}]},
 
488
                   2: {30: [{'selected_weight': 638.0, 'memory_move_id': 40, 'return_from': 2, 'weight': 22.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 30, 'integrity_status': 'empty', 'num_of_packs': 29, 'selected_number': 29, 'return_to': 30, 'width': 0.0, 'sale_order_id': 61}, {'selected_weight': 638.0, 'memory_move_id': 51, 'return_from': 2, 'weight': 22.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 30, 'integrity_status': 'empty', 'num_of_packs': 29, 'selected_number': 29, 'return_to': 30, 'width': 0.0, 'sale_order_id': 61}, {'selected_weight': 638.0, 'memory_move_id': 52, 'return_from': 2, 'weight': 22.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 30, 'integrity_status': 'empty', 'num_of_packs': 29, 'selected_number': 29, 'return_to': 30, 'width': 0.0, 'sale_order_id': 61}]},
 
489
                   31: {32: [{'selected_weight': 22.0, 'memory_move_id': 41, 'return_from': 31, 'weight': 11.0, 'pack_type': False, 'ppl_id': 224, 'draft_packing_id': 240, 'wizard_id': 5, 'height': 0.0, 'from_pack': 31, 'length': 0.0, 'to_pack': 32, 'integrity_status': 'empty', 'num_of_packs': 2, 'selected_number': 2, 'return_to': 32, 'width': 0.0, 'sale_order_id': 61}]}},
 
490
             241: {8: {8: [{'selected_weight': 5.0, 'memory_move_id': 42, 'return_from': 8, 'weight': 5.0, 'pack_type': False, 'ppl_id': 225, 'draft_packing_id': 241, 'wizard_id': 5, 'height': 0.0, 'from_pack': 8, 'length': 0.0, 'to_pack': 8, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 8, 'width': 0.0, 'sale_order_id': 61}]}, 1: {1: [{'selected_weight': 3.0, 'memory_move_id': 43, 'return_from': 1, 'weight': 3.0, 'pack_type': False, 'ppl_id': 225, 'draft_packing_id': 241, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 1, 'width': 0.0, 'sale_order_id': 61}]}, 2: {7: [{'selected_weight': 24.0, 'memory_move_id': 44, 'return_from': 2, 'weight': 4.0, 'pack_type': False, 'ppl_id': 225, 'draft_packing_id': 241, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 7, 'integrity_status': 'empty', 'num_of_packs': 6, 'selected_number': 6, 'return_to': 7, 'width': 0.0, 'sale_order_id': 61}]}},
 
491
             238: {16: {16: [{'selected_weight': 22.0, 'memory_move_id': 45, 'return_from': 16, 'weight': 22.0, 'pack_type': False, 'ppl_id': 231, 'draft_packing_id': 238, 'wizard_id': 5, 'height': 0.0, 'from_pack': 16, 'length': 0.0, 'to_pack': 16, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 16, 'width': 0.0, 'sale_order_id': 62}]}, 1: {10: [{'selected_weight': 440.0, 'memory_move_id': 46, 'return_from': 1, 'weight': 44.0, 'pack_type': False, 'ppl_id': 231, 'draft_packing_id': 238, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 10, 'integrity_status': 'empty', 'num_of_packs': 10, 'selected_number': 10, 'return_to': 10, 'width': 0.0, 'sale_order_id': 62}]}, 11: {15: [{'selected_weight': 165.0, 'memory_move_id': 47, 'return_from': 11, 'weight': 33.0, 'pack_type': False, 'ppl_id': 231, 'draft_packing_id': 238, 'wizard_id': 5, 'height': 0.0, 'from_pack': 11, 'length': 0.0, 'to_pack': 15, 'integrity_status': 'empty', 'num_of_packs': 5, 'selected_number': 5, 'return_to': 15, 'width': 0.0, 'sale_order_id': 62}]}},
 
492
             239: {1: {1: [{'selected_weight': 22.0, 'memory_move_id': 48, 'return_from': 1, 'weight': 22.0, 'pack_type': False, 'ppl_id': 230, 'draft_packing_id': 239, 'wizard_id': 5, 'height': 0.0, 'from_pack': 1, 'length': 0.0, 'to_pack': 1, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 1, 'width': 0.0, 'sale_order_id': 62}]}, 2: {2: [{'selected_weight': 33.0, 'memory_move_id': 49, 'return_from': 2, 'weight': 33.0, 'pack_type': False, 'ppl_id': 230, 'draft_packing_id': 239, 'wizard_id': 5, 'height': 0.0, 'from_pack': 2, 'length': 0.0, 'to_pack': 2, 'integrity_status': 'empty', 'num_of_packs': 1, 'selected_number': 1, 'return_to': 2, 'width': 0.0, 'sale_order_id': 62}]}}}}
374
493
        '''
 
494
        memory_move_obj = self.pool.get('stock.move.memory.shipment.returnpacksfromshipment')
375
495
        for shipment_data in data.values():
 
496
            # counter for detecting empty return
 
497
            number_of_sequences = 0
 
498
            # flag for detecting to value smaller than from value
 
499
            to_samller_than_from = False
 
500
            # flag for detecting overlapping sequences
 
501
            overlap = False
 
502
            # flag for detecting out of range selection
 
503
            out_of_range = False
376
504
            for packing_data in shipment_data.values():
 
505
                # list of sequences for each picking - sequences must be treated separately for each packing
 
506
                sequences = []
 
507
                # gather the sequences for this packing - ppl (one packing corresponds to one ppl)
377
508
                for from_pack_data in packing_data.values():
378
509
                    for to_pack_data in from_pack_data.values():
379
510
                        for partial in to_pack_data:
380
 
                            if partial.get('return_from', False) and partial.get('return_to', False):
381
 
                                return True
 
511
                            # we have to treat all partial (split) data for each ppl as many sequence can exists for the same ppl
 
512
                            # rule #1: sfrom <= sto // integrity of selected sequence
 
513
                            if not (partial['return_from'] <= partial['return_to']):
 
514
                                to_samller_than_from = True
 
515
                                memory_move_obj.write(cr, uid, [partial['memory_move_id']], {'integrity_status': 'to_smaller_than_from',}, context=context)
 
516
                            # rule #2: (sfrom >= from) and (sto <= to) // in the initial range
 
517
                            elif not (partial['return_from'] >= partial['from_pack'] and partial['return_to'] <= partial['to_pack']):
 
518
                                out_of_range = True
 
519
                                memory_move_obj.write(cr, uid, [partial['memory_move_id']], {'integrity_status': 'seq_out_of_range',}, context=context)
 
520
                            else:
 
521
                                # [0]: selected FROM PACK / [1]: selected TO PACK / [2]: MEMORY MOVE ID
 
522
                                sequences.append((partial['return_from'], partial['return_to'], partial['memory_move_id']))
 
523
                # increase the number of valid sequences
 
524
                number_of_sequences += len(sequences)
 
525
                # sort the sequences according to from value
 
526
                sequences = sorted(sequences, key=lambda seq: seq[0])
 
527
                # go through the list of sequences applying the rules
 
528
                for i in range(len(sequences)):
 
529
                    seq = sequences[i]
 
530
                    # rules 3 applies from second element
 
531
                    if i > 0:
 
532
                        # previsous sequence
 
533
                        seqb = sequences[i-1]
 
534
                        # rule #3: sfrom[i] > sto[i-1] for i>0 // no overlapping, unique sequence
 
535
                        if not (seq[0] > seqb[1]):
 
536
                            overlap = True
 
537
                            memory_move_obj.write(cr, uid, [seq[2]], {'integrity_status': 'overlap',}, context=context)
 
538
            
 
539
            # if error, return False
 
540
            if not number_of_sequences or to_samller_than_from or overlap or out_of_range:
 
541
                return False
382
542
        
383
 
        return False
 
543
        return True
384
544
    
385
545
    def do_return_packs_from_shipment(self, cr, uid, ids, context=None):
386
546
        '''
391
551
        assert 'active_ids' in context, 'No shipment ids in context. Action call is wrong'
392
552
        
393
553
        ship_obj = self.pool.get('shipment')
 
554
        # name of the wizard field for moves (one2many)
 
555
        field_name = 'product_moves_shipment_returnpacksfromshipment'
394
556
        # shipment ids
395
557
        shipment_ids = context['active_ids']
396
558
        # generate data structure - return_from and return_to must be non zero
 
559
        # TODO: there is a problem with (0,3) for example as it does not take part to data
 
560
        # the list is therefore empty, and no error message is displayed by the integrity check
 
561
        # to be modified along with delete lines policy implementation
 
562
        # as a (temporary?) fix, all conditions must be true at the same time to be skipped (0,0) is skipped, (0,3) isn't
397
563
        partial_datas = self.generate_data_from_partial(cr, uid, ids, conditions=['return_from', 'return_to'], context=context)
398
 
        # integrity check on wizard data
399
 
        if not self.integrity_check_return_packs_from_shipment(cr, uid, ids, partial_datas, context=context):
400
 
            raise osv.except_osv(_('Warning !'), _('You must at least select one pack to return!'))
 
564
        
 
565
        # reset the integrity status of all lines
 
566
        self.set_integrity_status(cr, uid, ids, field_name=field_name, context=context)
 
567
        # integrity check on wizard data - sequence -> no prodlot check as the screen is readonly
 
568
        sequence_check = self.integrity_check_return_packs_from_shipment(cr, uid, ids, partial_datas, context=context)
 
569
        if not sequence_check:
 
570
            # the windows must be updated to trigger tree colors
 
571
            return self.pool.get('wizard').open_wizard(cr, uid, shipment_ids, type='update', context=context)
401
572
        # call stock_picking method which returns action call
402
573
        return ship_obj.do_return_packs_from_shipment(cr, uid, shipment_ids, context=dict(context, partial_datas=partial_datas))
403
574
    
404
575
 
405
576
shipment_wizard()
 
577
 
 
578
 
 
579
class memory_additionalitems(osv.osv_memory):
 
580
    '''
 
581
    view corresponding to additionalitems
 
582
    
 
583
    integrity constraint 
 
584
    '''
 
585
    _name = "memory.additionalitems"
 
586
    _description="Additional Items"
 
587
    
 
588
    _columns = {'name': fields.char(string='Additional Item', size=1024, required=True),
 
589
                'quantity': fields.float(digits=(16,2), string='Quantity', required=True),
 
590
                'uom': fields.many2one('product.uom', string='UOM', required=True),
 
591
                'comment': fields.char(string='Comment', size=1024),
 
592
                'volume': fields.float(digits=(16,2), string='Volume[dm³]'),
 
593
                'weight': fields.float(digits=(16,2), string='Weight[kg]', required=True),
 
594
                'picking_id': fields.many2one('stock.picking', 'PPL', readonly=True),
 
595
                'additional_item_id': fields.many2one('shipment.additionalitems', 'Additional item id', readonly=True),
 
596
                }
 
597
    
 
598
memory_additionalitems()
 
599
 
 
600
 
 
601
class stock_move_memory_shipment_additionalitems(osv.osv_memory):
 
602
    '''
 
603
    view corresponding to additionalitems
 
604
    
 
605
    integrity constraint 
 
606
    '''
 
607
    _inherit = "memory.additionalitems"
 
608
    _name  = 'stock.move.memory.shipment.additionalitems'
 
609
    _description="Additional Items"
 
610
    _columns = {
 
611
                'wizard_id' : fields.many2one('shipment.wizard', string="Wizard"),
 
612
                }
 
613
stock_move_memory_shipment_additionalitems()