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

« back to all changes in this revision

Viewing changes to threshold_value/threshold_value.py

  • Committer: Olivier DOSSMANN
  • Date: 2013-05-31 14:22:09 UTC
  • mto: This revision was merged to the branch mainline in revision 1687.
  • Revision ID: od@tempo-consulting.fr-20130531142209-sbcwvzuema11guzz
UF-1991 [FIX] Problem with wizard on "msg" field. Change it to "name".

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- encoding: utf-8 -*-
 
3
##############################################################################
 
4
#
 
5
#    OpenERP, Open Source Management Solution
 
6
#    Copyright (C) 2011 TeMPO Consulting, MSF
 
7
#
 
8
#    This program is free software: you can redistribute it and/or modify
 
9
#    it under the terms of the GNU Affero General Public License as
 
10
#    published by the Free Software Foundation, either version 3 of the
 
11
#    License, or (at your option) any later version.
 
12
#
 
13
#    This program is distributed in the hope that it will be useful,
 
14
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
#    GNU Affero General Public License for more details.
 
17
#
 
18
#    You should have received a copy of the GNU Affero General Public License
 
19
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
20
#
 
21
##############################################################################
 
22
 
 
23
from osv import osv
 
24
from osv import fields
 
25
 
 
26
from mx.DateTime import DateFrom
 
27
from mx.DateTime import now
 
28
from mx.DateTime import RelativeDate 
 
29
 
 
30
class threshold_value(osv.osv):
 
31
    _name = 'threshold.value'
 
32
    _description = 'Threshold value'
 
33
    
 
34
    def _get_product_ids(self, cr, uid, ids, field_name, arg, context=None):
 
35
        '''
 
36
        Returns a list of products for the rule
 
37
        '''
 
38
        res = {}
 
39
        
 
40
        for rule in self.browse(cr, uid, ids, context=context):
 
41
            res[rule.id] = []
 
42
            for line in rule.line_ids:
 
43
                res[rule.id].append(line.product_id.id)
 
44
        
 
45
        return res
 
46
    
 
47
    def _src_product_ids(self, cr, uid, obj, name, args, context=None):
 
48
        if not context:
 
49
            context = {}
 
50
            
 
51
        res = []
 
52
            
 
53
        for arg in args:
 
54
            if arg[0] == 'product_ids':
 
55
                rule_ids = []
 
56
                line_ids = self.pool.get('threshold.value.line').search(cr, uid, [('product_id', arg[1], arg[2])])
 
57
                for l in self.pool.get('threshold.value.line').browse(cr, uid, line_ids):
 
58
                    if l.threshold_value_id.id not in rule_ids:
 
59
                        rule_ids.append(l.threshold_value_id.id)
 
60
                res.append(('id', 'in', rule_ids))
 
61
                
 
62
        return res
 
63
    
 
64
    _columns = {
 
65
        'name': fields.char(size=128, string='Reference', required=True),
 
66
        'active': fields.boolean(string='Active'),
 
67
        'warehouse_id': fields.many2one('stock.warehouse', string='Warehouse', required=True),
 
68
        'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade", 
 
69
                                       domain="[('is_replenishment', '=', warehouse_id)]",
 
70
                                       help='Location where the computation is made'),
 
71
        'compute_method': fields.selection([('fixed', 'Fixed values'), ('computed', 'Computed values')],
 
72
                                           string='Method of computation', required=True,
 
73
                                           help="""If 'Fixed values', the scheduler will compare stock of product with the threshold value of the line. \n
 
74
                                           If 'Computed values', the threshold value and the ordered quantity will be calculated according to defined parameters"""),
 
75
        'consumption_method': fields.selection([('amc', 'Average Monthly Consumption'), ('fmc', 'Forecasted Monthly Consumption')],
 
76
                                               string='Consumption Method',
 
77
                                               help='Method used to compute the consumption of products.'),
 
