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

« back to all changes in this revision

Viewing changes to msf_tools/msf_tools.py

UF-385 [ADD] Added consumption_calculation module

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#    OpenERP, Open Source Management Solution
5
 
#    Copyright (C) 2011 TeMPO Consulting, MSF 
6
 
#
7
 
#    This program is free software: you can redistribute it and/or modify
8
 
#    it under the terms of the GNU Affero General Public License as
9
 
#    published by the Free Software Foundation, either version 3 of the
10
 
#    License, or (at your option) any later version.
11
 
#
12
 
#    This program is distributed in the hope that it will be useful,
13
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#    GNU Affero General Public License for more details.
16
 
#
17
 
#    You should have received a copy of the GNU Affero General Public License
18
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
##############################################################################
21
 
 
22
 
from osv import osv, fields
23
 
 
24
 
import time
25
 
 
26
 
import inspect
27
 
 
28
 
from tools.translate import _
29
 
from dateutil.relativedelta import relativedelta
30
 
from datetime import datetime
31
 
from decimal import Decimal, ROUND_UP
32
 
 
33
 
import netsvc
34
 
 
35
 
class lang(osv.osv):
36
 
    '''
37
 
    define getter for date / time / datetime formats
38
 
    '''
39
 
    _inherit = 'res.lang'
40
 
    
41
 
    def _get_format(self, cr, uid, type, context=None):
42
 
        '''
43
 
        generic function
44
 
        '''
45
 
        if context is None:
46
 
            context = {}
47
 
        type = type + '_format'
48
 
        assert type in self._columns, 'Specified format field does not exist'
49
 
        user_obj = self.pool.get('res.users')
50
 
        # get user context lang
51
 
        user_lang = user_obj.read(cr, uid, uid, ['context_lang'], context=context)['context_lang']
52
 
        # get coresponding id
53
 
        lang_id = self.search(cr, uid, [('code','=',user_lang)])
54
 
        # return format value or from default function if not exists
55
 
        format = lang_id and self.read(cr, uid, lang_id[0], [type], context=context)[type] or getattr(self, '_get_default_%s'%type)(cr, uid, context=context)
56
 
        return format
57
 
    
58
 
    def _get_db_format(self, cr, uid, type, context=None):
59
 
        '''
60
 
        generic function - for now constant values
61
 
        '''
62
 
        if context is None:
63
 
            context = {}
64
 
        if type == 'date':
65
 
            return '%Y-%m-%d'
66
 
        if type == 'time':
67
 
            return '%H:%M:%S'
68
 
        # default value
69
 
        return '%Y-%m-%d'
70
 
lang()
71
 
 
72
 
 
73
 
class date_tools(osv.osv):
74
 
    '''
75
 
    date related tools for msf project
76
 
    '''
77
 
    _name = 'date.tools'
78
 
    
79
 
    def get_date_format(self, cr, uid, context=None):
80
 
        '''
81
 
        get the date format for the uid specified user
82
 
        
83
 
        from msf_order_date module
84
 
        '''
85
 
        lang_obj = self.pool.get('res.lang')
86
 
        return lang_obj._get_format(cr, uid, 'date', context=context)
87
 
    
88
 
    def get_db_date_format(self, cr, uid, context=None):
89
 
        '''
90
 
        return constant value
91
 
        '''
92
 
        lang_obj = self.pool.get('res.lang')
93
 
        return lang_obj._get_db_format(cr, uid, 'date', context=context)
94
 
    
95
 
    def get_time_format(self, cr, uid, context=None):
96
 
        '''
97
 
        get the time format for the uid specified user
98
 
        
99
 
        from msf_order_date module
100
 
        '''
101
 
        lang_obj = self.pool.get('res.lang')
102
 
        return lang_obj._get_format(cr, uid, 'time', context=context)
103
 
    
104
 
    def get_db_time_format(self, cr, uid, context=None):
105
 
        '''
106
 
        return constant value
107
 
        '''
108
 
        lang_obj = self.pool.get('res.lang')
109
 
        return lang_obj._get_db_format(cr, uid, 'time', context=context)
