quality_control: Total refactorization with new API, README files, and new concepts. * Triggers for product category * ACLs * Tolerances in questions * one2many copyable * Fix some views * Error on same tolerance * Colors on inspection lines * Unify inspection header data assignment in set_test method * key name in set_test * Change icon set

This commit is contained in:
Pedro M. Baeza
2014-12-10 23:35:21 +01:00
parent 78caa2fec5
commit 4963f87226
32 changed files with 2189 additions and 2281 deletions

View File

@@ -1,31 +1,13 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010 NaN Projectes de Programari Lliure, S.L.
# All Rights Reserved.
# http://www.NaN-tic.com
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
from . import quality_control
from . import qc_trigger
from . import qc_trigger_line
from . import qc_test_category
from . import qc_test
from . import qc_inspection
from . import product_product
from . import product_template
from . import product_category

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
from openerp import models, fields
class ProductCategory(models.Model):
_inherit = "product.category"
qc_triggers = fields.One2many(
comodel_name="qc.trigger.product_category_line",
inverse_name="product_category",
string="Quality control triggers")

View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
from openerp import models, fields
class ProductProduct(models.Model):
_inherit = "product.product"
qc_triggers = fields.One2many(
comodel_name="qc.trigger.product_line", inverse_name="product",
string="Quality control triggers")

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
from openerp import models, fields
class ProductTemplate(models.Model):
_inherit = "product.template"
qc_triggers = fields.One2many(
comodel_name="qc.trigger.product_template_line",
inverse_name="product_template",
string="Quality control triggers")

View File