78
        'consumption_period_from': fields.date(string='Period of calculation', 
 
79
                                             help='This period is a number of past months the system has to consider for AMC calculation.'\
 
80
                                             'By default this value is equal to the frequency in the Threshold.'),
 
81
        'consumption_period_to': fields.date(string='-'),
 
82
        'frequency': fields.float(digits=(16,2), string='Order frequency', 
 
83
                                  help='The time between two replenishments. Will be used to compute the quantity to order.'),
 
84
        'safety_month': fields.float(digits=(16,2), string='Safety Stock in months',
 
85
                                     help='In months. Period during the stock is not empty but need to be replenish. \
 
86
                                     Used to compute the quantity to order.'),
 
87
        'lead_time': fields.float(digits=(16,2), string='Fixed Lead Time in months',
 
88
                                  help='In months. Time to be delivered after processing the purchase order.'),
 
89
        'supplier_lt': fields.boolean(string='Product\'s supplier LT',
 
90
                                      help='If checked, use the lead time set in the supplier form.'),
 
91
        'line_ids': fields.one2many('threshold.value.line', 'threshold_value_id', string="Products"),
 
92
        'fixed_line_ids': fields.one2many('threshold.value.line', 'threshold_value_id2', string="Products"),
 
93
        'product_ids': fields.function(_get_product_ids, fnct_search=_src_product_ids, 
 
94
                                    type='many2many', relation='product.product', method=True, string='Products'),
 
95
        'sublist_id': fields.many2one('product.list', string='List/Sublist'),
 
96
        'nomen_manda_0': fields.many2one('product.nomenclature', 'Main Type'),
 
97
        'nomen_manda_1': fields.many2one('product.nomenclature', 'Group'),
 
98
        'nomen_manda_2': fields.many2one('product.nomenclature', 'Family'),
 
99
        'nomen_manda_3': fields.many2one('product.nomenclature', 'Root'),
 
100
    }
 
101
    
 
102
    _defaults = {
 
103
        'name': lambda obj, cr, uid, context=None: obj.pool.get('ir.sequence').get(cr, uid, 'threshold.value') or '',
 
104
        'active': lambda *a: True,
 
105
        'frequency': lambda *a: 3,
 
106
        'consumption_method': lambda *a: 'amc',
 
107
        'consumption_period_from': lambda *a: (now() + RelativeDate(day=1, months=-2)).strftime('%Y-%m-%d'),
 
108
        'consumption_period_to': lambda *a: (now() + RelativeDate(day=1)).strftime('%Y-%m-%d'),
 
109
    }
 
110
    
 
111
    def copy(self, cr, uid, ids, defaults={}, context=None):
 
112
        '''
 
113
        Increment the sequence
 
114
        '''
 
115
        name = self.pool.get('ir.sequence').get(cr, uid, 'threshold.value') or ''
 
116
        defaults.update({'name': name})
 
117
        
 
118
        return super(threshold_value, self).copy(cr, uid, ids, defaults, context=context)
 
119
    
 
120
    def default_get(self, cr, uid, fields, context=None):
 
121
        '''
 
122
        Get the default values for the replenishment rule
 
123
        '''
 
124
        res = super(threshold_value, self).default_get(cr, uid, fields, context=context)
 
125
        
 
126
        company_id = res.get('company_id')
 
127
        warehouse_id = res.get('warehouse_id')
 
128
        
 
129
        if not 'company_id' in res:
 
130
            company_id = self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.automatic.supply', context=context)
 
131
            res.update({'company_id': company_id})
 
132
        
 
133
        if not 'warehouse_id' in res:
 
134
            warehouse_id = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', company_id)], context=context)[0]
 
135
            res.update({'warehouse_id': warehouse_id})
 
136
            
 
137
        if not 'location_id' in res:
 
138
            location_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_stock_id.id
 
139
            res.update({'location_id': location_id})
 
140
        
 
141
        return res
 
