~zaber/openobject-addons/stable_5.0-extra-addons

« back to all changes in this revision

Viewing changes to nan_quality_control/quality_control.py

  • Committer: Albert Cervera i Areny
  • Date: 2010-03-20 23:22:43 UTC
  • mto: (4256.1.65 stable_5.0_extra-addons)
  • mto: This revision was merged to the branch mainline in revision 4259.
  • Revision ID: albert@nan-tic.com-20100320232243-ayqm3qqkhd3px0cj
[ADD] Added new nan_quality_control module which aims to provide a generic quality control basis (not tight to products) which can be later used to create quality tests adapted to each business processes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: latin-1 -*-
 
2
##############################################################################
 
3
#
 
4
# Copyright (c) 2010 NaN Projectes de Programari Lliure, S.L. All Rights Reserved.
 
5
#                    http://www.NaN-tic.com
 
6
#
 
7
# WARNING: This program as such is intended to be used by professional
 
8
# programmers who take the whole responsability of assessing all potential
 
9
# consequences resulting from its eventual inadequacies and bugs
 
10
# End users who are looking for a ready-to-use solution with commercial
 
11
# garantees and support are strongly adviced to contract a Free Software
 
12
# Service Company
 
13
#
 
14
# This program is Free Software; you can redistribute it and/or
 
15
# modify it under the terms of the GNU General Public License
 
16
# as published by the Free Software Foundation; either version 2
 
17
# of the License, or (at your option) any later version.
 
18
#
 
19
# This program is distributed in the hope that it will be useful,
 
20
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
21
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
22
# GNU General Public License for more details.
 
23
#
 
24
# You should have received a copy of the GNU General Public License
 
25
# along with this program; if not, write to the Free Software
 
26
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
27
#
 
28
##############################################################################
 
29
 
 
30
import netsvc
 
31
 
 
32
from tools.translate import _
 
33
from osv import fields, osv
 
34
import time
 
35
 
 
36
class qc_proof_method(osv.osv):
 
37
    """
 
38
    This model stores a method for doing a test. Examples of methods are: "Eu.Pharm.v.v. (2.2.32)" or "HPLC"
 
39
    """
 
40
 
 
41
    _name = 'qc.proof.method'
 
42
    _description = 'Method'
 
43
    _columns = {
 
44
        'name': fields.char('Name', size=100, required=True ,select="1", translate=True),
 
45
        'active':fields.boolean('Active', select="1"),
 
46
    }
 
47
    _defaults = {
 
48
        'active': lambda *a: True,
 
49
    }
 
50
qc_proof_method()
 
51
 
 
52
 
 
53
class qc_posible_value(osv.osv):
 
54
    """
 
55
    This model stores all possible values of qualitative proof.
 
56
    """
 
57
 
 
58
    _name = 'qc.posible.value'
 
59
    _columns = {
 
60
        'name': fields.char('Name', size=200, required=True, select="1", translate=True),
 
61
        'active':fields.boolean('Active', select="1"),
 
62
    }
 
63
    _defaults = {
 
64
        'active': lambda *a: True,
 
65
    }
 
66
qc_posible_value()
 
67
 
 
68
 
 
69
class qc_proof( osv.osv ):
 
70
    """
 
71
    This model stores proofs which will be part of a test. Proofs are classified between qualitative 
 
72
    (such as color) and quantitative (such as density).
 
73
 
 
74
    A name_search on thish model will search on 'name' field but also on any of its synonyms.
 
75
    """
 
76
    _name = 'qc.proof'
 
77
 
 
78
    def _synonyms(self, cr, uid, ids, field_name, arg, context=None):
 
79
        result = {}
 
80
        for proof in self.browse(cr, uid, ids, context):
 
81
            texts = []
 
82
            for syn in proof.synonym_ids:
 
83
                texts.append( syn.name )
 
84
            result[proof.id] = ', '.join( texts )
 
85
        return result
 
86
 
 
87
    _columns = {
 
88
        'name': fields.char('Name', size=200, required=True,select="1",translate=True),
 
89
        'ref':fields.char('Code',size=30, select="1"),
 
90
        'active': fields.boolean('Active'),
 
91
        'synonym_ids': fields.one2many('qc.proof.synonym','proof_id','Synonyms'),
 
92
        'type': fields.selection([('qualitative','Qualitative'),('quantitative','Quantitative')], 'Type', select="1",required=True),
 
93
        'posible_values_ids': fields.many2many('qc.posible.value', 'proof_posible_value_rel','posible_value_id','proof_id','Posible Values'),
 
94
        'synonyms': fields.function(_synonyms, method=True, type='char', size='1000', string='Synonyms', store=False),
 
95
    }
 