110
 
    
111
 
    def get_datetime_format(self, cr, uid, context=None):
112
 
        '''
113
 
        get the datetime format for the uid specified user
114
 
        '''
115
 
        return self.get_date_format(cr, uid, context=context) + ' ' + self.get_time_format(cr, uid, context=context)
116
 
    
117
 
    def get_db_datetime_format(self, cr, uid, context=None):
118
 
        '''
119
 
        return constant value
120
 
        '''
121
 
        return self.get_db_date_format(cr, uid, context=context) + ' ' + self.get_db_time_format(cr, uid, context=context)
122
 
    
123
 
    def get_date_formatted(self, cr, uid, d_type='date', datetime=None, context=None):
124
 
        '''
125
 
        Return the datetime in the format of the user
126
 
        @param d_type: 'date' or 'datetime' : determines which is the out format
127
 
        @param datetime: date to format 
128
 
        '''
129
 
        assert d_type in ('date', 'datetime'), 'Give only \'date\' or \'datetime\' as type parameter'
130
 
 
131
 
        if not datetime:
132
 
            datetime = time.strftime('%Y-%m-%d')
133
 
        
134
 
        if d_type == 'date':
135
 
            d_format = self.get_date_format(cr, uid)
136
 
            date = time.strptime(datetime, '%Y-%m-%d')
137
 
            return time.strftime(d_format, date)
138
 
        elif d_type == 'datetime':
139
 
            d_format = self.get_datetime_format(cr, uid)
140
 
            date = time.strptime(datetime, '%Y-%m-%d %H:%M:%S')
141
 
            return time.strftime(d_format, date)
142
 
    
143
 
date_tools()
144
 
 
145
 
 
146
 
class fields_tools(osv.osv):
147
 
    '''
148
 
    date related tools for msf project
149
 
    '''
150
 
    _name = 'fields.tools'
151
 
    
152
 
    def get_field_from_company(self, cr, uid, object=False, field=False, context=None):
153
 
        '''
154
 
        return the value for field from company for object 
155
 
        '''
156
 
        # field is required for value
157
 
        if not field:
158
 
            return False
159
 
        # object
160
 
        company_obj = self.pool.get('res.company')
161
 
        # corresponding company
162
 
        company_id = company_obj._company_default_get(cr, uid, object, context=context)
163
 
        # get the value
164
 
        res = company_obj.read(cr, uid, [company_id], [field], context=context)[0][field]
165
 
        return res
166
 
    
167
 
    def get_selection_name(self, cr, uid, object=False, field=False, key=False, context=None):
168
 
        '''
169
 
        return the name from the key of selection field
170
 
        '''
171
 
        if not object or not field or not key:
172
 
            return False
173
 
        # get the selection values list
174
 
        if isinstance(object, str):
175
 
            object = self.pool.get(object)
176
 
        list = object._columns[field].selection
177
 
        name = [x[1] for x in list if x[0] == key][0]
178
 
        return name
179
 
    
180
 
    def get_ids_from_browse_list(self, cr, uid, browse_list=False, context=None):
181
 
        '''
182
 
        return the list of ids corresponding to browse list in parameter
183
 
        '''
184
 
        if not browse_list:
185
 
            return []
186
 
        
187
 
        result = [x.id for x in browse_list]
188
 
        return result
189
 
    
190
 
fields_tools()
191
 
    
192
 
 
193
 
class data_tools(osv.osv):
194
 
    '''
195
 
    data related tools for msf project
196
 
    '''
197
 
    _name = 'data.tools'
198
 
    
199
 
    def load_common_data(self, cr, uid, ids, context=None):
200
 
        '''
201
 
        load common data into context
202
 
        '''
203
 
        if context is None:
204
 
            context = {}
205
 
        context.setdefault('common', {})
206
 
        # objects
207
 
        date_tools = self.pool.get('date.tools')
208
 
        obj_data = self.pool.get('ir.model.data')
209
 
        comp_obj = self.pool.get('res.company')
210
 
        # date format
211
 
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
212
 
        context['common']['db_date_format'] = db_date_format