142
    
 
143
    def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):
 
144
        """ Finds default stock location id for changed warehouse.
 
145
        @param warehouse_id: Changed id of warehouse.
 
146
        @return: Dictionary of values.
 
147
        """
 
148
        if warehouse_id:
 
149
            w = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
 
150
            v = {'location_id': w.lot_stock_id.id}
 
151
            return {'value': v}
 
152
        return {}
 
153
    
 
154
    def on_change_method(self, cr, uid, ids, method):
 
155
        '''
 
156
        Unfill the consumption period if the method is FMC
 
157
        '''
 
158
        res = {}
 
159
        
 
160
        if method and method == 'fmc':
 
161
            res.update({'consumption_period_from': False, 'consumption_period_to': False})
 
162
        elif method and method == 'amc':
 
163
            res.update({'consumption_period_from': (now() + RelativeDate(day=1, months=-2)).strftime('%Y-%m-%d'),
 
164
                        'consumption_period_to': (now() + RelativeDate(day=1, months=1, days=-1)).strftime('%Y-%m-%d')})
 
165
        
 
166
        return {'value': res}
 
167
    
 
168
    def on_change_period(self, cr, uid, ids, from_date, to_date):
 
169
        '''
 
170
        Check if the from date is younger than the to date
 
171
        '''
 
172
        warn = {}
 
173
        val = {}
 
174
        
 
175
        if from_date and to_date and from_date > to_date:
 
176
            warn = {'title': 'Issue on date',
 
177
                    'message': 'The start date must be younger than end date'}
 
178
            
 
179
        if from_date:
 
180
            val.update({'consumption_period_from': (DateFrom(from_date) + RelativeDate(day=1)).strftime('%Y-%m-%d')})
 
181
            
 
182
        if to_date:
 
183
            val.update({'consumption_period_to': (DateFrom(to_date) + RelativeDate(months=1, day=1, days=-1)).strftime('%Y-%m-%d')})
 
184
        
 
185
        return {'value': val, 'warning': warn}
 
186
    
 
187
    ##############################################################################################################################
 
188
    # The code below aims to enable filtering products regarding their sublist or their nomenclature.
 
189
    # Then, we fill lines of the one2many object 'threshold.value.line' according to the filtered products
 
190
    ##############################################################################################################################
 
191
    def onChangeSearchNomenclature(self, cr, uid, id, position, type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, num=True, context=None):
 
192
        return self.pool.get('product.product').onChangeSearchNomenclature(cr, uid, 0, position, type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, False, context={'withnum': 1})
 
193
 
 
194
    def fill_lines(self, cr, uid, ids, context=None):
 
195
        '''
 
196
        Fill all lines according to defined nomenclature level and sublist
 
197
        '''
 
198
        if context is None:
 
199
            context = {}
 
200
        for report in self.browse(cr, uid, ids, context=context):
 
201
            product_ids = []
 
202
            products = []
 
203
            nom = False
 
204
            field = False
 
205
            # Get all products for the defined nomenclature
 
206
            if report.nomen_manda_3:
 
207
                nom = report.nomen_manda_3.id
 
208
                field = 'nomen_manda_3'
 
209
            elif report.nomen_manda_2:
 
210
                nom = report.nomen_manda_2.id
 
211
                field = 'nomen_manda_2'
 
212
            elif report.nomen_manda_1:
 
213
                nom = report.nomen_manda_1.id
 
214
                field = 'nomen_manda_1'
 
215
            elif report.nomen_manda_0:
 
216
                nom = report.nomen_manda_0.id
 
217
                field = 'nomen_manda_0'
 
218
            if nom:
 
219
                product_ids.extend(self.pool.get('product.product').search(cr, uid, [(field, '=', nom)], context=context))
 
220
 
 
221
            # Get all products for the defined list
 
222
            if report.sublist_id:
 
223
                for line in report.sublist_id.product_ids:
 
