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 @@ -