213
 
        date_format = date_tools.get_date_format(cr, uid, context=context)
214
 
        context['common']['date_format'] = date_format
215
 
        # date is today
216
 
        date = time.strftime(db_date_format)
217
 
        context['common']['date'] = date
218
 
        # default company id
219
 
        company_id = comp_obj._company_default_get(cr, uid, 'stock.picking', context=context)
220
 
        context['common']['company_id'] = company_id
221
 
        
222
 
        # stock location
223
 
        stock_id = obj_data.get_object_reference(cr, uid, 'stock', 'stock_location_stock')[1]
224
 
        context['common']['stock_id'] = stock_id
225
 
        # kitting location
226
 
        kitting_id = obj_data.get_object_reference(cr, uid, 'stock', 'location_production')[1]
227
 
        context['common']['kitting_id'] = kitting_id
228
 
        # input location
229
 
        input_id = obj_data.get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_input')[1]
230
 
        context['common']['input_id'] = input_id
231
 
        # quarantine analyze
232
 
        quarantine_anal = obj_data.get_object_reference(cr, uid, 'stock_override', 'stock_location_quarantine_analyze')[1]
233
 
        context['common']['quarantine_anal'] = quarantine_anal
234
 
        # quarantine before scrap
235
 
        quarantine_scrap = obj_data.get_object_reference(cr, uid, 'stock_override', 'stock_location_quarantine_scrap')[1]
236
 
        context['common']['quarantine_scrap'] = quarantine_scrap
237
 
        # log
238
 
        log = obj_data.get_object_reference(cr, uid, 'stock_override', 'stock_location_logistic')[1]
239
 
        context['common']['log'] = log
240
 
        # cross docking
241
 
        cross_docking = obj_data.get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
242
 
        context['common']['cross_docking'] = cross_docking
243
 
        
244
 
        # kit reason type
245
 
        reason_type_id = obj_data.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_kit')[1]
246
 
        context['common']['reason_type_id'] = reason_type_id
247
 
        # reason type goods return
248
 
        rt_goods_return = obj_data.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_goods_return')[1]
249
 
        context['common']['rt_goods_return'] = rt_goods_return
250
 
        # reason type goods replacement
251
 
        rt_goods_replacement = obj_data.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_goods_replacement')[1]
252
 
        context['common']['rt_goods_replacement'] = rt_goods_replacement
253
 
        # reason type internal supply
254
 
        rt_internal_supply = obj_data.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_supply')[1]
255
 
        context['common']['rt_internal_supply'] = rt_internal_supply
256
 
        
257
 
        return True
258
 
 
259
 
data_tools()
260
 
 
261
 
 
262
 
class sequence_tools(osv.osv):
263
 
    '''
264
 
    sequence tools
265
 
    '''
266
 
    _name = 'sequence.tools'
267
 
    
268
 
    def reorder_sequence_number(self, cr, uid, base_object, base_seq_field, dest_object, foreign_field, foreign_ids, seq_field, context=None):
269
 
        '''
270
 
        receive a browse list corresponding to one2many lines
271
 
        recompute numbering corresponding to specified field
272
 
        compute next number of sequence
273
 
        
274
 
        we must make sure we reorder in conservative way according to original order
275
 
        
276
 
        *not used presently*
277
 
        '''
278
 
        # Some verifications
279
 
        if context is None:
280
 
            context = {}
281
 
        if isinstance(foreign_ids, (int, long)):
282
 
            foreign_ids = [foreign_ids]
283
 
            
284
 
        # objects
285
 
        base_obj = self.pool.get(base_object)
286
 
        dest_obj = self.pool.get(dest_object)
287
 
        seq_obj = self.pool.get('ir.sequence')
288
 
        
289
 
        for foreign_id in foreign_ids:
290
 
            # will be ordered by default according to db id, it's what we want according to user sequence
291
 
            item_ids = dest_obj.search(cr, uid, [(foreign_field, '=', foreign_id)], context=context)
292
 
            if item_ids:
293
 
                # read line number and id from items
294
 
                item_data = dest_obj.read(cr, uid, item_ids, [seq_field], context=context)