@@ -0,0 +1,293 @@
# -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
from openerp import models, fields, api, exceptions, _
class QcInspection(models.Model):
_name = 'qc.inspection'
_inherit = ['mail.thread', 'ir.needaction_mixin']
@api.one
@api.depends('inspection_lines', 'inspection_lines.success')
def _success(self):
self.success = all([x.success for x in self.inspection_lines])
@api.multi
def _links_get(self):
link_obj = self.env['res.request.link']
return [(r.object, r.name) for r in link_obj.search([])]
@api.one
@api.depends('object_id')
def _get_product(self):
if self.object_id and self.object_id._name == 'product.product':
self.product = self.object_id
else:
self.product = False
@api.one
@api.depends('object_id')
def _get_qty(self):
self.qty = 1.0
name = fields.Char(
string='Inspection number', required=True, default='/', select=True,
readonly=True, states={'draft': [('readonly', False)]}, copy=False)
date = fields.Datetime(
string='Date', required=True, readonly=True, copy=False,
default=fields.Datetime.now(),
states={'draft': [('readonly', False)]}, select=True)
object_id = fields.Reference(
string='Reference', selection=_links_get, readonly=True,
states={'draft': [('readonly', False)]}, ondelete="set null")
product = fields.Many2one(
comodel_name="product.product", compute="_get_product", store=True,
help="Product associated with the inspection")
qty = fields.Float(string="Quantity", compute="_get_qty", store=True)
test = fields.Many2one(
comodel_name='qc.test', string='Test', readonly=True, select=True)
inspection_lines = fields.One2many(
comodel_name='qc.inspection.line', inverse_name='inspection_id',
string='Inspection lines', readonly=True,
states={'ready': [('readonly', False)]})
internal_notes = fields.Text(string='Internal notes')
external_notes = fields.Text(
string='External notes',
states={'success': [('readonly', True)],
'failed': [('readonly', True)]})
state = fields.Selection(
[('draft', 'Draft'),
('ready', 'Ready'),
('waiting', 'Waiting supervisor approval'),
('success', 'Quality success'),
('failed', 'Quality failed'),
('canceled', 'Cancelled')],
string='State', readonly=True, default='draft')
success = fields.Boolean(
compute="_success", string='Success',
help='This field will be marked if all tests have been succeeded.',
store=True)
auto_generated = fields.Boolean(
string='Auto-generated', readonly=True, copy=False,
help='If an inspection is auto-generated, it can be cancelled nor '
'removed')
company_id = fields.Many2one(
comodel_name='res.company', string='Company', readonly=True,
states={'draft': [('readonly', False)]},
default=lambda self: self.env['res.company']._company_default_get(
'qc.inspection'))
@api.model
def create(self, vals):
if vals.get('name', '/') == '/':
vals['name'] = self.env['ir.sequence'].get('qc.inspection')
return super(QcInspection, self).create(vals)
@api.multi
def unlink(self):
for inspection in self:
if inspection.auto_generated:
raise exceptions.Warning(
_("You cannot remove an auto-generated inspection"))
if inspection.state != 'draft':
raise exceptions.Warning(
_("You cannot remove an inspection that it's not in draft "
"state"))
return super(QcInspection, self).unlink()
@api.multi
def action_draft(self):
self.write({'state': 'draft'})
@api.multi
def action_todo(self):
for inspection in self:
if not inspection.test:
raise exceptions.Warning(
_("You must set the test to perform first."))
self.write({'state': 'ready'})
@api.multi
def action_confirm(self):
for inspection in self:
for line in inspection.inspection_lines:
if line.question_type == 'qualitative':
if not line.qualitative_value:
raise exceptions.Warning(
_("You should provide an answer for all "
"quantitative questions."))
else:
if not line.uom_id:
raise exceptions.Warning(
_("You should provide a unit of measure for "
"qualitative questions."))
if inspection.success:
inspection.state = 'success'
else:
inspection.state = 'waiting'
@api.multi
def action_approve(self):
for inspection in self:
if inspection.success:
inspection.state = 'success'
else:
inspection.state = 'failed'
@api.multi
def action_cancel(self):
self.write({'state': 'canceled'})
@api.multi
def set_test(self, test, force_fill=False):
for inspection in self:
header = self._prepare_inspection_header(
inspection.object_id, test)
del header['state'] # don't change current status
del header['auto_generated'] # don't change auto_generated flag
inspection.write(header)
self.inspection_lines.unlink()
inspection.inspection_lines = inspection._prepare_inspection_lines(
test, force_fill=force_fill)
@api.multi
def _make_inspection(self, object_ref, test):
"""Overridable hook method for creating inspection from test.
:param object_ref: Object instance
:param test: Test instance
:return: Inspection object
"""
inspection = self.create(self._prepare_inspection_header(
object_ref, test))
inspection.set_test(test)
return inspection
@api.multi
def _prepare_inspection_header(self, object_ref, test):
"""Overridable hook method for preparing inspection header.
:param object_ref: Object instance
:param test: Test instance
:return: List of values for creating the inspection
"""
return {
'object_id': object_ref and '%s,%s' % (object_ref._name,
object_ref.id) or False,
'state': 'ready',
'test': test.id,
'auto_generated': True,
}
@api.multi
def _prepare_inspection_lines(self, test, force_fill=False):
new_data = []
for line in test.test_lines:
data = self._prepare_inspection_line(
test, line, fill=test.fill_correct_values or force_fill)
new_data.append((0, 0, data))
return new_data
@api.multi
def _prepare_inspection_line(self, test, line, fill=None):
data = {
'name': line.name,
'test_line': line.id,
'notes': line.notes,
'min_value': line.min_value,
'max_value': line.max_value,
'test_uom_id': line.uom_id.id,
'uom_id': line.uom_id.id,
'question_type': line.type,
'possible_ql_values': [x.id for x in line.ql_values]
}
if fill:
if line.type == 'qualitative':
# Fill with the first correct value found
for value in line.ql_values:
if value.ok:
data['qualitative_value'] = value.id
break
else:
# Fill with a value inside the interval
data['quantitative_value'] = (line.min_value +
line.max_value) * 0.5
return data
class QcInspectionLine(models.Model):
_name = 'qc.inspection.line'
_description = "Quality control inspection line"
@api.one
@api.depends('question_type', 'uom_id', 'test_uom_id', 'max_value',
'min_value', 'quantitative_value', 'qualitative_value',
'possible_ql_values')
def quality_test_check(self):
if self.question_type == 'qualitative':
self.success = self.qualitative_value.ok
else:
if self.uom_id.id == self.test_uom_id.id:
amount = self.quantitative_value
else:
amount = self.env['product.uom']._compute_qty(
self.uom_id.id, self.quantitative_value,
self.test_uom_id.id)
self.success = self.max_value >= amount >= self.min_value
@api.one
@api.depends('possible_ql_values', 'min_value', 'max_value', 'test_uom_id',
'question_type')
def get_valid_values(self):
if self.question_type == 'qualitative':
self.valid_values = ", ".join([x.name for x in
self.possible_ql_values if x.ok])
else:
self.valid_values = "%s-%s" % (self.min_value, self.max_value)
if self.env.ref("product.group_uom") in self.env.user.groups_id:
self.valid_values += " %s" % self.test_uom_id.name
inspection_id = fields.Many2one(
comodel_name='qc.inspection', string='Inspection')
name = fields.Char(string="Question", readonly=True)
product = fields.Many2one(
comodel_name="product.product", related="inspection_id.product",
store=True)
test_line = fields.Many2one(
comodel_name='qc.test.question', string='Test question',
readonly=True)
possible_ql_values = fields.Many2many(
comodel_name='qc.test.question.value', string='Answers')
quantitative_value = fields.Float(
'Quantitative value', digits=(16, 5),
help="Value of the result if it's a quantitative question.")
qualitative_value = fields.Many2one(
comodel_name='qc.test.question.value', string='Qualitative value',
help="Value of the result if it's a qualitative question.",
domain="[('id', 'in', possible_ql_values[0][2])]")
notes = fields.Text(string='Notes')
min_value = fields.Float(
string='Min', digits=(16, 5), readonly=True,
help="Minimum valid value if it's a quantitative question.")
max_value = fields.Float(
string='Max', digits=(16, 5), readonly=True,
help="Maximum valid value if it's a quantitative question.")
test_uom_id = fields.Many2one(
comodel_name='product.uom', string='Test UoM', readonly=True,
help="UoM for minimum and maximum values if it's a quantitative "
"question.")
test_uom_category = fields.Many2one(
comodel_name="product.uom.categ", related="test_uom_id.category_id",
store=True)
uom_id = fields.Many2one(
comodel_name='product.uom', string='UoM',
domain="[('category_id', '=', test_uom_category)]",
help="UoM of the inspection value if it's a quantitative question.")
question_type = fields.Selection(
[('qualitative', 'Qualitative'),
('quantitative', 'Quantitative')],
string='Question type', readonly=True)
valid_values = fields.Char(string="Valid values", store=True,
compute="get_valid_values")
success = fields.Boolean(
compute="quality_test_check", string="Success?", store=True)

