trytond-agronomics/production.py

385 lines
15 KiB
Python
Raw Permalink Normal View History

2021-09-03 11:35:21 +02:00
# 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
2021-09-09 12:20:10 +02:00
from trytond.pyson import Eval, Bool, If
2021-09-03 11:35:21 +02:00
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',
2021-09-09 12:20:10 +02:00
'production_template', 'Complementary Products')
2021-09-03 11:35:21 +02:00
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]
2021-09-09 12:20:10 +02:00
uoms.append(category_uom)
if len(list(set(uoms))) > 1:
2021-09-03 11:35:21 +02:00
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')),
2021-09-09 12:20:10 +02:00
If((Eval('state').in_(['waiting', 'draft'])),
('product.quantity', '>', 0), ())],
2021-09-03 11:35:21 +02:00
states={
'invisible': ~Bool(Eval('production_template'))
2021-09-09 12:20:10 +02:00
}, depends=['allowed_enology_products', 'state'])
2021-09-03 11:35:21 +02:00
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')
2021-09-13 09:17:34 +02:00
OutputDistribution = Pool().get('production.output.distribution')
2021-09-03 11:35:21 +02:00
moves = []
delete = []
2021-09-13 09:17:34 +02:00
outputs = []
delete_outputs = []
2021-09-03 11:35:21 +02:00
for production in productions:
2021-09-15 15:54:57 +02:00
if not production.production_template:
continue
2021-09-13 09:17:34 +02:00
delete += [x for x in production.inputs]
2021-09-03 11:35:21 +02:00
input_quantity = 0
2021-09-13 09:17:34 +02:00
template_qty = production.production_template.quantity
2021-09-03 11:35:21 +02:00
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,
2021-09-09 12:20:10 +02:00
production.production_template.uom, round=True)
2021-09-03 11:35:21 +02:00
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,
2021-09-09 12:20:10 +02:00
production.production_template.uom, round=True)
2021-09-13 09:17:34 +02:00
qty = quantity * (input_quantity or 1) / template_qty
qty = enology.uom.round(qty)
2021-09-03 11:35:21 +02:00
move = production._move(production.picking_location,
production.location,
production.company,
enology.product,
enology.uom.id,
float(qty))
move.production_input = production
moves.append(move)
2021-09-13 09:17:34 +02:00
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)
2021-09-03 11:35:21 +02:00
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)
2021-09-15 11:51:00 +02:00
for x in self.inputs if x.product.template in
self.production_template.inputs])
2021-09-03 11:35:21 +02:00
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:
2021-09-15 11:51:00 +02:00
percent = round(input.quantity/total_output, 6)
2021-09-03 11:35:21 +02:00
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
2021-09-15 11:51:00 +02:00
new_variety.percent += variety.percent/100.0*percent
2021-09-03 11:35:21 +02:00
varieties[new_variety.variety] = new_variety
for key, variety in varieties.items():
2021-09-15 11:51:00 +02:00
variety.percent = "%.4f" % round(100.0*variety.percent, 4)
2021-09-03 11:35:21 +02:00
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)
2021-09-15 11:51:00 +02:00
move = production._move(
production.location,
2021-09-03 11:35:21 +02:00
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)
2021-09-13 09:17:34 +02:00
location = fields.Many2One('stock.location', 'Location',
states={
'required': Eval('production_state').in_(['done'])
}, depends=['production_state'])
2021-09-03 11:35:21 +02:00
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)),
2021-09-13 09:17:34 +02:00
depends=['unit_digits'])
initial_quantity_readonly = fields.Function(fields.Float('Initial Quantity',
2021-09-03 11:35:21 +02:00
digits=(16, Eval('unit_digits', 2)),
2021-09-13 09:17:34 +02:00
depends=['unit_digits']), 'on_change_with_initial_quantity_readonly')
final_quantity = fields.Float('Final Quantity',
2021-09-03 11:35:21 +02:00
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
2021-09-13 09:17:34 +02:00
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')
2021-09-15 11:51:00 +02:00
@fields.depends('production', '_parent_production.state')
2021-09-13 09:17:34 +02:00
def on_change_with_production_state(self, name=None):
return self.production and self.production.state
2021-09-03 11:35:21 +02:00
@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()
2021-09-15 11:51:00 +02:00
@fields.depends('product', 'location', 'initial_quantity')
2021-09-13 09:17:34 +02:00
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))
2021-09-03 11:35:21 +02:00
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