295
 
                # check the line number: data are ordered according to db id, so line number must be equal to index+1
296
 
                for i in range(len(item_data)):
297
 
                    if item_data[i][seq_field] != i+1:
298
 
                        dest_obj.write(cr, uid, [item_data[i]['id']], {seq_field: i+1}, context=context)
299
 
                # reset sequence to length + 1 all time, checking if needed would take much time
300
 
                # get the sequence id
301
 
                seq_id = base_obj.read(cr, uid, foreign_id, [base_seq_field], context=context)[base_seq_field][0]
302
 
                # we reset the sequence to length+1
303
 
                self.reset_next_number(cr, uid, [seq_id], value=len(item_ids)+1, context=context)
304
 
        
305
 
        return True
306
 
    
307
 
    def reorder_sequence_number_from_unlink(self, cr, uid, ids, base_object, base_seq_field, dest_object, foreign_field, seq_field, context=None):
308
 
        '''
309
 
        receive a browse list corresponding to one2many lines
310
 
        recompute numbering corresponding to specified field
311
 
        compute next number of sequence
312
 
        
313
 
        for unlink, only items with id > min(deleted id) are resequenced + reset the sequence value
314
 
        
315
 
        we must make sure we reorder in conservative way according to original order
316
 
        
317
 
        this method is called from methods of **destination object**
318
 
        '''
319
 
        # Some verifications
320
 
        if context is None:
321
 
            context = {}
322
 
        # if no ids as parameter return Tru
323
 
        if not ids:
324
 
            return True
325
 
            
326
 
        # objects
327
 
        base_obj = self.pool.get(base_object)
328
 
        dest_obj = self.pool.get(dest_object)
329
 
        seq_obj = self.pool.get('ir.sequence')
330
 
        
331
 
        # find the corresponding base ids
332
 
        base_ids = [x[foreign_field][0] for x in dest_obj.read(cr, uid, ids, [foreign_field], context=context) if x[foreign_field]]
333
 
        # simulate unique sql
334
 
        foreign_ids = set(base_ids)
335
 
        
336
 
        for foreign_id in foreign_ids:
337
 
            # will be ordered by default according to db id, it's what we want according to user sequence
338
 
            # reorder only ids bigger than min deleted + do not select deleted ones
339
 
            item_ids = dest_obj.search(cr, uid, [('id', '>', min(ids)), (foreign_field, '=', foreign_id), ('id', 'not in', ids)], context=context)
340
 
            # start numbering sequence
341
 
            start_num = 0
342
 
            # if deleted object is not the first one, we find the numbering value of previous one
343
 
            before_ids = dest_obj.search(cr, uid, [('id', '<', min(ids)), (foreign_field, '=', foreign_id)], context=context)
344
 
            if before_ids:
345
 
                # we read the numbering value of previous value (biggest id)
346
 
                start_num = dest_obj.read(cr, uid, max(before_ids), [seq_field], context=context)[seq_field]
347
 
            if item_ids:
348
 
                # read line number and id from items
349
 
                item_data = dest_obj.read(cr, uid, item_ids, [seq_field], context=context)
350
 
                # check the line number: data are ordered according to db id, so line number must be equal to index+1
351
 
                for i in range(len(item_data)):
352
 
                    # numbering value
353
 
                    start_num = start_num+1
354
 
                    if item_data[i][seq_field] != start_num:
355
 
                        cr.execute("update "+dest_obj._table+" set "+seq_field+"=%s where id=%s", (start_num, item_data[i]['id']))
356
 
                        #dest_obj.write(cr, uid, [item_data[i]['id']], {seq_field: start_num}, context=context)
357
 
            
358
 
            # reset sequence to start_num + 1 all time, checking if needed would take much time
359
 
            # get the sequence id
360
 
            seq_id = base_obj.read(cr, uid, foreign_id, [base_seq_field], context=context)[base_seq_field][0]
361
 
            # we reset the sequence to length+1, whether or not items
362
 
            self.reset_next_number(cr, uid, [seq_id], value=start_num+1, context=context)
363
 
        
364
 
        return True