View File

@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
from openerp import models, fields, api, exceptions, _
class QcTest(models.Model):
"""A test is a group of questions to with the values that make them valid.
"""
_name = 'qc.test'
_description = 'Quality control test'
@api.multi
def _links_get(self):
link_obj = self.env['res.request.link']
return [(r.object, r.name) for r in link_obj.search([])]
active = fields.Boolean('Active', default=True)
name = fields.Char(
string='Name', required=True, translate=True, select=True)
test_lines = fields.One2many(
comodel_name='qc.test.question', inverse_name='test',
string='Questions', copy=True)
object_id = fields.Reference(
string='Reference object', selection=_links_get,)
fill_correct_values = fields.Boolean(
string='Pre-fill with correct values')
type = fields.Selection(
[('generic', 'Generic'),
('related', 'Related')],
string='Type', select=True, required=True, default='generic')
category = fields.Many2one(
comodel_name='qc.test.category', string='Category')
company_id = fields.Many2one(
comodel_name='res.company', string='Company',
default=lambda self: self.env['res.company']._company_default_get(
'qc.test'))
class QcTestQuestion(models.Model):
"""Each test line is a question with its valid value(s)."""
_name = 'qc.test.question'
_description = 'Quality control question'
_order = 'sequence, id'
@api.one
@api.constrains('ql_values')
def _check_valid_answers(self):
if self.type == 'quantitative':
return
for value in self.ql_values:
if value.ok:
return
raise exceptions.Warning(
_("There isn't any value with OK marked. You have to mark at "
"least one."))
@api.one
@api.constrains('min_value', 'max_value')
def _check_valid_range(self):
if self.type == 'qualitative':
return
if self.min_value > self.max_value:
raise exceptions.Warning(
_("Minimum value can't be higher than maximum value."))
sequence = fields.Integer(
string='Sequence', required=True, default="10")
test = fields.Many2one(comodel_name='qc.test', string='Test')
name = fields.Char(
string='Name', required=True, select=True, translate=True)
type = fields.Selection(
[('qualitative', 'Qualitative'),
('quantitative', 'Quantitative')], string='Type', required=True)
ql_values = fields.One2many(
comodel_name='qc.test.question.value', inverse_name="test_line",
string='Qualitative values', copy=True)
notes = fields.Text(string='Notes')
min_value = fields.Float(string='Min', digits=(16, 5))
max_value = fields.Float(string='Max', digits=(15, 5))
uom_id = fields.Many2one(comodel_name='product.uom', string='Uom')
class QcTestQuestionValue(models.Model):
_name = 'qc.test.question.value'
_description = 'Possible values of qualitative questions.'
test_line = fields.Many2one(
comodel_name="qc.test.question", string="Test question")
name = fields.Char(
string='Name', required=True, select=True, translate=True)
ok = fields.Boolean(
string='Correct answer?',
help="When this field is marked, the answer is considered correct.")

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
from openerp import models, fields, api, exceptions, _
class QcTestTemplateCategory(models.Model):
_name = 'qc.test.category'
_description = 'Test category'
@api.one
@api.depends('name', 'parent_id')
def _get_complete_name(self):
if self.name:
names = [self.name]
parent = self.parent_id
while parent:
names.append(parent.name)
parent = parent.parent_id
self.complete_name = " / ".join(reversed(names))
else:
self.complete_name = ""
@api.constrains('parent_id')
def _check_recursion(self):
ids = self.ids
level = 100
while ids:
parents = self.search([('id', 'in', ids),
('parent_id', '!=', False)])
ids = list(set([x.parent_id.id for x in parents]))
if not level:
raise exceptions.Warning(
_('Error ! You can not create recursive categories.'))
level -= 1
name = fields.Char('Name', required=True, translate=True)
parent_id = fields.Many2one(
comodel_name='qc.test.category', string='Parent category', select=True)
complete_name = fields.Char(
compute="_get_complete_name", string='Full name')
child_ids = fields.One2many(
comodel_name='qc.test.category', inverse_name='parent_id',
string='Child categories')
active = fields.Boolean(
string='Active', default=True,
help="This field allows you to hide the category without removing it.")

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
from openerp import models, fields
class QcTrigger(models.Model):
_name = 'qc.trigger'
_description = 'Quality control trigger'
name = fields.Char(string='Name', required=True, select=True,
translate=True)
active = fields.Boolean(string='Active', default=True)
company_id = fields.Many2one(
comodel_name='res.company', string='Company',
default=lambda self: self.env['res.company']._company_default_get(
'qc.test'))

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
##############################################################################
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
from openerp import models, fields
class QcTriggerLine(models.AbstractModel):
_name = "qc.trigger.line"
_description = "Abstract line for defining triggers"
trigger = fields.Many2one(comodel_name="qc.trigger", required=True)
test = fields.Many2one(comodel_name="qc.test", required=True)
def get_test_for_product(self, trigger, product):
"""Overridable method for getting test associated to a product.
Each inherited model will complete this module to make the search by
product, template or category.
:param trigger: Trigger instance.
:param product: Product instance.
:return: Set of tests that matches to the given product and trigger.
"""
return set()
class QcTriggerProductCategoryLine(models.Model):
_inherit = "qc.trigger.line"
_name = "qc.trigger.product_category_line"
product_category = fields.Many2one(comodel_name="product.category")
def get_test_for_product(self, trigger, product):
tests = super(QcTriggerProductCategoryLine,
self).get_test_for_product(trigger, product)
category = product.categ_id
while category:
for trigger_line in category.qc_triggers:
if trigger_line.trigger.id == trigger.id:
tests.add(trigger_line.test)
category = category.parent_id
return tests
class QcTriggerProductTemplateLine(models.Model):
_inherit = "qc.trigger.line"
_name = "qc.trigger.product_template_line"
product_template = fields.Many2one(comodel_name="product.template")
def get_test_for_product(self, trigger, product):
tests = super(QcTriggerProductTemplateLine,
self).get_test_for_product(trigger, product)
for trigger_line in product.product_tmpl_id.qc_triggers:
if trigger_line.trigger.id == trigger.id:
tests.add(trigger_line.test)
return tests
class QcTriggerProductLine(models.Model):
_inherit = "qc.trigger.line"
_name = "qc.trigger.product_line"
product = fields.Many2one(comodel_name="product.product")
def get_test_for_product(self, trigger, product):
tests = super(QcTriggerProductLine, self).get_test_for_product(
trigger, product)
for trigger_line in product.qc_triggers:
if trigger_line.trigger.id == trigger.id:
tests.add(trigger_line.test)
return tests