224
                    product_ids.append(line.name.id)
 
225
 
 
226
            # Check if products in already existing lines are in domain
 
227
            products = []
 
228
            for line in report.line_ids:
 
229
                if line.product_id.id in product_ids:
 
230
                    products.append(line.product_id.id)
 
231
                else:
 
232
                    self.pool.get('threshold.value.line').unlink(cr, uid, line.id, context=context)
 
233
 
 
234
            for product in self.pool.get('product.product').browse(cr, uid, product_ids, context=context):
 
235
                # Check if the product is not already on the report
 
236
                if product.type not in ('consu', 'service', 'service_recep') and product.id not in products:
 
237
                    self.pool.get('threshold.value.line').create(cr, uid, {'product_id': product.id,
 
238
                                                                                            'product_uom_id': product.uom_id.id,
 
239
                                                                                            'product_qty': 1.00,
 
240
                                                                                            'threshold_value_id': report.id})
 
241
        return {'type': 'ir.actions.act_window',
 
242
                'res_model': 'threshold.value',
 
243
                'view_type': 'form',
 
244
                'view_mode': 'form',
 
245
                'res_id': ids[0],
 
246
                'target': 'dummy',
 
247
                'context': context}
 
248
        
 
249
    def dummy(self, cr, uid, ids, context=None):
 
250
        return True
 
251
 
 
252
    def get_nomen(self, cr, uid, id, field):
 
253
        return self.pool.get('product.nomenclature').get_nomen(cr, uid, self, id, field, context={'withnum': 1})
 
254
 
 
255
    def write(self, cr, uid, ids, vals, context=None):
 
256
        if vals.get('sublist_id',False):
 
257
            vals.update({'nomen_manda_0':False,'nomen_manda_1':False,'nomen_manda_2':False,'nomen_manda_3':False})
 
258
        if vals.get('nomen_manda_0',False):
 
259
            vals.update({'sublist_id':False})
 
260
        ret = super(threshold_value, self).write(cr, uid, ids, vals, context=context)
 
261
        return ret
 
262
 
 
263
threshold_value()
 
264
 
 
265
class threshold_value_line(osv.osv):
 
266
    _name = 'threshold.value.line'
 
267
    _description = 'Threshold Value Line'
 
268
    _rec_name = 'product_id'
 
269
    
 
270
    def copy_data(self, cr, uid, ids, defaults={}, context=None):
 
271
        res = super(threshold_value_line, self).copy_data(cr, uid, ids, defaults, context=context)
 
272
        
 
273
        if isinstance(res, dict):
 
274
            if 'threshold_value_id' in res:
 
275
                del res['threshold_value_id']
 
276
            if 'threshold_value_id2' in res:
 
277
                del res['threshold_value_id2']
 
278
        
 
279
        return res
 
280
    
 
281
    def create(self, cr, uid, vals, context=None):
 
282
        '''
 
283
        Add the second link to the threshold value rule
 
284
        '''
 
285
        if 'threshold_value_id' in vals:
 
286
            vals.update({'threshold_value_id2': vals['threshold_value_id']})
 
287
        elif 'threshold_value_id2' in vals:
 
288
            vals.update({'threshold_value_id': vals['threshold_value_id2']})
 
289
        
 
290
        return super(threshold_value_line, self).create(cr, uid, vals, context=context)
 
291
    
 
292
    def write(self, cr, uid, ids, vals, context=None):
 
293
        '''
 
294
        Add the second link to the threshold value rule
 
295
        '''
 
296
        if 'threshold_value_id' in vals:
 
297
            vals.update({'threshold_value_id2': vals['threshold_value_id']})
 
298
        elif 'threshold_value_id2' in vals:
 
299
            vals.update({'threshold_value_id': vals['threshold_value_id2']})
 
300
            
 
301
        context.update({'fake_threshold_value': vals.get('fake_threshold_value', False)})
 
