diff --git a/__init__.py b/__init__.py
index d8a8a6f..83518be 100644
--- a/__init__.py
+++ b/__init__.py
@@ -34,6 +34,10 @@ def register():
weighing.Weighing,
weighing.WeighingPlantation,
weighing.WeighingDo,
+ quality.Configuration,
+ quality.ConfigurationCompany,
+ quality.ProductQualitySample,
+ quality.QualitySample,
quality.QualityTest,
quality.QuantitativeTestLine,
quality.QualitativeTestLine,
diff --git a/product.py b/product.py
index e4c2a56..c7d1c8b 100644
--- a/product.py
+++ b/product.py
@@ -32,6 +32,7 @@ class Container(ModelSQL, ModelView):
class Template(metaclass=PoolMeta):
__name__ = 'product.template'
+ needs_sample = fields.Boolean('Needs Samples')
agronomic_type = fields.Selection([
(None, ''),
('grape', "Grape"),
@@ -89,13 +90,6 @@ class Product(WineMixin, metaclass=PoolMeta):
}, depends=['agronomic_type'])
ecologicals = fields.Many2Many('product.product-agronomics.ecological',
'product', 'ecological', 'Ecologicals')
- quality_sample = fields.Many2One('quality.sample', 'Quality Sample',
- states={
- 'invisible': ~ Eval('agronomic_type').in_(
- ['wine', 'unfiltered-wine', 'filtered-wine', 'clarified-wine',
- 'bottled-wine']
- )
- }, depends=['agronomic_type'])
certification = fields.Many2One('agronomics.certification',
'Certification', states={
'invisible': ~ Eval('agronomic_type').in_(
@@ -108,6 +102,9 @@ class Product(WineMixin, metaclass=PoolMeta):
['wine', 'unfiltered-wine', 'filtered-wine', 'clarified-wine',
'bottled-wine']
)}, depends=['agronomic_type']), 'get_alcohol_volume')
+ quality_tests = fields.One2Many('quality.test', 'document', 'Quality Tests')
+ quality_samples = fields.Many2Many('product.product-quality.sample',
+ 'product', 'sample', 'Quality Samples')
@classmethod
def validate(cls, products):
diff --git a/production.py b/production.py
index b49fd7b..801fc40 100644
--- a/production.py
+++ b/production.py
@@ -1,5 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
+from decimal import Decimal
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval, Bool, If
@@ -9,7 +10,6 @@ from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateView, StateAction, Button
from trytond.modules.product import round_price
from trytond.model.exceptions import ValidationError
-from decimal import Decimal
class ProductionTemplate(ModelSQL, ModelView):
@@ -30,6 +30,9 @@ class ProductionTemplate(ModelSQL, ModelView):
enology_products = fields.One2Many('production.template.line',
'production_template', 'Complementary Products')
pass_feature = fields.Boolean('Pass on Feature')
+ pass_quality = fields.Boolean('Pass Quality')
+ pass_certification = fields.Boolean('Pass Certification')
+ pass_quality_sample = fields.Boolean('Pass Quality Sample')
cost_distribution_template = fields.Many2One(
'production.cost_price.distribution.template',
"Default cost distribution template",
@@ -68,8 +71,8 @@ class ProductionTemplate(ModelSQL, ModelView):
return
output_templates = set([o for o in self.outputs])
- for c in self.cost_distribution_template.cost_distribution_templates:
- if c.template not in output_templates:
+ for cost in self.cost_distribution_template.cost_distribution_templates:
+ if cost.template not in output_templates:
raise ValidationError(
gettext('agronomics.msg_check_cost_distribution_template',
production=self.rec_name))
@@ -170,19 +173,39 @@ class Production(metaclass=PoolMeta):
'production.cost_price.distribution.template',
"Cost Distribution Template",
domain=[
- ('id', 'in', Eval('production_template_cost_distribution_templates'))
+ ('id', 'in',
+ Eval('production_template_cost_distribution_templates'))
], states={
'readonly': Eval('state').in_(['cancelled', 'done']),
- }, depends=['state', 'production_template_cost_distribution_templates'])
+ }, depends=['state',
+ 'production_template_cost_distribution_templates'])
cost_distribution_templates = fields.Function(
fields.Many2Many('product.template',
None, None, "Cost Product Templates"),
'on_change_with_cost_distribution_templates')
+ pass_quality = fields.Boolean('Pass Quality')
+ pass_certification = fields.Boolean('Pass Certification')
+ pass_quality_sample = fields.Boolean('Pass Quality Sample')
@classmethod
def set_allowed_products(cls, productions, name, value):
pass
+ @fields.depends('production_template')
+ def on_change_with_pass_quality(self):
+ if self.production_template:
+ return self.production_template.pass_quality
+
+ @fields.depends('production_template')
+ def on_change_with_pass_certification(self):
+ if self.production_template:
+ return self.production_template.pass_certification
+
+ @fields.depends('production_template')
+ def on_change_with_pass_quality_sample(self):
+ if self.production_template:
+ return self.production_template.pass_quality_sample
+
@fields.depends('production_template')
def on_change_production_template(self):
if (self.production_template and
@@ -195,8 +218,8 @@ class Production(metaclass=PoolMeta):
products = []
if not self.production_template:
return []
- for x in self.production_template.inputs:
- products += x.products
+ for input_ in self.production_template.inputs:
+ products += input_.products
return [x.id for x in products]
@fields.depends('production_template')
@@ -249,7 +272,7 @@ class Production(metaclass=PoolMeta):
gettext('agronomics.msg_check_production_percentatge',
production=self.rec_name,
percentatge=percentatge * 100,
- ))
+ ))
@classmethod
def wait(cls, productions):
@@ -300,11 +323,12 @@ class Production(metaclass=PoolMeta):
for output_product in production.production_template.outputs:
delete_outputs += [x for x in production.output_distribution]
- od = OutputDistribution()
- od.product = output_product
- od.uom = od.on_change_with_uom()
- od.production = production
- outputs.append(od)
+ output_distribution = OutputDistribution()
+ output_distribution.product = output_product
+ output_distribution.uom = (
+ output_distribution.on_change_with_uom())
+ output_distribution.production = production
+ outputs.append(output_distribution)
if not production.cost_distributions:
if production.cost_distribution_template:
@@ -335,6 +359,45 @@ class Production(metaclass=PoolMeta):
product.template = template
return product
+ def copy_certification(self, new_product):
+ products = [x.product for x in self.inputs if x.product.certification]
+ if not self.pass_certification or len(products) != 1:
+ return new_product
+ certification = products[0].certification
+ new_product.certification = certification
+ return new_product
+
+ def copy_quality_samples(self, new_product):
+ ProductSample = Pool().get('product.product-quality.sample')
+ products = [x.product for x in self.inputs if x.product.quality_samples]
+ if not self.pass_quality_sample or len(products) != 1:
+ return new_product
+ samples = products[0].quality_samples
+ new_samples =[]
+ for sample in samples:
+ product_sample = ProductSample()
+ product_sample.product = new_product
+ product_sample.sample = sample
+ new_samples.append(product_sample)
+ ProductSample.save(new_samples)
+ return new_product
+
+ def copy_quality(self, new_product):
+ Quality = Pool().get('quality.test')
+ products = [x.product for x in self.inputs]
+
+ if not self.pass_quality:
+ return
+
+ tests = []
+ for product in products:
+ if tests and product.quality_tests:
+ return
+ tests += product.quality_tests or []
+ new_tests = Quality.copy(tests, {'document': str(new_product)})
+ Quality.confirmed(new_tests)
+ Quality.manager_validate(new_tests)
+
def pass_feature(self, product):
Variety = Pool().get('product.variety')
Uom = Pool().get('product.uom')
@@ -355,17 +418,17 @@ class Production(metaclass=PoolMeta):
product.vintages = list(set(vintages))
varieties = {}
for input in self.inputs:
- percent = round(input.quantity/total_output, 6)
+ percent = round(input.quantity / total_output, 6)
for variety in input.product.varieties:
new_variety = varieties.get(variety.variety)
if not new_variety:
new_variety = Variety()
new_variety.percent = 0
new_variety.variety = variety.variety
- new_variety.percent += variety.percent/100.0*percent
+ new_variety.percent += variety.percent / 100.0 * percent
varieties[new_variety.variety] = new_variety
for key, variety in varieties.items():
- variety.percent = "%.4f" % round(100.0*variety.percent, 4)
+ variety.percent = "%.4f" % round(100.0 * variety.percent, 4)
product.varieties = varieties.values()
return product
@@ -378,6 +441,7 @@ class Production(metaclass=PoolMeta):
product = production.create_variant(distrib.product,
production.production_template.pass_feature)
product = production.pass_feature(product)
+ product = production.copy_certification(product)
move = production._move(
production.location,
distrib.location,
@@ -388,8 +452,13 @@ class Production(metaclass=PoolMeta):
move.production_output = production
move.unit_price = Decimal(0)
moves.append(move)
+
Move.save(moves)
super().done(productions)
+ for production in productions:
+ for output in production.outputs:
+ production.copy_quality(output.product)
+ production.copy_quality_samples(output.product)
@classmethod
def set_cost(cls, productions):
@@ -417,7 +486,8 @@ class Production(metaclass=PoolMeta):
if output.product not in products:
continue
has_product = True
- cost = production_cost * (1 + cdist.percentatge) - production_cost
+ cost = (production_cost * (1 + cdist.percentatge) -
+ production_cost)
output_cost += round_price(cost / Decimal(total_output))
output_cost = output_cost if has_product else Decimal(0)
@@ -492,7 +562,8 @@ class OutputDistribution(ModelSQL, ModelView):
context = Transaction().context
context['locations'] = [self.location.id]
with Transaction().set_context(context):
- quantities = Product.get_quantity(self.product.products, 'quantity')
+ quantities = Product.get_quantity(self.product.products,
+ 'quantity')
self.initial_quantity = sum(quantities.values())
@fields.depends('location', methods=['on_change_product'])
@@ -511,7 +582,8 @@ class OutputDistribution(ModelSQL, ModelView):
context = Transaction().context
context['locations'] = [self.location.id]
with Transaction().set_context(context):
- quantities = Product.get_quantity(self.product.products, 'quantity')
+ quantities = Product.get_quantity(self.product.products,
+ 'quantity')
return sum(quantities.values())
@fields.depends('final_quantity', 'initial_quantity')
@@ -557,7 +629,7 @@ class ProductionCostPriceDistribution(ModelSQL, ModelView):
__name__ = 'production.cost_price.distribution'
template = fields.Many2One('product.template', "Template", required=True,
ondelete='RESTRICT')
- origin = fields.Reference('Origin', selection='_get_models', required=True,)
+ origin = fields.Reference('Origin', selection='_get_models', required=True)
percentatge = fields.Numeric("Percentatge", digits=(16, 4), required=True)
@classmethod
diff --git a/quality.py b/quality.py
index 05c38ca..9f37d45 100644
--- a/quality.py
+++ b/quality.py
@@ -1,8 +1,117 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
+import datetime
from trytond.pool import PoolMeta, Pool
-from trytond.model import fields, Model
+from trytond.model import fields, Model, ModelSQL, ModelView, Workflow
+from trytond.pyson import Eval, Id
from trytond.modules.agronomics.wine import _WINE_DIGITS
+from trytond.transaction import Transaction
+
+class ConfigurationCompany(ModelSQL):
+ 'Company Quality configuration'
+ __name__ = 'quality.configuration.company'
+
+ company = fields.Many2One('company.company', 'Company')
+ sample_sequence = fields.Many2One('ir.sequence',
+ 'Sample Sequence', domain=[
+ ('company', 'in',
+ [Eval('context', {}).get('company', -1), None]),
+ ('sequence_type', '=', Id('agronomics',
+ 'sequence_type_sample')),
+ ])
+
+ @staticmethod
+ def default_company():
+ return Transaction().context.get('company')
+
+
+class Configuration(metaclass=PoolMeta):
+ __name__ = 'quality.configuration'
+
+ sample_sequence = fields.Function(fields.Many2One('ir.sequence',
+ 'Sample Sequence', domain=[
+ ('company', 'in',
+ [Eval('context', {}).get('company', -1), None]),
+ ('sequence_type', '=', Id('agronomics',
+ 'sequence_type_sample')),
+ ]),
+ 'get_company_config', setter='set_company_config')
+
+ @classmethod
+ def get_company_config(cls, configs, names):
+ pool = Pool()
+ CompanyConfig = pool.get('quality.configuration.company')
+ res = dict.fromkeys(names, {configs[0].id: None})
+ company_configs = CompanyConfig.search([], limit=1)
+ if len(company_configs) == 1:
+ company_config, = company_configs
+ for field_name in set(names):
+ value = getattr(company_config, field_name, None)
+ if value:
+ res[field_name] = {configs[0].id: value.id}
+ return res
+
+ @classmethod
+ def set_company_config(cls, configs, name, value):
+ pool = Pool()
+ CompanyConfig = pool.get('quality.configuration.company')
+ company_configs = CompanyConfig.search([], limit=1)
+ if len(company_configs) == 1:
+ company_config, = company_configs
+ else:
+ company_config = CompanyConfig()
+ setattr(company_config, name, value)
+ company_config.save()
+
+
+class QualitySample(ModelSQL, ModelView):
+ 'Quality Sample'
+ __name__ = 'quality.sample'
+
+ code = fields.Char('Code', select=True, readonly=True)
+ reference = fields.Char('Reference')
+ products = fields.Many2Many('product.product-quality.sample', 'sample',
+ 'product', 'Products')
+ collection_date = fields.DateTime('Collection Date', required=True)
+ company = fields.Many2One('company.company', 'Company', required=True,
+ select=True)
+
+ @staticmethod
+ def default_company():
+ return Transaction().context.get('company')
+
+ @staticmethod
+ def default_collection_date():
+ return datetime.datetime.now()
+
+ @classmethod
+ def create(cls, vlist):
+ pool = Pool()
+ Config = pool.get('quality.configuration')
+
+ sequence = Config(1).sample_sequence
+ for value in vlist:
+ if not value.get('code'):
+ value['code'] = sequence.get()
+ return super(QualitySample, cls).create(vlist)
+
+ @classmethod
+ def copy(cls, samples, default=None):
+ if default is None:
+ default = {}
+ else:
+ default = default.copy()
+ default['code'] = None
+ return super(QualitySample, cls).copy(samples, default=default)
+
+class ProductQualitySample(ModelSQL):
+ 'Product - Quality Sample'
+ __name__ = 'product.product-quality.sample'
+
+ product = fields.Many2One('product.product', 'Product', required=True,
+ ondelete='CASCADE', select=True)
+ sample = fields.Many2One('quality.sample', 'Sample', ondelete='CASCADE',
+ required=True, select=True)
class QualityTest(metaclass=PoolMeta):
diff --git a/quality.xml b/quality.xml
new file mode 100644
index 0000000..027866e
--- /dev/null
+++ b/quality.xml
@@ -0,0 +1,111 @@
+
+
+
+ quality.configuration
+
+ quality_configuration_form
+
+
+
+ Quality Configuration Company
+
+
+
+
+
+
+
+
+
+ Quality Sample
+
+
+
+
+
+
+
+
+
+
+
+ Sample
+
+
+
+
+ Quality Samples
+
+
+
+
+
+
+
+
+ quality.sample
+ form
+ quality_sample_form
+
+
+
+ quality.sample
+ tree
+ quality_sample_list
+
+
+
+ Samples
+ quality.sample
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Quality Sample
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/scenario_production_template.rst b/tests/scenario_production_template.rst
index b322a69..42ca49d 100644
--- a/tests/scenario_production_template.rst
+++ b/tests/scenario_production_template.rst
@@ -188,6 +188,7 @@ Create Production Template::
>>> production_template.inputs.append(productA.template)
>>> production_template.outputs.append(mostflor)
>>> production_template.outputs.append(mostprimeres)
+ >>> production_template.pass_quality = True
>>> line = ProductionTemplateLine()
>>> line.product = product2
>>> line.quantity = 100
@@ -202,6 +203,10 @@ Create Production Template::
>>> production_template.enology_products.append(line)
>>> production_template.save()
+Create Test:
+
+ >>> QualityTest = Model.get('quality.test')
+ >>> Test = QualityTest()
Create an Inventory::
@@ -315,3 +320,5 @@ Create Production
[('Parellada', 16.6667), ('Macabeu', 83.3334)]
>>> [x.name for x in most.product.denominations_of_origin]
['Catalunya']
+
+
diff --git a/tryton.cfg b/tryton.cfg
index 4ce2ddc..0492564 100644
--- a/tryton.cfg
+++ b/tryton.cfg
@@ -7,7 +7,7 @@ depends:
product_classification
product_classification_taxonomic
product_template_form_quantity
- quality_control_sample
+ quality_control
purchase_contract
production
xml:
@@ -18,3 +18,4 @@ xml:
wine.xml
message.xml
production.xml
+ quality.xml
\ No newline at end of file
diff --git a/view/product_form.xml b/view/product_form.xml
index e65f6aa..a4653e5 100644
--- a/view/product_form.xml
+++ b/view/product_form.xml
@@ -5,8 +5,6 @@
-
-
@@ -180,6 +178,7 @@
+
diff --git a/view/product_list.xml b/view/product_list.xml
index aa4f948..97e60c7 100644
--- a/view/product_list.xml
+++ b/view/product_list.xml
@@ -4,11 +4,11 @@
-
+
diff --git a/view/production_form.xml b/view/production_form.xml
index ed2c0ac..0a9975c 100644
--- a/view/production_form.xml
+++ b/view/production_form.xml
@@ -6,6 +6,14 @@ this repository contains the full copyright notices and license terms. -->
+
+
+
+
+
+
+
+
diff --git a/view/production_template_form.xml b/view/production_template_form.xml
index 9334054..d6c5272 100644
--- a/view/production_template_form.xml
+++ b/view/production_template_form.xml
@@ -4,12 +4,26 @@ this repository contains the full copyright notices and license terms. -->
diff --git a/view/quality_sample_list.xml b/view/quality_sample_list.xml
new file mode 100644
index 0000000..94b3722
--- /dev/null
+++ b/view/quality_sample_list.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/weighing.py b/weighing.py
index b3def24..6e01053 100644
--- a/weighing.py
+++ b/weighing.py
@@ -100,7 +100,11 @@ class Weighing(Workflow, ModelSQL, ModelView):
], "State", readonly=True, required=True)
state_string = state.translated('state')
all_do = fields.Function(fields.Char('All DO'), 'get_all_do')
- quality_test = fields.Many2One('quality.test', 'Test')
+ quality_test = fields.Function(fields.Many2One('quality.test', 'Test',
+ states={
+ 'readonly': Eval('state').in_(['done', 'cancelled']),
+ }),
+ 'get_quality_test', 'set_quality_test')
product_created = fields.Many2One('product.product', 'Product Created',
readonly=True)
@@ -157,6 +161,19 @@ class Weighing(Workflow, ModelSQL, ModelView):
def get_all_do(self, name):
return ",".join([x.name for x in self.denomination_origin])
+ def get_quality_test(self, name):
+ if not self.product_created:
+ return
+ tests = self.product_created.quality_tests
+ return tests and tests[0] and tests[0].id
+
+ @classmethod
+ def set_quality_test(cls, weighings, name, value):
+ Test = Pool().get('quality.test')
+ if not value:
+ return
+
+
@fields.depends('weighing_date')
def on_change_with_crop(self):
Crop = Pool().get('agronomics.crop')
@@ -252,19 +269,29 @@ class Weighing(Workflow, ModelSQL, ModelView):
def analysis(cls, weighings):
pool = Pool()
Product = pool.get('product.product')
+ Quality = pool.get('quality.test')
+ Variety = Pool().get('product.variety')
default_product_values = Product.default_get(Product._fields.keys(),
with_rec_name=False)
product = Product(**default_product_values)
for weighing in weighings:
product.template = weighing.product
product.denominations_of_origin = weighing.denomination_origin
- product.ecologicals = [weighing.ecological]
- product.varieties = [weighing.variety.id]
+ if weighing.ecological:
+ product.ecologicals = [weighing.ecological]
+ if weighing.variety:
+ new_variety = Variety()
+ new_variety.percent = 100
+ new_variety.variety = weighing.variety
+ product.varieties = [new_variety]
product.vintages = [weighing.crop.id]
weighing.product_created = product
- weighing.quality_test = weighing.create_quality_test()
cls.save(weighings)
+ tests = []
+ for weighing in weighings:
+ tests.append(weighing.create_quality_test())
+ Quality.save(tests)
def create_quality_test(self):
pool = Pool()
@@ -277,7 +304,7 @@ class Weighing(Workflow, ModelSQL, ModelView):
test = QualityTest(
test_date=datetime.now(),
templates=[template],
- document=str(self))
+ document=str(self.product_created))
test.apply_template_values()
return test
@@ -343,6 +370,7 @@ class Weighing(Workflow, ModelSQL, ModelView):
else:
default = default.copy()
default.setdefault('beneficiaries', None)
+ default.setdefault('product_created', None)
return super().copy(weighings, default=default)