kalenislims/lims_production/production.py

266 lines
9.3 KiB
Python

# -*- coding: utf-8 -*-
# This file is part of lims_production module for 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 fields
from trytond.pyson import Eval, Bool
from trytond.pool import PoolMeta, Pool
from trytond.transaction import Transaction
from trytond.report import Report
from trytond.exceptions import UserError
from trytond.i18n import gettext
class BOM(metaclass=PoolMeta):
__name__ = 'production.bom'
divide_lots = fields.Boolean('Divide lots')
class Production(metaclass=PoolMeta):
__name__ = 'production'
concentration = fields.Char('Concentration',
depends=['salable_product', 'state'], states={
'invisible': Bool(Eval('salable_product')),
'readonly': ~Eval('state').in_(['request', 'draft']),
})
preparation_date = fields.Date('Preparation date',
depends=['salable_product', 'state'], states={
'invisible': Bool(Eval('salable_product')),
'readonly': ~Eval('state').in_(['request', 'draft']),
})
expiration_date = fields.Date('Expiration date',
depends=['state'], states={
'readonly': ~Eval('state').in_(['request', 'draft']),
})
technician = fields.Many2One('lims.laboratory.professional',
'Technician', depends=['state'], states={
'readonly': ~Eval('state').in_(['request', 'draft']),
})
solvent = fields.Function(fields.Many2One('product.product',
'Solvent', depends=['salable_product', 'state'], states={
'invisible': Bool(Eval('salable_product')),
'readonly': ~Eval('state').in_(['request', 'draft']),
}), 'on_change_with_solvent')
salable_product = fields.Function(fields.Boolean('Salable',
depends=['product']),
'on_change_with_salable_product')
comments = fields.Text('Comments')
@fields.depends('product', '_parent_product.salable')
def on_change_with_salable_product(self, name=None):
if self.product:
return self.product.salable
return False
@fields.depends('inputs')
def on_change_with_solvent(self, name=None):
Config = Pool().get('lims.configuration')
config = Config(1)
solvent_domain = config.get_solvents()
if not solvent_domain:
return None
for input_ in self.inputs:
if (input_.product and input_.product.account_category and
(input_.product.account_category.id in solvent_domain)):
return input_.product.id
return None
def explode_bom(self):
pool = Pool()
Template = pool.get('product.template')
Product = pool.get('product.product')
super().explode_bom()
if not (self.bom and self.product and self.uom):
return
if not self.bom.divide_lots:
return
if self.quantity:
quantity = 0.0
for output in self.bom.outputs:
quantity += output.quantity
if not (self.quantity % quantity == 0):
raise UserError(
gettext('lims_production.msg_quantity_multiple_required'))
outputs = []
if self.warehouse:
storage_location = self.warehouse.storage_location
else:
storage_location = None
factor = self.bom.compute_factor(self.product, self.quantity or 0,
self.uom)
if hasattr(Product, 'cost_price'):
digits = Product.cost_price.digits
else:
digits = Template.cost_price.digits
for output in self.bom.outputs:
quantity = output.compute_quantity(factor)
line_qty = output.quantity
lines = int(quantity / line_qty)
for q in range(lines):
move = self._explode_move_values(self.location,
storage_location, self.company, output, line_qty)
if move:
move.unit_price = Decimal(0)
if output.product == move.product and quantity:
move.unit_price = Decimal(
self.cost / Decimal(str(quantity))
).quantize(Decimal(str(10 ** -digits[1])))
outputs.append(move)
self.outputs = outputs
@classmethod
def run(cls, productions):
cls.update_inputs_origin(productions)
super().run(productions)
@classmethod
def update_inputs_origin(cls, productions):
for p in productions:
for m in p.inputs:
m.origin = p
m.save()
@classmethod
def done(cls, productions):
cls.update_outputs_origin(productions)
cls.update_outputs_costs(productions)
cls.create_lot(productions)
super().done(productions)
@classmethod
def update_outputs_origin(cls, productions):
for p in productions:
for m in p.outputs:
m.origin = p
m.save()
@classmethod
def update_outputs_costs(cls, productions):
pool = Pool()
Template = pool.get('product.template')
Product = pool.get('product.product')
if hasattr(Product, 'cost_price'):
digits = Product.cost_price.digits
else:
digits = Template.cost_price.digits
for production in productions:
if production.bom:
divide_lots = production.bom.divide_lots
factor = production.bom.compute_factor(production.product,
production.quantity or 0, production.uom)
else:
divide_lots = False
cost_price = production.cost
for output in production.outputs:
if divide_lots:
quantity = output.uom.round(output.quantity * factor)
else:
quantity = output.uom.round(output.quantity)
if quantity:
output.unit_price = Decimal(0)
output.unit_price = Decimal(
cost_price / Decimal(str(quantity))
).quantize(Decimal(str(10 ** -digits[1])))
output.save()
@classmethod
def create_lot(cls, productions):
pool = Pool()
Config = pool.get('production.configuration')
Sequence = pool.get('ir.sequence')
Lot = pool.get('stock.lot')
Move = pool.get('stock.move')
config = Config(1)
for production in productions:
for move in production.outputs:
number = Sequence.get_id(config.lot_sequence.id)
lot = Lot(
number=number,
product=move.product,
concentration=production.concentration,
preparation_date=production.preparation_date,
expiration_date=production.expiration_date,
technician=(production.technician.id if
production.technician else None),
solvent=(production.solvent.id if production.solvent
else None),
)
if lot:
lot.save()
Move.write([move], {'lot': lot})
def _explode_move_values(self, from_location, to_location, company,
bom_io, quantity):
User = Pool().get('res.user')
product_location = None
for d in User(Transaction().user).departments:
if d.default:
for l in bom_io.product.locations:
if l.department == d.department:
product_location = l.location
break
break
if product_location:
if from_location == self.location:
to_location = product_location
else:
from_location = product_location
return super()._explode_move_values(from_location, to_location,
company, bom_io, quantity)
class FamilyEquivalentReport(Report):
'Family/Equivalent'
__name__ = 'lims.family.equivalent.report'
@classmethod
def get_context(cls, records, data):
pool = Pool()
Company = pool.get('company.company')
report_context = super().get_context(records, data)
report_context['company'] = Company(Transaction().context['company'])
report_context['records'] = cls._get_family_records(records)
report_context['compute_qty'] = cls.compute_qty
return report_context
@classmethod
def _get_family_records(cls, records):
pool = Pool()
Location = pool.get('stock.location')
Date_ = pool.get('ir.date')
FamilyEquivalent = pool.get('lims.family.equivalent')
locations = Location.search([
('type', '=', 'storage'),
])
context = {}
context['locations'] = [l.id for l in locations]
context['stock_date_end'] = Date_.today()
with Transaction().set_context(context):
res = FamilyEquivalent.browse(records)
return res
@classmethod
def compute_qty(cls, from_uom, qty, to_uom):
pool = Pool()
Uom = pool.get('product.uom')
return Uom.compute_qty(from_uom, qty, to_uom)