View File

@@ -1,638 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010 NaN Projectes de Programari Lliure, S.L.
# All Rights Reserved.
# http://www.NaN-tic.com
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from openerp.tools.translate import _
from openerp.osv import orm, fields
import time
class QcProofMethod(orm.Model):
"""
This model stores a method for doing a test. Examples of methods are:
"Eu.Pharm.v.v. (2.2.32)" or "HPLC"
"""
_name = 'qc.proof.method'
_description = 'Method'
_columns = {
'name': fields.char('Name', size=100, required=True, select="1",
translate=True),
'active': fields.boolean('Active'),
}
_defaults = {
'active': True,
}
class QcPosibleValue(orm.Model):
"""
This model stores all possible values of qualitative proof.
"""
_name = 'qc.posible.value'
_columns = {
'name': fields.char('Name', size=200, required=True, select="1",
translate=True),
'active': fields.boolean('Active'),
'ok': fields.boolean('Correct answer',
help="When this field is marked, the answer "
"is considered correct."),
}
_defaults = {
'active': True,
}
def search(self, cr, uid, args, offset=0, limit=None, order=None,
context=None, count=False):
if context is None:
context = {}
if context.get('proof_id'):
ctx = context.copy()
del ctx['proof_id']
proof = self.pool['qc.proof'].browse(cr, uid, context['proof_id'],
ctx)
result = [x.id for x in proof.value_ids]
args = args[:]
args.append(('id', 'in', result))
return super(QcPosibleValue, self).search(cr, uid, args, offset, limit,
order, context, count)
class QcProof(orm.Model):
"""
This model stores proofs which will be part of a test. Proofs are
classified between qualitative (such as color) and quantitative (such as
density).
Proof must be related with method, and Poof-Method relation must be unique
A name_search on thish model will search on 'name' field but also on any of
its synonyms.
"""
_name = 'qc.proof'
def _synonyms(self, cr, uid, ids, field_name, arg, context=None):
result = {}
for proof in self.browse(cr, uid, ids, context=context):
texts = []
for syn in proof.synonym_ids:
texts.append(syn.name)
result[proof.id] = ', '.join(texts)
return result
_columns = {
'name': fields.char('Name', size=200, required=True, select="1",
translate=True),
'ref': fields.char('Code', size=30, select="1"),
'active': fields.boolean('Active'),
'synonym_ids': fields.one2many('qc.proof.synonym', 'proof_id',
'Synonyms'),
'type': fields.selection([('qualitative', 'Qualitative'),
('quantitative', 'Quantitative')], 'Type',
select="1", required=True),
'value_ids': fields.many2many('qc.posible.value',
'qc_proof_posible_value_rel', 'proof_id',
'posible_value_id', 'Posible Values'),
'synonyms': fields.function(_synonyms, method=True, type='char',
string='Synonyms', store=False),
}
_defaults = {
'active': True,
}
def name_search(self, cr, uid, name='', args=None, operator='ilike',
context=None, limit=None):
result = super(QcProof, self).name_search(cr, uid, name=name,
args=args, operator=operator,
context=context, limit=limit)
synonym_obj = self.pool['qc.proof.synonym']
if name:
ids = [x[0] for x in result]
new_ids = []
syns = synonym_obj.name_search(cr, uid, name=name, args=args,
operator=operator, context=context,
limit=limit)
syns = [x[0] for x in syns]
for syn in synonym_obj.browse(cr, uid, syns, context=context):
if syn.proof_id.id not in ids:
new_ids.append(syn.proof_id.id)
result += self.name_get(cr, uid, new_ids, context=context)
return result
# def name_get(self, cr, uid, ids, context=None):
# result = []
# for proof in self.browse(cr, uid, ids, context=context):
# text = proof.name
# if proof.synonym_ids:
# text += " [%s]" % proof.synonyms
# result.append((proof.id, text))
# return result
class QcProofSynonym(orm.Model):
"""
Proofs may have synonyms. These are used because suppliers may use
different names for the same proof.
"""
_name = 'qc.proof.synonym'
_columns = {
'name': fields.char('Name', size=200, required=True, select="1",
translate=True),
'proof_id': fields.many2one('qc.proof', 'Proof', required=True),
}
class QcTestTemplateCategory(orm.Model):
"""
This model is used to categorize proof templates.
"""
_name = 'qc.test.template.category'
_description = 'Test Template Category'
def name_get(self, cr, uid, ids, context=None):
if not len(ids):
return []
reads = self.read(cr, uid, ids, ['name', 'parent_id'], context=context)
res = []
for record in reads:
name = record['name']
if record['parent_id']:
name = record['parent_id'][1] + ' / ' + name
res.append((record['id'], name))
return res
def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
res = self.name_get(cr, uid, ids, context=context)
return dict(res)
def _check_recursion(self, cr, uid, ids):
level = 100
while len(ids):
cr.execute('SELECT DISTINCT parent_id FROM'
' qc_test_template_category WHERE id IN (' +
','.join(map(str, ids)) + ')')
ids = [x[0] for x in cr.fetchall() if x[0] is not None]
if not level:
return False
level -= 1
return True
_columns = {
'name': fields.char('Category Name', required=True, size=64,
translate=True),
'parent_id': fields.many2one('qc.test.template.category',
'Parent Category', select=True),
'complete_name': fields.function(_name_get_fnc, method=True,
type="char", string='Full Name'),
'child_ids': fields.one2many('qc.test.template.category', 'parent_id',
'Child Categories'),
'active': fields.boolean('Active',
help="The active field allows you to hide the"
" category without removing it."),
}
_constraints = [
(_check_recursion,
_('Error ! You can not create recursive categories.'), ['parent_id'])
]
_defaults = {
'active': 1,
}
class QcTestTemplateTrigger(orm.Model):
_name = 'qc.test.template.trigger'
_description = 'Test Template Trigger'
_columns = {
'name': fields.char('Name', size=64, required=True, readonly=False,
translate=True),
'active': fields.boolean('Active', required=False),
}
_defaults = {
'active': 1,
}
class QcTestTemplate(orm.Model):
"""
A template is a group of proofs to with the values that make them valid.
"""
_name = 'qc.test.template'
_description = 'Test Template'
def _links_get(self, cr, uid, context=None):
# TODO: Select models
link_obj = self.pool['res.request.link']
ids = link_obj.search(cr, uid, [], context=context)
res = link_obj.read(cr, uid, ids, ['object', 'name'], context=context)
return [(r['object'], r['name']) for r in res]
def _default_name(self, cr, uid, context=None):
if context and context.get('reference_model', False):
if 'reference_id' in context:
source = self.pool[context['reference_model']].browse(
cr, uid, context.get('active_id'), context=context)
if hasattr(source, 'name'):
return source.name
def _default_object_id(self, cr, uid, context=None):
if context and context.get('reference_model', False):
return '%s,%d' % (context['reference_model'],
context['reference_id'])
else:
return False
def _default_type(self, cr, uid, context=None):
if context and context.get('reference_model'):
return 'related'
else:
return False
_columns = {
'active': fields.boolean('Active', select="1"),
'name': fields.char('Name', size=200, required=True, translate=True,
select="1"),
'test_template_line_ids': fields.one2many('qc.test.template.line',
'test_template_id', 'Lines'),
'object_id': fields.reference('Reference Object', selection=_links_get,
size=128),
'fill_correct_values': fields.boolean('Fill With Correct Values'),
'type': fields.selection([('generic', 'Generic'),
('related', 'Related')], 'Type', select="1"),
'category_id': fields.many2one('qc.test.template.category',
'Category'),
'trig_on': fields.many2one('qc.test.template.trigger', 'Trigger'),
}
_defaults = {
'name': _default_name,
'active': True,
'object_id': _default_object_id,
'type': _default_type,
}
class QcTestTemplateLine(orm.Model):
"""
Each test template line has a reference to a proof and the valid
value/values.
"""
_name = 'qc.test.template.line'
_description = 'Test Template Line'
_order = 'sequence asc'
_rec_name = 'sequence'
def onchange_proof_id(self, cr, uid, ids, proof_id, context=None):
if not proof_id:
return {}
proof = self.pool['qc.proof'].browse(cr, uid, proof_id,
context=context)
return {'value': {'type': proof.type}}
_columns = {
'sequence': fields.integer('Sequence', required=True),
'test_template_id': fields.many2one('qc.test.template',
'Test Template', select="1"),
'proof_id': fields.many2one('qc.proof', 'Question', required=True,
select="1"),
'valid_value_ids': fields.many2many('qc.posible.value',
'qc_template_value_rel',
'template_line_id', 'value_id',
'Answers'),
'method_id': fields.many2one('qc.proof.method', 'Method', select="1"),
'notes': fields.text('Notes'),
'min_value': fields.float('Min', digits=(16, 5)), # Quantitative only
'max_value': fields.float('Max', digits=(15, 5)), # Quantitative only
'uom_id': fields.many2one('product.uom', 'Uom'), # Quantitative only
'type': fields.selection([('qualitative', 'Qualitative'),
('quantitative', 'Quantitative')],
'Type', select="1"),
}
_defaults = {
'sequence': 1,
}
class QcTest(orm.Model):
"""
This model contains an instance of a test template.
"""
_name = 'qc.test'
_inherit = ['mail.thread', 'ir.needaction_mixin']
def _success(self, cr, uid, ids, field_name, arg, context=None):
result = {}
for test in self.browse(cr, uid, ids, context=context):
success = False
if len(test.test_line_ids):
success = True
proof = {}
for line in test.test_line_ids:
proof[line.proof_id.id] = (proof.get(line.proof_id.id,
False)
or line.success)
for p in proof:
if not proof[p]:
success = False
break
result[test.id] = success
return result
def _links_get(self, cr, uid, context=None):
# TODO: Select models
link_obj = self.pool['res.request.link']
ids = link_obj.search(cr, uid, [], context=context)
res = link_obj.read(cr, uid, ids, ['object', 'name'], context=context)
return [(r['object'], r['name']) for r in res]
def _default_object_id(self, cr, uid, context=None):
if context and context.get('reference_model', False):
return '%s,%d' % (context['reference_model'],
context['reference_id'])
else:
return False
_columns = {
'name': fields.datetime('Date', required=True, readonly=True,
states={'draft': [('readonly', False)]},
select="1"),
'object_id': fields.reference('Reference', selection=_links_get,
size=128, readonly=True,
states={'draft': [('readonly', False)]},
select="1"),
'test_template_id': fields.many2one('qc.test.template', 'Test',
states={'success':
[('readonly', True)],
'failed':
[('readonly', True)]},
select="1"),
'test_line_ids': fields.one2many('qc.test.line', 'test_id',
'Test Lines',
states={'success':
[('readonly', True)],
'failed':
[('readonly', True)]}),
'test_internal_note': fields.text('Internal Note',
states={'success':
[('readonly', True)],
'failed':
[('readonly', True)]}),
'test_external_note': fields.text('External Note',
states={'success':
[('readonly', True)],
'failed':
[('readonly', True)]}),
'state': fields.selection([
('draft', 'Draft'),
('waiting', 'Waiting Supervisor Approval'),
('success', 'Quality Success'),
('failed', 'Quality Failed'),
('canceled', 'Canceled'),
], 'State', readonly=True, select="1"),
'success': fields.function(_success, method=True, type='boolean',
string='Success',
help='This field will be active if all'
' tests have succeeded.', select="1",
store=True),
'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.', select="1"),
}
_defaults = {
'name': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'state': 'draft',
'object_id': _default_object_id,
'enabled': True,
}
def copy(self, cr, uid, copy_id, default=None, context=None):
if context is None:
context = {}
if default is None:
default = {}
default['name'] = time.strftime('%Y-%m-%d %H:%M:%S')
return super(QcTest, self).copy(cr, uid, copy_id, default,
context=context)
def create(self, cr, uid, datas, context=None):
if context and context.get('reference_model', False):
datas['object_id'] = (context['reference_model'] + "," +
str(context['reference_id']))
return super(orm.Model, self).create(cr, uid, datas, context=context)
def qc_test_success(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {
'state': 'success'
}, context=context)
return True
def qc_test_failed(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {
'state': 'failed'
}, context=context)
return True
def test_state(self, cr, uid, ids, mode, *args):
quality_check = False
if mode == 'failed':
return not quality_check
if mode == 'success':
return quality_check
return False
def set_test_template(self, cr, uid, ids, template_id, force_fill=False,
context=None):
if context is None:
context = {}
test_obj = self.pool['qc.test']
test_line_obj = self.pool['qc.test.line']
for test_id in ids:
test_obj.write(cr, uid, test_id, {'test_template_id': template_id},
context)
test = test_obj.browse(cr, uid, test_id, context=context)
if len(test.test_line_ids) > 0:
test_line_obj.unlink(cr, uid,
[x.id for x in test.test_line_ids],
context=context)
test_lines = self._prepare_test_lines(
cr, uid, test, force_fill=force_fill, context=context)
if test_lines:
test_obj.write(cr, uid, id, {'test_line_ids': test_lines},
context)
def _prepare_test_lines(self, cr, uid, test, force_fill=False,
context=None):
new_data = []
fill = test.test_template_id.fill_correct_values
for line in test.test_template_id.test_template_line_ids:
data = self._prepare_test_line(
cr, uid, test, line, fill=fill or force_fill, context=context)
new_data.append((0, 0, data))
return new_data
def _prepare_test_line(self, cr, uid, test, line, fill=None, context=None):
data = {}
data = {'test_id': test.id,
'method_id': line.method_id.id,
'proof_id': line.proof_id.id,
'test_template_line_id': line.id,
'notes': line.notes,
'min_value': line.min_value,
'max_value': line.max_value,
'uom_id': line.uom_id.id,
'test_uom_id': line.uom_id.id,
'proof_type': line.type,
}
if fill:
if line.type == 'qualitative':
# Fill with the first correct value found.
data['actual_value_ql'] = (
len(line.valid_value_ids) and
line.valid_value_ids[0] and
line.valid_value_ids[0].id or False)
else:
# Fill with value inside range.
data['actual_value_qt'] = line.min_value
data['test_uom_id'] = line.uom_id.id
data['valid_value_ids'] = [(6, 0, [x.id for x in
line.valid_value_ids])]
return data
class QcTestLine(orm.Model):
"""
Each test line has a value and a reference to a proof template line.
"""
_name = 'qc.test.line'
_rec_name = 'proof_id'
def quality_test_check(self, cr, uid, ids, field_name, field_value,
context=None):
res = {}
lines = self.browse(cr, uid, ids, context=None)
for line in lines:
if line.proof_type == 'qualitative':
res[line.id] = self.quality_test_qualitative_check(
cr, uid, line, context=None)
else:
res[line.id] = self.quality_test_quantitative_check(
cr, uid, line, context=None)
return res
def quality_test_qualitative_check(self, cr, uid, test_line, context=None):
if test_line.actual_value_ql in test_line.valid_value_ids:
return test_line.actual_value_ql.ok
else:
return False
def quality_test_quantitative_check(self, cr, uid, test_line,
context=None):
amount = self.pool['product.uom']._compute_qty(
cr, uid, test_line.uom_id.id, test_line.actual_value_qt,
test_line.test_uom_id.id)
if amount >= test_line.min_value and amount <= test_line.max_value:
return True
else:
return False
_columns = {
'test_id': fields.many2one('qc.test', 'Test'),
'test_template_line_id': fields.many2one('qc.test.template.line',
'Test Template Line',
readonly=True),
'proof_id': fields.many2one('qc.proof', 'Question', readonly=True),
'method_id': fields.many2one('qc.proof.method', 'Method',
readonly=True),
'valid_value_ids': fields.many2many('qc.posible.value',
'qc_test_value_rel',
'test_line_id', 'value_id',
'Answers'),
'actual_value_qt': fields.float('Qt.Value', digits=(16, 5),
help="Value of the result if it is a"
" quantitative proof."),
'actual_value_ql': fields.many2one('qc.posible.value', 'Ql.Value',
help="Value of the result if it is"
" a qualitative proof."),
'notes': fields.text('Notes', readonly=True),
'min_value': fields.float('Min', digits=(16, 5), readonly=True,
help="Minimum valid value if it is a"
" quantitative proof."),
'max_value': fields.float('Max', digits=(16, 5), readonly=True,
help="Maximum valid value if it is a"
" quantitative proof."),
'uom_id': fields.many2one('product.uom', 'Uom', readonly=True,
help="UoM for minimum and maximum values if"
" it is a quantitative proof."),
'test_uom_id': fields.many2one('product.uom', 'Uom Test',
help="UoM of the value of the result"
" if it is a quantitative proof."),
'proof_type': fields.selection([('qualitative', 'Qualitative'),
('quantitative', 'Quantitative')],
'Proof Type', readonly=True),
'success': fields.function(quality_test_check, type='boolean',
method=True, string="Success?", select="1"),
}
def onchange_actual_value_qt(self, cr, uid, ids, uom_id, test_uom_id,
actual_value_qt, min_value, max_value,
context=None):
res = {}
if actual_value_qt:
amount = self.pool['product.uom']._compute_qty(cr, uid, uom_id,
actual_value_qt,
test_uom_id)
if amount >= min_value and amount <= max_value:
res['success'] = True
else:
res['success'] = False
return {'value': res}
def onchange_actual_value_ql(self, cr, uid, ids, actual_value_ql,
valid_value_ids, context=None):
res = {}
value_obj = self.pool['qc.posible.value']
if actual_value_ql:
valid = valid_value_ids[0][2]
if actual_value_ql in valid:
value = value_obj.browse(cr, uid, actual_value_ql, context)
res['success'] = value.ok
else:
res['success'] = False
return {'value': res}