96
 
 
97
    _defaults = {
 
98
        'active': lambda *a: True,
 
99
    }
 
100
 
 
101
    def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=None):
 
102
        result = super(qc_proof,self).name_search(cr, uid, name, args, operator, context, limit)
 
103
        if name:
 
104
            ids = [x[0] for x in result]
 
105
            new_ids = []
 
106
            syns = self.pool.get('qc.proof.synonym').name_search(cr, uid, name, args, operator, context, limit)
 
107
            syns = [x[0] for x in syns]
 
108
            for syn in self.pool.get('qc.proof.synonym').browse(cr, uid, syns, context):
 
109
                if not syn.proof_id.id in ids:
 
110
                    new_ids.append( syn.proof_id.id )
 
111
            result += self.name_get(cr, uid, new_ids, context)
 
112
        return result
 
113
 
 
114
    def name_get(self, cr, uid, ids, context=None):
 
115
        result = []
 
116
        for proof in self.browse(cr, uid, ids, context):
 
117
            text = proof.name
 
118
            if proof.synonyms:
 
119
                text += "  [%s]" % proof.synonyms
 
120
            result.append( (proof.id, text) )
 
121
        return result
 
122
 
 
123
qc_proof()
 
124
 
 
125
 
 
126
class qc_proof_synonym(osv.osv):
 
127
    """
 
128
    Proofs may have synonyms. These are used because suppliers may use different names for the same
 
129
    proof.
 
130
    """
 
131
 
 
132
    _name = 'qc.proof.synonym'
 
133
    _columns = {
 
134
        'name': fields.char('Name', size=200, required=True, select="1",translate=True),
 
135
        'proof_id':fields.many2one('qc.proof','Proof', required=True ),
 
136
    }
 
137
qc_proof_synonym()
 
138
 
 
139
 
 
140
class qc_test_template_category( osv.osv):
 
141
    """
 
142
    This model is used to categorize proof templates.
 
143
    """
 
144
 
 
145
    _name = 'qc.test.template.category'
 
146
 
 
147
    def name_get(self, cr, uid, ids, context=None):
 
148
        if not len(ids):
 
149
            return []
 
150
        reads = self.read(cr, uid, ids, ['name','parent_id'], context=context)
 
151
        res = []
 
152
        for record in reads:
 
153
            name = record['name']
 
154
            if record['parent_id']:
 
155
                name = record['parent_id'][1]+' / '+name
 
156
            res.append((record['id'], name))
 
157
        return res
 
158
 
 
159
    def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
 
160
        res = self.name_get(cr, uid, ids, context=context)
 
161
        return dict(res)
 
162
 
 
163
    def _check_recursion(self, cr, uid, ids):
 
164
        level = 100
 
165
        while len(ids):
 
166
            cr.execute('SELECT DISTINCT parent_id FROM qc_test_template_category WHERE id IN ('+','.join(map(str,ids))+')')
 
167
            ids = [x[0] for x in cr.fetchall() if x[0] != None]
 
168
            if not level:
 
169
                return False
 
170
            level -= 1
 
171
        return True
 
172
 
 
173
    _columns = {
 
174
        'name': fields.char('Category Name', required=True, size=64, translate=True),
 
175
        'parent_id': fields.many2one('qc.test.template.category', 'Parent Category', select=True),
 
176
        'complete_name': fields.function(_name_get_fnc, method=True, type="char", string='Full Name'),
 
177
        'child_ids': fields.one2many('qc.test.template.category', 'parent_id', 'Child Categories'),
 
178
        'active' : fields.boolean('Active', help="The active field allows you to hide the category without removing it."),
 
179
    }
 