302
        vals.update({'fake_threshold_value': 0.00})
 
303
        
 
304
        return super(threshold_value_line, self).write(cr, uid, ids, vals, context=context)
 
305
    
 
306
    def _get_values(self, cr, uid, ids, field_name, arg, context=None):
 
307
        '''
 
308
        Compute and return the threshold value and qty to order
 
309
        '''
 
310
        res = {}
 
311
        if context is None:
 
312
            context = {}
 
313
        
 
314
        for line in self.browse(cr, uid, ids, context=context):
 
315
            if context.get('fake_threshold_value', False):
 
316
                res[line.id] = context.get('fake_threshold_value', 0.00)
 
317
                continue 
 
318
            res[line.id] = 0.00
 
319
            
 
320
            rule = line.threshold_value_id
 
321
            context.update({'location_id': rule.location_id.id, 'compute_child': True})
 
322
            product = self.pool.get('product.product').browse(cr, uid, line.product_id.id, context=context)
 
323
            result = self._get_threshold_value(cr, uid, line.id, product, rule.compute_method, rule.consumption_method, 
 
324
                                                     rule.consumption_period_from, rule.consumption_period_to, rule.frequency, 
 
325
                                                     rule.safety_month, rule.lead_time, rule.supplier_lt, line.product_uom_id.id, context)
 
326
            res[line.id] = result.get(field_name, 0.00) 
 
327
        
 
328
        return res
 
329
 
 
330
    
 
331
    def _get_threshold(self, cr, uid, ids, context={}):
 
332
        res = {}
 
333
        for t in self.pool.get('threshold.value').browse(cr, uid, ids, context=context):
 
334
            for l in t.line_ids:
 
335
                res[l.id] = True
 
336
                
 
337
        return res.keys()
 
338
    
 
339
    _columns = {
 
340
        'product_id': fields.many2one('product.product', string='Product', required=True),
 
341
        'product_uom_id': fields.many2one('product.uom', string='Product UoM', required=True),
 
342
        'product_qty': fields.function(_get_values, method=True, type='float', string='Quantity to order'),
 
343
        'fake_threshold_value': fields.float(digits=(16,2), string='Threshold value'),
 
344
        'threshold_value': fields.function(_get_values, method=True, type='float', string='Threshold value',
 
345
                                           store={'threshold.value.line': (lambda self, cr, uid, ids, c=None: ids, ['product_id'],20),
 
346
                                                  'threshold.value': (_get_threshold, ['compute_method',
 
347
                                                                                       'consumption_method',
 
348
                                                                                       'consumption_period_from',
 
349
                                                                                       'consumption_period_to',
 
350
                                                                                       'frequency',
 
351
                                                                                       'safety_month',
 
352
                                                                                       'lead_time',
 
353
                                                                                       'supplier_lt'], 10)}),
 
354
        'fixed_product_qty': fields.float(digits=(16,2), string='Quantity to order'),
 
355
        'fixed_threshold_value': fields.float(digits=(16,2), string='Threshold value'),
 
356
        'threshold_value_id': fields.many2one('threshold.value', string='Threshold', ondelete='cascade', required=True),
 
357
        'threshold_value_id2': fields.many2one('threshold.value', string='Threshold', ondelete='cascade', required=True)
 
358
    }
 
359
    
 
360
    def _check_uniqueness(self, cr, uid, ids, context=None):
 
361
        '''
 
362
        Check if the product is not already in the current rule
 
363
        '''
 
364
        for line in self.browse(cr, uid, ids, context=context):
 
365
            lines = self.search(cr, uid, [('id', '!=', line.id), 
 
366
                                          ('product_id', '=', line.product_id.id),
 
367
                                          '|',
 
368
                                          ('threshold_value_id2', '=', line.threshold_value_id2.id),
 
369
                                          ('threshold_value_id', '=', line.threshold_value_id.id)], context=context)
 
370
            if lines:
 
371
                return False
 
