diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 645c6df..2f0bda8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -24,7 +24,8 @@ "Bash(true)", "Bash(bash:*)", "Bash(grep:*)", - "Bash(gh pr merge:*)" + "Bash(gh pr merge:*)", + "Bash(git cherry-pick:*)" ], "deny": [] } diff --git a/documents/logs/Screenshot_3.png b/documents/logs/Screenshot_3.png new file mode 100644 index 0000000..ff2e0f7 Binary files /dev/null and b/documents/logs/Screenshot_3.png differ diff --git a/init_odoo.py b/init_odoo.py index 46e197f..a10b3c9 100644 --- a/init_odoo.py +++ b/init_odoo.py @@ -36,7 +36,6 @@ odoo_command = [ "-d", DB_NAME, "-i", MODULES_TO_INSTALL, "--load-language", "es_ES", - "--without-demo=", # Forzar carga de datos demo "--stop-after-init" ] diff --git a/lims_management/__init__.py b/lims_management/__init__.py index cde864b..f553d8f 100644 --- a/lims_management/__init__.py +++ b/lims_management/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import models +from . import wizards diff --git a/lims_management/__manifest__.py b/lims_management/__manifest__.py index 3895a7a..ca286f3 100644 --- a/lims_management/__manifest__.py +++ b/lims_management/__manifest__.py @@ -29,9 +29,12 @@ 'data/product_category.xml', 'data/sample_types.xml', 'data/lims_sequence.xml', + 'data/rejection_reason_data.xml', 'views/partner_views.xml', 'views/analysis_views.xml', 'views/sale_order_views.xml', + 'views/rejection_reason_views.xml', + 'wizards/sample_rejection_wizard_views.xml', 'views/stock_lot_views.xml', 'views/lims_test_views.xml', 'views/lims_result_views.xml', diff --git a/lims_management/__pycache__/__init__.cpython-312.pyc b/lims_management/__pycache__/__init__.cpython-312.pyc index 98b96b4..9160d9a 100644 Binary files a/lims_management/__pycache__/__init__.cpython-312.pyc and b/lims_management/__pycache__/__init__.cpython-312.pyc differ diff --git a/lims_management/data/rejection_reason_data.xml b/lims_management/data/rejection_reason_data.xml new file mode 100644 index 0000000..b8b174b --- /dev/null +++ b/lims_management/data/rejection_reason_data.xml @@ -0,0 +1,95 @@ + + + + + + Muestra Insuficiente + INSUF + El volumen de muestra recibido es insuficiente para realizar los análisis solicitados + high + + 10 + + + + Muestra Hemolizada + HEMO + La muestra presenta hemólisis que interfiere con los análisis + high + + 20 + + + + Muestra Coagulada + COAG + La muestra presenta coágulos que impiden su procesamiento + high + + 30 + + + + Muestra Lipémica + LIP + La muestra presenta lipemia excesiva que interfiere con los análisis + medium + + 40 + + + + Recipiente Inadecuado + RECIP + El tipo de recipiente utilizado no es apropiado para el análisis solicitado + high + + 50 + + + + Identificación Incorrecta + ID + La identificación de la muestra no coincide con la solicitud o es ilegible + critical + + 60 + + + + Muestra sin Rotular + NOLAB + La muestra no tiene etiqueta de identificación + critical + + 70 + + + + Condiciones de Transporte Inadecuadas + TRANS + La muestra no fue transportada en las condiciones requeridas (temperatura, tiempo, etc.) + high + + 80 + + + + Muestra Contaminada + CONT + La muestra presenta signos evidentes de contaminación + critical + + 90 + + + + Tiempo de Entrega Excedido + TIME + La muestra fue recibida fuera del tiempo límite establecido para su procesamiento + high + + 100 + + + \ No newline at end of file diff --git a/lims_management/models/__init__.py b/lims_management/models/__init__.py index 8e58433..7110907 100644 --- a/lims_management/models/__init__.py +++ b/lims_management/models/__init__.py @@ -6,6 +6,7 @@ from . import product from . import partner from . import sale_order from . import stock_lot +from . import rejection_reason from . import lims_test from . import lims_result from . import res_config_settings diff --git a/lims_management/models/__pycache__/__init__.cpython-312.pyc b/lims_management/models/__pycache__/__init__.cpython-312.pyc index 9029664..fc0d06e 100644 Binary files a/lims_management/models/__pycache__/__init__.cpython-312.pyc and b/lims_management/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/lims_management/models/__pycache__/sale_order.cpython-312.pyc b/lims_management/models/__pycache__/sale_order.cpython-312.pyc index 4a05057..7492287 100644 Binary files a/lims_management/models/__pycache__/sale_order.cpython-312.pyc and b/lims_management/models/__pycache__/sale_order.cpython-312.pyc differ diff --git a/lims_management/models/__pycache__/stock_lot.cpython-312.pyc b/lims_management/models/__pycache__/stock_lot.cpython-312.pyc index e690aed..8e1cd01 100644 Binary files a/lims_management/models/__pycache__/stock_lot.cpython-312.pyc and b/lims_management/models/__pycache__/stock_lot.cpython-312.pyc differ diff --git a/lims_management/models/rejection_reason.py b/lims_management/models/rejection_reason.py new file mode 100644 index 0000000..bd65d1a --- /dev/null +++ b/lims_management/models/rejection_reason.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api + +class LimsRejectionReason(models.Model): + _name = 'lims.rejection.reason' + _description = 'Motivo de Rechazo de Muestra' + _order = 'sequence, name' + + name = fields.Char( + string='Motivo', + required=True + ) + code = fields.Char( + string='Código', + required=True, + help="Código único para identificar el motivo" + ) + description = fields.Text( + string='Descripción', + help="Descripción detallada del motivo de rechazo" + ) + active = fields.Boolean( + string='Activo', + default=True + ) + sequence = fields.Integer( + string='Secuencia', + default=10, + help="Orden de aparición en las listas" + ) + requires_new_sample = fields.Boolean( + string='Requiere Nueva Muestra', + default=True, + help="Indica si este tipo de rechazo requiere solicitar una nueva muestra" + ) + severity = fields.Selection([ + ('low', 'Baja'), + ('medium', 'Media'), + ('high', 'Alta'), + ('critical', 'Crítica') + ], string='Severidad', default='medium', + help="Severidad del problema que causa el rechazo") + + # Statistics + rejection_count = fields.Integer( + string='Cantidad de Rechazos', + compute='_compute_rejection_count', + help="Número de muestras rechazadas con este motivo" + ) + + @api.depends('name') + def _compute_rejection_count(self): + for record in self: + record.rejection_count = self.env['stock.lot'].search_count([ + ('rejection_reason_id', '=', record.id), + ('state', '=', 'rejected') + ]) + + _sql_constraints = [ + ('code_uniq', 'unique (code)', 'El código del motivo de rechazo debe ser único!'), + ] \ No newline at end of file diff --git a/lims_management/models/stock_lot.py b/lims_management/models/stock_lot.py index 6d8e313..ee44c78 100644 --- a/lims_management/models/stock_lot.py +++ b/lims_management/models/stock_lot.py @@ -82,8 +82,29 @@ class StockLot(models.Model): ('analyzed', 'Analizada'), ('stored', 'Almacenada'), ('disposed', 'Desechada'), - ('cancelled', 'Cancelada') + ('cancelled', 'Cancelada'), + ('rejected', 'Rechazada') ], string='Estado', default='collected', tracking=True) + + # Rejection fields + rejection_reason_id = fields.Many2one( + 'lims.rejection.reason', + string='Motivo de Rechazo', + tracking=True + ) + rejection_notes = fields.Text( + string='Notas de Rechazo', + help="Información adicional sobre el rechazo" + ) + rejected_by = fields.Many2one( + 'res.users', + string='Rechazado por', + readonly=True + ) + rejection_date = fields.Datetime( + string='Fecha de Rechazo', + readonly=True + ) def action_collect(self): """Mark sample as collected""" @@ -155,6 +176,54 @@ class StockLot(models.Model): message_type='notification' ) + def action_open_rejection_wizard(self): + """Open the rejection wizard""" + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Rechazar Muestra', + 'res_model': 'lims.sample.rejection.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_sample_id': self.id, + } + } + + def action_reject(self): + """Reject the sample - to be called from wizard""" + self.ensure_one() + if self.state == 'completed': + raise ValueError('No se puede rechazar una muestra ya completada') + + # This method is called from the wizard, so rejection fields should already be set + self.write({ + 'state': 'rejected', + 'rejected_by': self.env.user.id, + 'rejection_date': fields.Datetime.now() + }) + + reason_name = self.rejection_reason_id.name if self.rejection_reason_id else 'Sin especificar' + notes = self.rejection_notes or '' + + body = f'Muestra rechazada por {self.env.user.name}
Motivo: {reason_name}' + if notes: + body += f'
Notas: {notes}' + + self.message_post( + body=body, + subject='Estado actualizado: Rechazada', + message_type='notification' + ) + + # Notify related sale order if exists + if self.request_id: + self.request_id.message_post( + body=f'La muestra {self.name} ha sido rechazada. Motivo: {reason_name}', + subject='Muestra Rechazada', + message_type='notification' + ) + @api.onchange('sample_type_product_id') def _onchange_sample_type_product_id(self): """Synchronize container_type when sample_type_product_id changes""" diff --git a/lims_management/security/ir.model.access.csv b/lims_management/security/ir.model.access.csv index 1e120c3..fbe8000 100644 --- a/lims_management/security/ir.model.access.csv +++ b/lims_management/security/ir.model.access.csv @@ -6,6 +6,11 @@ access_product_template_parameter_manager,product.template.parameter.manager,mod access_lims_parameter_range_user,lims.parameter.range.user,model_lims_parameter_range,base.group_user,1,0,0,0 access_lims_parameter_range_manager,lims.parameter.range.manager,model_lims_parameter_range,group_lims_admin,1,1,1,1 access_sale_order_receptionist,sale.order.receptionist,sale.model_sale_order,group_lims_receptionist,1,1,1,0 +access_sale_order_line_receptionist,sale.order.line.receptionist,sale.model_sale_order_line,group_lims_receptionist,1,1,1,0 +access_sale_order_technician,sale.order.technician,sale.model_sale_order,group_lims_technician,1,0,0,0 +access_sale_order_line_technician,sale.order.line.technician,sale.model_sale_order_line,group_lims_technician,1,0,0,0 +access_sale_order_admin,sale.order.admin,sale.model_sale_order,group_lims_admin,1,1,1,1 +access_sale_order_line_admin,sale.order.line.admin,sale.model_sale_order_line,group_lims_admin,1,1,1,1 access_stock_lot_user,stock.lot.user,stock.model_stock_lot,base.group_user,1,1,1,1 access_lims_test_receptionist,lims.test.receptionist,model_lims_test,group_lims_receptionist,1,0,0,0 access_lims_test_technician,lims.test.technician,model_lims_test,group_lims_technician,1,1,1,0 @@ -13,3 +18,8 @@ access_lims_test_admin,lims.test.admin,model_lims_test,group_lims_admin,1,1,1,1 access_lims_result_receptionist,lims.result.receptionist,model_lims_result,group_lims_receptionist,1,0,0,0 access_lims_result_technician,lims.result.technician,model_lims_result,group_lims_technician,1,1,1,0 access_lims_result_admin,lims.result.admin,model_lims_result,group_lims_admin,1,1,1,1 +access_lims_rejection_reason_user,lims.rejection.reason.user,model_lims_rejection_reason,base.group_user,1,0,0,0 +access_lims_rejection_reason_technician,lims.rejection.reason.technician,model_lims_rejection_reason,group_lims_technician,1,0,0,0 +access_lims_rejection_reason_admin,lims.rejection.reason.admin,model_lims_rejection_reason,group_lims_admin,1,1,1,1 +access_lims_sample_rejection_wizard_user,lims.sample.rejection.wizard.user,model_lims_sample_rejection_wizard,base.group_user,1,1,1,1 +access_lims_sample_rejection_wizard_technician,lims.sample.rejection.wizard.technician,model_lims_sample_rejection_wizard,group_lims_technician,1,1,1,1 diff --git a/lims_management/views/lims_test_views.xml b/lims_management/views/lims_test_views.xml index e4b721c..262938d 100644 --- a/lims_management/views/lims_test_views.xml +++ b/lims_management/views/lims_test_views.xml @@ -64,7 +64,7 @@ + + + + + + + + + + lims.rejection.reason.list + lims.rejection.reason + + + + + + + + + + + + + + + + lims.rejection.reason.form + lims.rejection.reason + +
+ + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + + + lims.rejection.reason.search + lims.rejection.reason + + + + + + + + + + + + + + + + + + + + + Motivos de Rechazo + lims.rejection.reason + list,form + + {'search_default_active': 1} + +

+ Configure los motivos de rechazo de muestras +

+

+ Los motivos de rechazo permiten categorizar y documentar + las razones por las cuales una muestra no puede ser procesada. +

+
+
+
\ No newline at end of file diff --git a/lims_management/views/stock_lot_views.xml b/lims_management/views/stock_lot_views.xml index ec3f47d..35dce0c 100644 --- a/lims_management/views/stock_lot_views.xml +++ b/lims_management/views/stock_lot_views.xml @@ -15,7 +15,7 @@ - +
@@ -33,7 +33,13 @@