180
    _constraints = [
 
181
        (_check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
 
182
    ]
 
183
    _defaults = {
 
184
        'active' : lambda *a: 1,
 
185
    }
 
186
 
 
187
qc_test_template_category()
 
188
 
 
189
 
 
190
class qc_test_template(osv.osv):
 
191
    """
 
192
    A template is a group of proofs to with the values that make them valid.
 
193
    """
 
194
 
 
195
    _name = 'qc.test.template'
 
196
    _description='Test Template'
 
197
 
 
198
    def _links_get(self, cr, uid, context=None):
 
199
        #TODO: Select models
 
200
        obj = self.pool.get('res.request.link')
 
201
        ids = obj.search(cr, uid, [], context=context)
 
202
        res = obj.read(cr, uid, ids, ['object', 'name'], context)
 
203
        return [(r['object'], r['name']) for r in res]
 
204
 
 
205
    def _default_name(self, cr, uid, context=None):
 
206
        if context and context.get('reference_model', False):
 
207
            id = context.get('reference_id')
 
208
            if not id:
 
209
                id = context.get('active_id')
 
210
            if id:
 
211
                source=self.pool.get(context['reference_model']).browse(cr, uid, id, context)
 
212
                if hasattr(source, 'name'):
 
213
                    return source.name
 
214
 
 
215
    def _default_object_id(self, cr, uid, context=None):
 
216
        if context and context.get('reference_model', False):
 
217
            return '%s,%d' % (context['reference_model'], context['reference_id'])
 
218
        else:
 
219
            return False
 
220
 
 
221
    def _default_type(self, cr, uid, context=None):
 
222
        if context and context.get('reference_model'):
 
223
            return 'related'
 
224
        else:
 
225
            return False
 
226
 
 
227
    _columns = {
 
228
        'active':fields.boolean('Active', select="1"),
 
229
        'name': fields.char('Name', size=200, required=True,translate=True,select="1"),
 
230
        'test_template_line_ids':fields.one2many('qc.test.template.line','test_template_id', 'Lines' ),
 
231
        'object_id':fields.reference('Reference Object', selection=_links_get, size=128 ) ,
 
232
        'fill_correct_values':fields.boolean('Fill With Correct Values' ),
 
233
        'type':fields.selection([('generic','Generic'),('related','Related')], 'Type' ),
 
234
        'category_id': fields.many2one('qc.test.template.category','Category'),
 
235
    }
 
236
 
 
237
    _defaults = {
 
238
        'name': _default_name,
 
239
        'active': lambda *a: True,
 
240
        'object_id': _default_object_id,
 
241
        'type': _default_type,
 
242
    }
 
243
 
 
244
qc_test_template()
 
245
 
 
246
class qc_test_template_line(osv.osv):
 
247
    """
 
248
    Each test template line has a reference to a proof and the valid value/values.
 
249
    """
 
250
 
 
251
    _name = 'qc.test.template.line'
 
252
    _order= 'sequence asc'
 
253
 
 
254
    def onchange_proof_id(self, cr, uid, ids, proof_id,context=None):
 
255
        if not proof_id:
 
256
            return {}
 
257
        proof = self.pool.get('qc.proof').browse(cr,uid,proof_id)
 
258
        return {'value':{'type':proof.type}}
 
259
 
 
260
    _columns = {
 
261
        'sequence':fields.integer('Sequence', required=True),
 
262
        'test_template_id': fields.many2one('qc.test.template', 'Test Template'),
 
263
        'proof_id': fields.many2one('qc.proof', 'Proof', required=True),
 
264
        'method_id': fields.many2one('qc.proof.method','Method'),
 
265
        'valid_value': fields.many2one('qc.posible.value','Valid Value'), # Only if qualitative
 
266
        'notes': fields.text('Notes'),
 
267
        'min_value': fields.float('Min'), # Only if quantitative
 
268
        'max_value': fields.float('Max'), # Only if quantitative
 
269
        'uom_id': fields.many2one('product.uom','Uom'), # Only if quantitative
 
270
        'type': fields.selection([('qualitative','Qualitative'),('quantitative','Quantitative')], 'Type', readonly=True),
 
271
    }
 
272
 
 
273
    _defaults = {
 
274
        'sequence':lambda *b:1,
 
275
    }
 
276
 
 
277
qc_test_template_line()
 
278
 
 
279
 
 
280
class qc_test(osv.osv):
 
281
    """
 
282
    This model contains an instance of a test template.
 
283
    """
 
284
 
 
285
    _name = 'qc.test'
 
286
 
 
287
    def _success(self, cr, uid, ids, field_name, arg, context=None):
 
288
        result = {}
 
289
        for test in self.browse(cr, uid, ids, context):
 
290
            success = True
 
291
            for line in test.test_line_ids:
 
292
                if not line.success:
 
293
                    success = False
 
294
                    break
 
295
            result[test.id] = success
 
296
        return result
 
297
 
 
298
    def _links_get(self, cr, uid, context=None):
 
299
        # TODO: Select models
 
300
        obj = self.pool.get('res.request.link')
 
301
        ids = obj.search(cr, uid, [], context=context)
 
302
        res = obj.read(cr, uid, ids, ['object', 'name'], context)
 
303
        return [(r['object'], r['name']) for r in res]
 
304
 
 
305
    def _default_object_id(self, cr, uid, context=None):
 
306
        if context and context.get('reference_model', False):
 
307
            return '%s,%d' % (context['reference_model'], context['reference_id'])
 
308
        else:
 
309
            return False
 
310
 
 
311
    _columns = {
 
312
        'name': fields.datetime('Date',required=True, readonly=True, states={'draft':[('readonly',False)]}),
 
313
        'object_id':fields.reference('Reference', selection=_links_get, size=128, readonly=True, states={'draft':[('readonly',False)]}),
 
314
        'test_template_id': fields.many2one('qc.test.template','Test', states={'success':[('readonly',True)], 'failed':[('readonly',True)]}),
 
315
        'test_line_ids': fields.one2many( 'qc.test.line', 'test_id', 'Test Lines', states={'success':[('readonly',True)], 'failed':[('readonly',True)]}),
 
316
        'test_internal_note': fields.text('Internal Note', states={'success':[('readonly',True)], 'failed':[('readonly',True)]}),
 
317
        'test_external_note': fields.text('External Note', states={'success':[('readonly',True)], 'failed':[('readonly',True)]}),
 
318
        'state': fields.selection([ 
 
319
            ('draft','Draft'),
 
320
            ('waiting','Waiting Supervisor Approval'),
 
321
            ('success','Quality Success'),
 
322
            ('failed','Quality Failed'),
 
323
        ], 'State', readonly=True),
 
324
        'success': fields.function(_success, method=True, type='boolean', string='Success', help='This field will be active if all tests have succeeded.'),
 
325
        'enabled': fields.boolean('Enabled', readonly=True, help='If a quality control test is not enabled it means it can not be moved from "Quality Success" or "Quality Failed" state.'),
 
326
    }
 
327
    _defaults = {
 
328
        'name' : lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
 
329
        'state': lambda *a: 'draft',
 
330
        'object_id': _default_object_id,
 
331
        'enabled': lambda *a: True,
 
332
    }
 
333
 
 
334
    def create(self, cr, uid, datas, context=None):
 
335
        if context and context.get('reference_model', False):
 
336
            datas['object_id'] = context['reference_model'] + "," + str(context['reference_id'])
 
337
        return super(osv.osv, self).create(cr, uid, datas, context=context)
 
338
 
 
339
    def qc_test_success(self, cr, uid, ids, context):
 
340
        self.write(cr, uid, ids, {
 
341
            'state' : 'success'
 
342
        }, context)
 
343
        return True
 
344
 
 
345
    def qc_test_failed(self, cr, uid, ids, context):
 
346
        self.write(cr, uid, ids, {
 
347
            'state' : 'failed'
 
348
        }, context)
 
349
        return True
 
350
 
 
351
    def test_state(self, cr, uid, ids, mode, *args):
 
352
        quality_check=False
 
353
        if mode == 'failed':
 
354
            return not quality_check
 
355
        if mode == 'success':
 
356
            return quality_check
 
357
        return False
 
358
 
 
359
    def set_test_template(self, cr, uid, ids, template_id, context):
 
360
        for id in ids:
 
361
            self.pool.get('qc.test').write( cr, uid, id, {
 
362
                'test_template_id' : template_id
 
363
            }, context)
 
364
 
 
365
            test = self.pool.get('qc.test').browse( cr, uid, id, context )
 
366
 
 
367
            if len(test.test_line_ids) > 0:
 
368
                self.pool.get( 'qc.test.line' ).unlink(cr, uid, [x.id for x in test.test_line_ids], context )
 
369
 
 
370
            for line in test.test_template_id.test_template_line_ids:
 
371
                test_line_id = self.pool.get('qc.test.line').create( cr,uid, {
 
372
                    'test_id': id,
 
373
                    'method_id': line.method_id.id,
 
374
                    'valid_value': line.valid_value.id,
 
375
                    'proof_id': line.proof_id.id,
 
376
                    'test_template_line_id': line.id,
 
377
                    'notes': line.notes,
 
378
                    'min_value': line.min_value,
 
379
                    'max_value': line.max_value,
 
380
                    'uom_id': line.uom_id.id,
 
381
                    'test_uom_id': line.uom_id.id,
 
382
                    'proof_type': line.proof_id.type,
 
383
                }, context)
 
384
qc_test()
 
385
 
 
386
 
 
387
class qc_test_line(osv.osv):
 
388
    """
 
389
    Each test line has a value and a reference to a proof template line.
 
390
    """
 
391
 
 
392
    _name = 'qc.test.line'
 
393
    _rec_name = 'proof_id'
 
394
 
 
395
    def quality_test_check( self, cr, uid, ids,field_name, field_value, context ):
 
396
        res ={}
 
397
        lines = self.browse(cr,uid,ids,context )
 
398
        for line in lines:
 
399
            if line.proof_type =='qualitative':
 
400
                res[line.id] =  self.quality_test_qualitative_check( cr, uid, line, context)
 
401
            else:
 
402
                res[line.id] = self.quality_test_quantitative_check( cr, uid, line, context)
 
403
        return res
 
404
 
 
405
    def quality_test_qualitative_check( self, cr, uid, test_line, context ):
 
406
        if test_line.valid_value == test_line.actual_value_ql:
 
407
            return True
 
408
        else:
 
409
            return False
 
410
 
 
411
    def quality_test_quantitative_check( self, cr, uid, test_line, context ):
 
412
        amount = self.pool.get('product.uom')._compute_qty( cr, uid, test_line.uom_id.id, test_line.actual_value_qt, test_line.test_uom_id.id)
 
413
        if amount >=  test_line.min_value and amount <= test_line.max_value:
 
414
            return True
 
415
        else:
 
416
            return False
 
417
 
 
418
    _columns = {
 
419
        'test_id': fields.many2one('qc.test','Test'),
 
420
        'test_template_line_id':fields.many2one('qc.test.template.line','Test Template Line', readonly=True),
 
421
        'proof_id': fields.many2one('qc.proof','Proof', readonly=True),
 
422
        'method_id': fields.many2one('qc.proof.method','Method', readonly=True),
 
423
        'valid_value': fields.many2one('qc.posible.value','Valid Value', readonly=True, help="Value that should have the result to be valid if it is a qualitative proof."),
 
424
        'actual_value_qt': fields.float('Qt.Value', help="Value of the result if it is a quantitative proof."),
 
425
        'actual_value_ql': fields.many2one('qc.posible.value','Ql.Value', help="Value of the result if it is a qualitative proof."),
 
426
        'notes': fields.text('Notes', readonly=True ),
 
427
        'min_value': fields.float('Min', readonly=True, help="Minimum valid value if it is a quantitative proof."),
 
428
        'max_value': fields.float('Max', readonly=True, help="Maximum valid value if it is a quantitative proof."),
 
429
        'uom_id': fields.many2one('product.uom','Uom', readonly=True, help="UoM for minimum and maximum values if it is a quantitative proof."),
 
430
        'test_uom_id':fields.many2one('product.uom','Uom Test', help="UoM of the value of the result if it is a quantitative proof."),
 
431
        'proof_type': fields.selection([('qualitative','Qualitative'),('quantitative','Quantitative')], 'Proof Type', readonly=True),
 
432
        'success': fields.function( quality_test_check,type='boolean',method=True, string="Success?", select="1"),
 
433
    }
 
434
qc_test_line()
 
435
 
 
436
 
 
437
class qc_test_wizard(osv.osv_memory):
 
438
    """
 
439
    This wizard is responsible for setting the proof template for a given test. This
 
440
    will not only fill in the 'test_template_id' field, but will also fill in all lines
 
441
    of the test with the corresponding lines of the template.
 
442
    """
 
443
 
 
444
    _name = 'qc.test.set.template.wizard'
 
445
 
 
446
    def _default_test_template_id(self, cr, uid, context):
 
447
        id = context.get('active_id',False)
 
448
        test = self.pool.get('qc.test').browse(cr, uid, id, context)
 
449
        ids = self.pool.get('qc.test.template').search(cr, uid, [('object_id','=',test.object_id)], context=context)
 
450
        return ids and ids[0] or False
 
451
 
 
452
    _columns = {
 
453
        'test_template_id': fields.many2one('qc.test.template', 'Template'),
 
454
    }
 
455
    _defaults = {
 
456
        'test_template_id': _default_test_template_id,
 
457
    }
 
458
 
 
459
    def action_create_test(self, cr, uid, ids, context):
 
460
        wizard = self.browse(cr, uid, ids[0], context)
 
461
        self.pool.get('qc.test').set_test_template(cr, uid, [context['active_id']], wizard.test_template_id.id, context)
 
462
        return {
 
463
            'type': 'ir.actions.act_window_close',
 
464
        }
 
465
 
 
466
    def action_cancel(self, cr, uid, ids, context=None):
 
467
        return {
 
468
            'type': 'ir.actions.act_window_close',
 
469
        }
 
470
   
 
471
qc_test_wizard()
 
472
 
 
473
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: