trytond-agronomics/production.py

385 lines
15 KiB
Python

# 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 trytond.model import ModelSQL, ModelView, fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval, Bool, If
from trytond.exceptions import UserError
from trytond.i18n import gettext
from decimal import Decimal
from trytond.transaction import Transaction
class ProductionTemplate(ModelSQL, ModelView):
"Produciton Template"
__name__ = 'production.template'
name = fields.Char('Name', required=True)
uom = fields.Many2One('product.uom', 'Uom', required=True)
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'on_change_with_unit_digits')
quantity = fields.Float('Quantity',
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
inputs = fields.Many2Many('production.template.inputs-product.template',
'production_template', 'template', "Inputs")
outputs = fields.Many2Many('production.template.outputs-product.template',
'production_template', 'template', "Outputs")
enology_products = fields.One2Many('production.template.line',
'production_template', 'Complementary Products')
pass_feature = fields.Boolean('Pass on Feature')
@fields.depends('uom')
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2
@classmethod
def check_input_uoms(cls, records):
for record in records:
category_uom = record.uom.category
uoms = [i.default_uom.category for i in record.inputs]
uoms.append(category_uom)
if len(list(set(uoms))) > 1:
raise UserError(gettext('agronomics.msg_uom_not_fit',
production=record.rec_name,
uom=record.uom.rec_name,
uoms=",".join([x.rec_name for x in set(uoms)])))
@classmethod
def validate(cls, records):
super().validate(records)
cls.check_input_uoms(records)
class ProductionTemplateInputsProductTemplate(ModelSQL):
'Production Template Inputs- Product Template'
__name__ = 'production.template.inputs-product.template'
production_template = fields.Many2One('production.template',
'Production Template', ondelete='CASCADE', required=True, select=True)
template = fields.Many2One('product.template', 'Template',
ondelete='CASCADE', required=True, select=True)
class ProductionTemplateOutputsProductTemplate(ModelSQL):
'Production Template Inputs- Product Template'
__name__ = 'production.template.outputs-product.template'
production_template = fields.Many2One('production.template',
'Production Template', ondelete='CASCADE', required=True, select=True)
template = fields.Many2One('product.template', 'Product',
ondelete='CASCADE', required=True, select=True)
class ProductionTemplateLine(ModelSQL, ModelView):
"Production Template Line"
__name__ = 'production.template.line'
product = fields.Many2One('product.product', 'Producte', required=True)
uom = fields.Many2One('product.uom', 'Uom')
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'on_change_with_unit_digits')
quantity = fields.Float('Quantity',
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
production_template = fields.Many2One('production.template',
'Production Template')
@fields.depends('uom')
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2
@fields.depends('product')
def on_change_with_uom(self):
if not self.product:
return
return self.product.default_uom and self.product.default_uom.id
class Production(metaclass=PoolMeta):
__name__ = 'production'
production_template = fields.Many2One('production.template',
'Production Template')
enology_products = fields.One2Many('production.enology.product',
'production', "Enology Products",
domain=[('product', 'in', Eval('allowed_enology_products')),
If((Eval('state').in_(['waiting', 'draft'])),
('product.quantity', '>', 0), ())],
states={
'invisible': ~Bool(Eval('production_template'))
}, depends=['allowed_enology_products', 'state'])
output_distribution = fields.One2Many('production.output.distribution',
'production', 'Output Distribution',
# domain=[('product', 'in', Eval('allowed_ouput_products'))],
states={
'invisible': ~Bool(Eval('production_template'))
}, depends=['allowed_output_products'])
allowed_enology_products = fields.Function(fields.One2Many(
'product.product', None, 'Allowed Enology Products', readonly=True),
'on_change_with_allowed_enology_products',
setter='set_allowed_products')
allowed_output_products = fields.Function(fields.One2Many(
'product.template', None, 'Allowed Output Products', readonly=True),
'on_change_with_allowed_output_products',
setter='set_allowed_products')
@classmethod
def set_allowed_products(cls, productions, name, value):
pass
@fields.depends('production_template')
def on_change_with_allowed_enology_products(self, name=None):
products = []
if not self.production_template:
return []
for template in self.production_template.inputs:
products += template.products
return [x.id for x in products]
@fields.depends('production_template')
def on_change_with_allowed_output_products(self, name=None):
if not self.production_template:
return []
return [x.id for x in self.production_template.outputs]
@classmethod
def wait(cls, productions):
Move = Pool().get('stock.move')
Uom = Pool().get('product.uom')
OutputDistribution = Pool().get('production.output.distribution')
moves = []
delete = []
outputs = []
delete_outputs = []
for production in productions:
if not production.production_template:
continue
delete += [x for x in production.inputs]
input_quantity = 0
template_qty = production.production_template.quantity
for enology in production.enology_products:
move = production._move(production.picking_location,
production.location,
production.company,
enology.product,
enology.uom.id,
enology.quantity)
move.production_input = production
moves.append(move)
input_quantity += Uom.compute_qty(enology.uom, enology.quantity,
production.production_template.uom, round=True)
enology_products = (production.production_template and
production.production_template.enology_products or [])
for enology in enology_products:
quantity = Uom.compute_qty(enology.uom, enology.quantity,
production.production_template.uom, round=True)
qty = quantity * (input_quantity or 1) / template_qty
qty = enology.uom.round(qty)
move = production._move(production.picking_location,
production.location,
production.company,
enology.product,
enology.uom.id,
float(qty))
move.production_input = production
moves.append(move)
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)
OutputDistribution.delete(delete_outputs)
OutputDistribution.save(outputs)
Move.save(moves)
Move.delete(delete)
super().wait(productions)
def create_variant(self, template, pass_feature):
Product = Pool().get('product.product')
product = Product()
product.template = template
return product
def pass_feature(self, product):
Variety = Pool().get('product.variety')
Uom = Pool().get('product.uom')
total_output = sum([Uom.compute_qty(x.uom, x.quantity,
x.product.default_uom)
for x in self.inputs if x.product.template in
self.production_template.inputs])
vintages = []
do = []
ecologicals = []
for input in self.inputs:
vintages += input.product.vintages
do += input.product.denominations_of_origin
ecologicals = input.product.ecologicals
product.denominations_of_origin = list(set(do))
product.ecologicals = list(set(ecologicals))
product.vintages = list(set(vintages))
varieties = {}
for input in self.inputs:
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
varieties[new_variety.variety] = new_variety
for key, variety in varieties.items():
variety.percent = "%.4f" % round(100.0*variety.percent, 4)
product.varieties = varieties.values()
return product
@classmethod
def done(cls, productions):
Move = Pool().get('stock.move')
moves = []
for production in productions:
for distrib in production.output_distribution:
product = production.create_variant(distrib.product,
production.production_template.pass_feature)
product = production.pass_feature(product)
move = production._move(
production.location,
distrib.location,
production.company,
product,
distrib.uom,
distrib.produced_quantity)
move.production_output = production
move.unit_price = Decimal(0)
moves.append(move)
Move.save(moves)
super().done(productions)
class OutputDistribution(ModelSQL, ModelView):
'Output Distribution'
__name__ = 'production.output.distribution'
production = fields.Many2One('production', 'Production',
required=True)
product = fields.Many2One('product.template', 'Template', required=True)
location = fields.Many2One('stock.location', 'Location',
states={
'required': Eval('production_state').in_(['done'])
}, depends=['production_state'])
uom = fields.Many2One('product.uom', 'Uom')
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'on_change_with_unit_digits')
initial_quantity = fields.Float('Initial Quantity',
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
initial_quantity_readonly = fields.Function(fields.Float('Initial Quantity',
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits']), 'on_change_with_initial_quantity_readonly')
final_quantity = fields.Float('Final Quantity',
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
produced_quantity = fields.Function(fields.Float('Produced Quantity',
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits']), 'on_change_with_produced_quantity')
production_state = fields.Function(fields.Selection([
('request', 'Request'), ('draft', 'Draft'), ('waiting', 'Waiting'),
('assigned', 'Assigned'), ('running', 'Running'), ('done', 'Done'),
('cancelled', 'Cancelled')], 'State'),
'on_change_with_production_state')
@fields.depends('production', '_parent_production.state')
def on_change_with_production_state(self, name=None):
return self.production and self.production.state
@fields.depends('product')
def on_change_with_uom(self):
if not self.product:
return
return self.product.default_uom and self.product.default_uom.id
@fields.depends('uom')
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2
@fields.depends('initial_quantity', 'final_quantity', 'produced_quantity',
'location', 'product')
def on_change_product(self):
Product = Pool().get('product.product')
if not self.product:
self.initial_quantity = 0
return
if not self.location:
return
context = Transaction().context
context['locations'] = [self.location.id]
with Transaction().set_context(context):
quantities = Product.get_quantity(self.product.products, 'quantity')
self.initial_quantity = sum(quantities.values())
@fields.depends('location', methods=['on_change_product'])
def on_change_location(self):
if not self.location:
return
self.on_change_product()
@fields.depends('product', 'location', 'initial_quantity')
def on_change_with_initial_quantity_readonly(self, name=None):
Product = Pool().get('product.product')
if not self.product or not self.location:
return
if self.initial_quantity:
return self.initial_quantity
context = Transaction().context
context['locations'] = [self.location.id]
with Transaction().set_context(context):
quantities = Product.get_quantity(self.product.products, 'quantity')
return sum(quantities.values())
@fields.depends('final_quantity', 'initial_quantity')
def on_change_with_produced_quantity(self, name=None):
return ((self.final_quantity or 0) -
(self.initial_quantity or 0))
class ProductionEnologyProduct(ModelSQL, ModelView):
'Production Enology Product'
__name__ = 'production.enology.product'
production = fields.Many2One('production', 'Production',
select=True)
product = fields.Many2One('product.product', 'Product', required=True)
uom = fields.Many2One('product.uom', 'Uom')
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'on_change_with_unit_digits')
quantity = fields.Float('Quantity',
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
@fields.depends('uom')
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2
@fields.depends('product')
def on_change_with_uom(self):
if not self.product:
return
return self.product.default_uom and self.product.default_uom.id
@fields.depends('product')
def on_change_product(self):
if not self.product:
return
self.quantity = self.product.quantity