365
 
    
366
 
    def reset_next_number(self, cr, uid, seq_ids, value=1, context=None):
367
 
        '''
368
 
        reset the next number of the sequence to value, default value 1
369
 
        '''
370
 
        # Some verifications
371
 
        if context is None:
372
 
            context = {}
373
 
        if isinstance(seq_ids, (int, long)):
374
 
            seq_ids = [seq_ids]
375
 
            
376
 
        # objects
377
 
        seq_obj = self.pool.get('ir.sequence')
378
 
        seq_obj.write(cr, uid, seq_ids, {'number_next': value}, context=context)
379
 
        return True
380
 
    
381
 
    def create_sequence(self, cr, uid, vals, name, code, prefix='', padding=0, context=None):
382
 
        '''
383
 
        create a new sequence
384
 
        '''
385
 
        seq_pool = self.pool.get('ir.sequence')
386
 
        seq_typ_pool = self.pool.get('ir.sequence.type')
387
 
        
388
 
        assert name, 'create sequence: missing name'
389
 
        assert code, 'create sequence: missing code'
390
 
 
391
 
        types = {'name': name,
392
 
                 'code': code
393
 
                 }
394
 
        seq_typ_pool.create(cr, uid, types)
395
 
 
396
 
        seq = {'name': name,
397
 
               'code': code,
398
 
               'prefix': prefix,
399
 
               'padding': padding,
400
 
               }
401
 
        return seq_pool.create(cr, uid, seq)
402
 
    
403
 
sequence_tools()
404
 
 
405
 
 
406
 
class picking_tools(osv.osv):
407
 
    '''
408
 
    picking related tools
409
 
    '''
410
 
    _name = 'picking.tools'
411
 
    
412
 
    def confirm(self, cr, uid, ids, context=None):
413
 
        '''
414
 
        confirm the picking
415
 
        '''
416
 
        # Some verifications
417
 
        if context is None:
418
 
            context = {}
419
 
        if isinstance(ids, (int, long)):
420
 
            ids = [ids]
421
 
            
422
 
        # objects
423
 
        pick_obj = self.pool.get('stock.picking')
424
 
        pick_obj.draft_force_assign(cr, uid, ids, context)
425
 
        return True
426
 
        
427
 
    def check_assign(self, cr, uid, ids, context=None):
428
 
        '''
429
 
        check assign the picking
430
 
        '''
431
 
        # Some verifications
432
 
        if context is None:
433
 
            context = {}
434
 
        if isinstance(ids, (int, long)):
435
 
            ids = [ids]
436
 
            
437
 
        # objects
438
 
        pick_obj = self.pool.get('stock.picking')
439
 
        pick_obj.action_assign(cr, uid, ids, context)
440
 
        return True
441
 
    
442
 
    def force_assign(self, cr, uid, ids, context=None):
443
 
        '''
444
 
        force assign the picking
445
 
        '''
446
 
        # Some verifications
447
 
        if context is None:
448
 
            context = {}
449
 
        if isinstance(ids, (int, long)):
450
 
            ids = [ids]
451
 
            
452
 
        # objects
453
 
        pick_obj = self.pool.get('stock.picking')
454
 
        pick_obj.force_assign(cr, uid, ids, context)
455
 
        return True
456
 
        
457
 
    def validate(self, cr, uid, ids, context=None):
458
 
        '''
459
 
        validate the picking
460
 
        '''
461
 
        # Some verifications
462
 
        if context is None:
463
 
            context = {}
464
 
        if isinstance(ids, (int, long)):
465
 
            ids = [ids]
466
 
            
467
 
        # objects
468
 
        pick_obj = self.pool.get('stock.picking')
469
 
        wf_service = netsvc.LocalService("workflow")
470
 
        # trigger standard workflow for validated picking ticket
471
 
        for id in ids:
472
 
            pick_obj.action_move(cr, uid, [id])
473
 
            wf_service.trg_validate(uid, 'stock.picking', id, 'button_done', cr)
474
 
        return True
475
 
        
476
 
    def all(self, cr, uid, ids, context=None):