372
            
 
373
        return True
 
374
    
 
375
    _constraints = [
 
376
        (_check_uniqueness, 'You cannot have two times the same product on the same threshold value rule', ['product_id'])
 
377
    ]
 
378
    
 
379
    def _get_threshold_value(self, cr, uid, line_id, product, compute_method, consumption_method,
 
380
                                consumption_period_from, consumption_period_to, frequency,
 
381
                                safety_month, lead_time, supplier_lt, uom_id, context=None):
 
382
        '''
 
383
        Return the threshold value and ordered qty of a product line
 
384
        '''
 
385
        if not context:
 
386
            context = {}
 
387
        
 
388
        cons = 0.00
 
389
        threshold_value = 0.00
 
390
        qty_to_order = 0.00
 
391
        if compute_method == 'computed':
 
392
            # Get the product available before change the context (from_date and to_date in context)
 
393
            product_available = product.qty_available
 
394
            
 
395
            # Change the context to compute consumption
 
396
            c = context.copy()
 
397
            c.update({'from_date': consumption_period_from, 'to_date': consumption_period_to})
 
398
            product = self.pool.get('product.product').browse(cr, uid, product.id, context=c)
 
399
            cons = consumption_method == 'fmc' and product.reviewed_consumption or product.product_amc
 
400
            
 
401
            # Set lead time according to choices in threshold rule (supplier or manual lead time)
 
402
            lt = supplier_lt and float(product.seller_delay)/30.0 or lead_time
 
403
                
 
404
            # Compute the threshold value
 
405
            threshold_value = cons * (lt + safety_month)
 
406
            threshold_value = self.pool.get('product.uom')._compute_qty(cr, uid, product.uom_id.id, threshold_value, product.uom_id.id)
 
407
                
 
408
            # Compute the quantity to re-order
 
409
            qty_to_order = cons * (frequency + lt + safety_month)\
 
410
                            - product_available - product.incoming_qty + product.outgoing_qty 
 
411
            qty_to_order = self.pool.get('product.uom')._compute_qty(cr, uid, uom_id or product.uom_id.id, \
 
412
                                                                     qty_to_order, product.uom_id.id)
 
413
            qty_to_order = qty_to_order > 0.00 and qty_to_order or 0.00
 
414
        elif line_id:
 
415
            line = self.browse(cr, uid, line_id, context=context)
 
416
            threshold_value = line.fixed_threshold_value
 
417
            qty_to_order = line.fixed_product_qty
 
418
            
 
419
        return {'threshold_value': threshold_value, 'product_qty': qty_to_order}
 
420
 
 
421
    def onchange_product_id(self, cr, uid, ids, product_id, compute_method=False, consumption_method=False,
 
422
                                consumption_period_from=False, consumption_period_to=False, frequency=False,
 
423
                                safety_month=False, lead_time=False, supplier_lt=False, context=None):
 
424
        """ Finds UoM for changed product.
 
425
        @param product_id: Changed id of product.
 
426
        @return: Dictionary of values.
 
427
        """
 
428
        if not context:
 
429
            context = {}
 
430
        
 
431
        res = {'value': {'product_uom_id': False,
 
432
                         'fake_threshold_value': 0.00,
 
433
                         'threshold_value': 0.00}}
 
434
        if product_id:
 
435
            prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
 
436
            res['value'].update({'product_uom_id': prod.uom_id.id})
 
437
            
 
438
            if compute_method:
 
439
                tv = self._get_threshold_value(cr, uid, ids, prod, compute_method, consumption_method,
 
440
                                               consumption_period_from, consumption_period_to, frequency,
 
441
                                               safety_month, lead_time, supplier_lt, prod.uom_id.id, context=context)['threshold_value']
 
442
                res['value'].update({'fake_threshold_value': tv, 'threshold_value': tv})
 
443
            
 
444
        return res
 
445
    
 
446
threshold_value_line()