# This file is part of lims_analysis_sheet_stock module for Tryton. # The COPYRIGHT file at the top level of this repository contains # the full copyright notices and license terms. from trytond.model import ModelView, ModelSQL, fields, Unique from trytond.wizard import Wizard, StateTransition, StateView, Button from trytond.pyson import Eval, Id from trytond.pool import Pool, PoolMeta from trytond.transaction import Transaction from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.modules.lims_interface.interface import FUNCTIONS from .function import custom_functions FUNCTIONS.update(custom_functions) class TemplateAnalysisSheet(metaclass=PoolMeta): __name__ = 'lims.template.analysis_sheet' materials = fields.One2Many('lims.template.analysis_sheet.material', 'template', 'Materials') class TemplateAnalysisSheetMaterial(ModelSQL, ModelView): 'Template Material' __name__ = 'lims.template.analysis_sheet.material' template = fields.Many2One('lims.template.analysis_sheet', 'Template', required=True, ondelete='CASCADE', select=True) product = fields.Many2One('product.product', 'Product', required=True, domain=[ ('type', '!=', 'service'), ]) uom_category = fields.Function(fields.Many2One( 'product.uom.category', 'Uom Category'), 'on_change_with_uom_category') uom = fields.Many2One('product.uom', 'Uom', required=True, domain=[ ('category', '=', Eval('uom_category')), ], depends=['uom_category']) unit_digits = fields.Function(fields.Integer('Unit Digits'), 'on_change_with_unit_digits') quantity = fields.Float('Quantity', required=True, domain=['OR', ('quantity', '>=', 0), ('quantity', '=', None), ], digits=(16, Eval('unit_digits', 2)), depends=['unit_digits']) quantity_by_sample = fields.Boolean('Quantity by Sample') interface = fields.Function(fields.Many2One( 'lims.interface', 'Device Interface'), 'get_interface') @classmethod def __setup__(cls): super().__setup__() t = cls.__table__() cls._sql_constraints = [ ('product_template_uniq', Unique(t, t.product, t.template), 'lims_analysis_sheet_stock.msg_product_template_unique'), ] @fields.depends('product', 'uom') def on_change_product(self): if self.product: category = self.product.default_uom.category if not self.uom or self.uom.category != category: self.uom = self.product.default_uom self.unit_digits = self.product.default_uom.digits else: self.uom = None @fields.depends('product') def on_change_with_uom_category(self, name=None): if self.product: return self.product.default_uom.category.id @fields.depends('uom') def on_change_with_unit_digits(self, name=None): if self.uom: return self.uom.digits return 2 def get_rec_name(self, name): return self.product.rec_name @classmethod def search_rec_name(cls, name, clause): return [('product.rec_name',) + tuple(clause[1:])] def compute_quantity(self, factor): return self.uom.ceil(self.quantity * factor) def get_interface(self, name): return self.template.interface.id class AnalysisSheet(metaclass=PoolMeta): __name__ = 'lims.analysis_sheet' moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True) @classmethod @ModelView.button def validate_(cls, sheets): super().validate_(sheets) cls.check_materials(sheets) @classmethod def check_materials(cls, sheets): for s in sheets: if s.template.materials and not s.moves: raise UserError(gettext( 'lims_analysis_sheet_stock.msg_sheet_not_materials')) class AddMaterialStart(ModelView): 'Add Material' __name__ = 'lims.analysis_sheet.add_material.start' materials = fields.One2Many( 'lims.analysis_sheet.add_material_detail.start', 'material', 'Materials') class AddMaterialDetailStart(ModelView): 'Add Material' __name__ = 'lims.analysis_sheet.add_material_detail.start' material = fields.Many2One('lims.analysis_sheet.add_material.start', 'Material') product = fields.Many2One('product.product', 'Product', required=True, domain=[ ('type', '!=', 'service'), ]) lot = fields.Many2One('stock.lot', 'Lot', domain=[ ('product', '=', Eval('product')), ], depends=['product']) from_location = fields.Many2One('stock.location', 'From Location', domain=[('type', '=', 'storage')]) uom_category = fields.Function(fields.Many2One( 'product.uom.category', 'Uom Category'), 'on_change_with_uom_category') uom = fields.Many2One('product.uom', 'Uom', required=True, domain=[ ('category', '=', Eval('uom_category')), ], depends=['uom_category']) unit_digits = fields.Function(fields.Integer('Unit Digits'), 'on_change_with_unit_digits') quantity = fields.Float('Quantity', required=True, domain=['OR', ('quantity', '>=', 0), ('quantity', '=', None), ], digits=(16, Eval('unit_digits', 2)), depends=['unit_digits']) @fields.depends('product', 'uom') def on_change_product(self): if self.product: category = self.product.default_uom.category if not self.uom or self.uom.category != category: self.uom = self.product.default_uom self.unit_digits = self.product.default_uom.digits else: self.uom = None @fields.depends('product') def on_change_with_uom_category(self, name=None): if self.product: return self.product.default_uom.category.id @fields.depends('uom') def on_change_with_unit_digits(self, name=None): if self.uom: return self.uom.digits return 2 class AddMaterialAssignFailed(ModelView): 'Add Material Assign Failed' __name__ = 'lims.analysis_sheet.add_material.assign.failed' inventory_moves = fields.Many2Many('stock.move', None, None, 'Inventory Moves', readonly=True) @staticmethod def default_inventory_moves(): AnalysisSheet = Pool().get('lims.analysis_sheet') sheet = None line_id = Transaction().context.get('active_id', None) sheet_id = Transaction().context.get('lims_analysis_sheet', None) if line_id and sheet_id: sheet = AnalysisSheet(sheet_id) if not sheet: return [] return [x.id for x in sheet.moves if x.state == 'draft'] class AddMaterial(Wizard): 'Add Material' __name__ = 'lims.analysis_sheet.add_material' start_state = 'check' check = StateTransition() start = StateView('lims.analysis_sheet.add_material.start', 'lims_analysis_sheet_stock.add_material_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Save', 'save', 'tryton-save', default=True), ]) save = StateTransition() failed = StateView('lims.analysis_sheet.add_material.assign.failed', 'lims_analysis_sheet_stock.add_material_assign_failed_view_form', [ Button('Force Assign', 'force', 'tryton-forward', states={ 'invisible': ~Id('stock', 'group_stock_force_assignment').in_( Eval('context', {}).get('groups', [])), }), Button('OK', 'delete_moves', 'tryton-ok', True), ]) force = StateTransition() delete_moves = StateTransition() def _get_analysis_sheet_id(self): return Transaction().context.get('lims_analysis_sheet', None) def transition_check(self): AnalysisSheet = Pool().get('lims.analysis_sheet') line_id = Transaction().context.get('active_id', None) sheet_id = self._get_analysis_sheet_id() if line_id and sheet_id: sheet = AnalysisSheet(sheet_id) if sheet.state == 'active': return 'start' return 'end' def default_start(self, fields): pool = Pool() AnalysisSheet = pool.get('lims.analysis_sheet') sheet_id = self._get_analysis_sheet_id() sheet = AnalysisSheet(sheet_id) moves = [] for material in sheet.template.materials: move = { 'product': material.product.id, 'uom': material.uom.id, 'quantity': material.quantity, } moves.append(move) defaults = { 'materials': moves, } return defaults def _move(self, origin, from_location, to_location, product, uom, quantity, lot): Move = Pool().get('stock.move') move = Move( origin=origin, product=product, lot=lot, uom=uom, quantity=quantity, from_location=from_location, to_location=to_location, state='draft', ) return move def get_moves(self): pool = Pool() AnalysisSheet = pool.get('lims.analysis_sheet') Move = pool.get('stock.move') Config = Pool().get('lims.configuration') config = Config(1) sheet_id = self._get_analysis_sheet_id() sheet = AnalysisSheet(sheet_id) moves = [] for material in self.start.materials: move = self._move(sheet, material.from_location, config.materials_consumption_location, material.product, material.uom, material.quantity, material.lot) moves.append(move) Move.save(moves) return moves def transition_save(self): pool = Pool() Move = pool.get('stock.move') moves = self.get_moves() if not Move.assign_try(moves): return 'failed' Move.do(moves) return 'end' def transition_force(self): pool = Pool() AnalysisSheet = pool.get('lims.analysis_sheet') Move = pool.get('stock.move') sheet = None line_id = Transaction().context.get('active_id', None) sheet_id = Transaction().context.get('lims_analysis_sheet', None) if line_id and sheet_id: sheet = AnalysisSheet(sheet_id) Move.do([m for m in sheet.moves]) return 'end' def transition_delete_moves(self): pool = Pool() AnalysisSheet = pool.get('lims.analysis_sheet') Move = pool.get('stock.move') sheet = None line_id = Transaction().context.get('active_id', None) sheet_id = Transaction().context.get('lims_analysis_sheet', None) if line_id and sheet_id: sheet = AnalysisSheet(sheet_id) Move.delete([m for m in sheet.moves]) return 'end'