477
 
        '''
478
 
        confirm - check - validate
479
 
        '''
480
 
        self.confirm(cr, uid, ids, context=context)
481
 
        self.check_assign(cr, uid, ids, context=context)
482
 
        self.validate(cr, uid, ids, context=context)
483
 
        return True
484
 
    
485
 
picking_tools()
486
 
    
487
 
 
488
 
class ir_translation(osv.osv):
489
 
    _name = 'ir.translation'
490
 
    _inherit = 'ir.translation'
491
 
 
492
 
    def tr_view(self, cr, name, context):
493
 
        if not context or not context.get('lang'):
494
 
            return name
495
 
        tr = self._get_source(cr, 1, False, 'view', context['lang'], name, True)
496
 
        if not tr:
497
 
            # sometimes de view name is empty and so the action name is used as view name
498
 
            tr2 = self._get_source(cr, 1, 'ir.actions.act_window,name', 'model', context['lang'], name)
499
 
            if tr2:
500
 
                return tr2
501
 
            return name
502
 
        return tr
503
 
 
504
 
 
505
 
ir_translation()
506
 
 
507
 
 
508
 
class uom_tools(osv.osv_memory):
509
 
    """
510
 
    This osv_memory class helps to check certain consistency related to the UOM.
511
 
    """
512
 
    _name = 'uom.tools'
513
 
 
514
 
    def check_uom(self, cr, uid, product_id, uom_id, context=None):
515
 
        """
516
 
        Check the consistency between the category of the UOM of a product and the category of a UOM.
517
 
        Return a boolean value (if false, it will raise an error).
518
 
        :param cr: database cursor
519
 
        :param product_id: takes the id of a product
520
 
        :param product_id: takes the id of a uom
521
 
        Note that this method is not consistent with the onchange method that returns a dictionary.
522
 
        """
523
 
        if context is None:
524
 
            context = {}
525
 
        uom_obj = self.pool.get('product.uom')
526
 
        product_obj = self.pool.get('product.product')
527
 
        if product_id and uom_id:
528
 
            if isinstance(product_id, (int, long)):
529
 
                product_id = [product_id]
530
 
            if isinstance(uom_id, (int, long)):
531
 
                uom_id = [uom_id]
532
 
            if not product_obj.browse(cr, uid, product_id, context)[0].uom_id.category_id.id == uom_obj.browse(cr, uid, uom_id, context)[0].category_id.id:
533
 
                return False
534
 
        return True
535
 
 
536
 
uom_tools()
537
 
 
538
 
 
539
 
class product_uom(osv.osv):
540
 
    _inherit = 'product.uom'
541
 
 
542
 
    def _compute_round_up_qty(self, cr, uid, uom_id, qty, context=None):
543
 
        '''
544
 
        Round up the qty according to the UoM
545
 
        '''
546
 
        uom = self.browse(cr, uid, uom_id, context=context)
547
 
        rounding_value = Decimal(str(uom.rounding).rstrip('0'))
548
 
 
549
 
        return float(Decimal(str(qty)).quantize(rounding_value, rounding=ROUND_UP))
550
 
 
551
 
    def _change_round_up_qty(self, cr, uid, uom_id, qty, fields=[], result=None, context=None):
552
 
        '''
553
 
        Returns the error message and the rounded value
554
 
        '''
555
 
        if not result:
556
 
            result = {'value': {}, 'warning': {}}
557
 
 
558
 
        if isinstance(fields, str):
559
 
            fields = [fields]
560
 
 
561
 
        message = {'title': _('Bad rounding'),
562
 
                   'message': _('The quantity entered is not valid according to the rounding value of the UoM. The product quantity has been rounded to the highest good value.')}
563
 
 
564
 
        if uom_id and qty:
565
 
            new_qty = self._compute_round_up_qty(cr, uid, uom_id, qty, context=context)
566
 
            if qty != new_qty:
567
 
                for f in fields:
568
 
                    result.setdefault('value', {}).update({f: new_qty})
569
 
                result.setdefault('warning', {}).update(message)
570
 
 
571
 
        return result
572
 
 
573
 
product_uom()