From aa8a0571fc7fa46065cbbc71c3797185de68f64a Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Wed, 16 Jul 2025 18:03:06 -0600 Subject: [PATCH] feat(#11): Implementar informe PDF de resultados de laboratorio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Agregar QWeb template para generar PDF profesional con: - Encabezado con datos del laboratorio y logo - Información completa del paciente y orden - Tabla de resultados con indicadores visuales para valores fuera de rango - Sección de observaciones y notas - Información del validador y fecha de validación - Agregar campo computado reference_text en parameter_range para mostrar rangos formateados - Agregar botón "Imprimir Informe de Resultados" en vista de órdenes (solo visible cuando hay pruebas validadas) - Agregar campo lab_notes en sale.order para observaciones generales - Reorganizar vista de lims.test con pestañas para mejor UX - Corregir manejo de employee_ids en el reporte para casos donde no existe el módulo HR - Incluir scripts de prueba para generar datos de demostración El informe resalta valores críticos y fuera de rango con colores distintivos, facilitando la interpretación rápida de los resultados por parte del médico. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .claude/settings.local.json | 4 +- lims_management/__manifest__.py | 2 + .../__pycache__/sale_order.cpython-312.pyc | Bin 15232 -> 17447 bytes lims_management/models/lims_test.py | 8 + lims_management/models/parameter_range.py | 20 ++ lims_management/models/sale_order.py | 53 ++++ .../reports/lab_results_report.xml | 274 ++++++++++++++++++ .../reports/lab_results_report_data.xml | 30 ++ lims_management/views/lims_test_views.xml | 19 +- lims_management/views/sale_order_views.xml | 11 + test/add_results_to_tests.py | 127 ++++++++ test/check_order_s00025.py | 67 +++++ test/check_patient_data.py | 62 ++++ test/create_validated_tests.py | 226 +++++++++++++++ test/update_patient_birthdate.py | 65 +++++ 15 files changed, 959 insertions(+), 9 deletions(-) create mode 100644 lims_management/reports/lab_results_report.xml create mode 100644 lims_management/reports/lab_results_report_data.xml create mode 100644 test/add_results_to_tests.py create mode 100644 test/check_order_s00025.py create mode 100644 test/check_patient_data.py create mode 100644 test/create_validated_tests.py create mode 100644 test/update_patient_birthdate.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2f0bda8..02f7faa 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -25,7 +25,9 @@ "Bash(bash:*)", "Bash(grep:*)", "Bash(gh pr merge:*)", - "Bash(git cherry-pick:*)" + "Bash(git cherry-pick:*)", + "Bash(del comment_issue_15.txt)", + "Bash(cat:*)" ], "deny": [] } diff --git a/lims_management/__manifest__.py b/lims_management/__manifest__.py index 86435d5..16f9d1f 100644 --- a/lims_management/__manifest__.py +++ b/lims_management/__manifest__.py @@ -48,6 +48,8 @@ 'views/menus.xml', 'views/lims_config_views.xml', 'report/sample_label_report.xml', + 'reports/lab_results_report_data.xml', + 'reports/lab_results_report.xml', ], 'demo': [ 'demo/demo_users.xml', diff --git a/lims_management/models/__pycache__/sale_order.cpython-312.pyc b/lims_management/models/__pycache__/sale_order.cpython-312.pyc index 79626ede326c1beea1b5f4554995680affca8004..cb430ed31c536f04b3d94e0b4d5e9c424161ad6a 100644 GIT binary patch delta 2011 zcmah}U2GIp6ux)%r`vY7+x;nJyQN_>;A1i|xt@%ZV!-7{rB3H8FwUpujmv9GdTYLFV|E9Xr71VRIA z1w_pwt@@(FQ_&p){4sp6WZwmF>JmA0NkbGaoNsFAjvLj%uEqj5Yy`ca_$R-5ocdou1j8lw^=2S&%3 zJSsEtDpg|+p&^Ayl!as|zUFA& z8vr~k4k=936ecV2j$?&*dQEF(l8CAjmW?PP4=fVyAsR3--V)~F!T}=2S1&Jo38)p) zuSDdqtcg*LGRl>8tCVlYC{c3<7H*Ma6KZcvKlZOUb97vMnsKD%##E|!+(6; zks+0`Gh$d)qEsakjXVweY4s@@r3~^_A=D6~P`(^)q8Jf#mFA5I2)RUf4smxx913ZW zB@{>vLb6&D4d)vUdW1NMIMJ5h1Tl*es0R%O5Sqk#SIe5@q>vECF>_<2<=KdkgwLUb z(6hpo0)G-Xl;;;h7h23{LI{}R9s6>eAo4KDlYsy|$PjdwGlWW{S&T#ohp6WJ51WA; z##+F{A0E36-sMgZhY&V^4nlwOFeUU>GeVc~EXL?U1%$?5na*M;#D{RckG&5vAY_H$ z7B&e>mBS>25%~OByo(ohe2ndmcl1Mfk8m~x|C`FeT@fWLM%3NGe+-jQP}K+CenG!a zq1&GBuj?=M%pROM_({uM@1{jh_aCYmwD0=-xG!MJ+E@1^Yl8H%u76z_prapbST%^O z#VC);%RutL*+=jbuQwnk{|B?m@x1`&r_hoWt*Ls1EX8Hhy&13Xp0{z)+c@3(puFM{ z!md5|ez^zme$#Nr)15BwUa}yE`>wt2p#==sdhqZ}?g-0Ibmz96LDj3}XA(kM5u7$2 z2^Sbr_`Zu1XvC(A>phOAEcC>-btW`YqPwa**uke$si|Ny;?!??;qnyzVJ4a54f~Bt zp;5SSQjSn?FNRi_#^{*BwD|Tu<(cE8FikqiX>np@7O8S!5V3e&FP{%l1?B{aMMa{U zw|IqkK%eQT3G*)IDIKt}y^t&Wj0>JPvvSeM#ab>db1@4dfQ`2Dj-VU^vunk$1`{AZ zF;{tw*XoPymC02*lf03`U^;~!*z6ab=bd+KK4@vV_nx!iwzJ{B+yC*NRL$$@>NnEv zfs}n9udBW7tj+88rK@|=?%tHW*U()YKR$S40Wf|v&O!>x)W8iU-*=J3eE0*lY zTX(5_wri#a|WCUMffa4ObekZN9oWW2?wG>oetk<3KAM z@Pms=IN_?kWSuRYDa|;8+|pulS*H#xmDD4vYpVZ)6Yrczxf;@z#)m?YrEJ-RtW8UN w^~CM43}XGe&R6gkdfCpFl5Zhb-Z8a9-?y{5a<3H+VsLDqvHymQAEQ!#12+CXg8%>k delta 118 zcmZ49!PrpFcbb=%ivb8suaswKm`~)p&*Z?k@x2uzR|i7{W0XWNgQm)6apn_dOq!CD z4>~NJyvosl=^4Z1JC2XozGX2Avzkm!bPi;kJ$bXUHrFbkIgCJDY&!XcvxcZIE2GH- Rw&^?*dA>4$*hPv!IRHm&A>;r6 diff --git a/lims_management/models/lims_test.py b/lims_management/models/lims_test.py index d9bb6e2..cc8dc39 100644 --- a/lims_management/models/lims_test.py +++ b/lims_management/models/lims_test.py @@ -28,6 +28,14 @@ class LimsTest(models.Model): ondelete='restrict' ) + sale_order_id = fields.Many2one( + 'sale.order', + string='Orden de Venta', + related='sale_order_line_id.order_id', + store=True, + readonly=True + ) + patient_id = fields.Many2one( 'res.partner', string='Paciente', diff --git a/lims_management/models/parameter_range.py b/lims_management/models/parameter_range.py index 097a449..7fe64c7 100644 --- a/lims_management/models/parameter_range.py +++ b/lims_management/models/parameter_range.py @@ -102,6 +102,26 @@ class LimsParameterRange(models.Model): readonly=True ) + reference_text = fields.Char( + string='Texto de Referencia', + compute='_compute_reference_text', + store=False, + help='Texto formateado del rango de referencia' + ) + + @api.depends('normal_min', 'normal_max', 'parameter_unit') + def _compute_reference_text(self): + """Computa el texto de referencia basado en los valores min/max y unidad""" + for record in self: + if record.normal_min is not False and record.normal_max is not False: + unit = record.parameter_unit or '' + # Formatear los números para evitar decimales innecesarios + min_val = f"{record.normal_min:.2f}".rstrip('0').rstrip('.') + max_val = f"{record.normal_max:.2f}".rstrip('0').rstrip('.') + record.reference_text = f"{min_val} - {max_val} {unit}".strip() + else: + record.reference_text = "N/A" + @api.depends('parameter_id', 'gender', 'age_min', 'age_max', 'pregnant') def _compute_name(self): for record in self: diff --git a/lims_management/models/sale_order.py b/lims_management/models/sale_order.py index 4e4bea2..e768100 100644 --- a/lims_management/models/sale_order.py +++ b/lims_management/models/sale_order.py @@ -339,3 +339,56 @@ class SaleOrder(models.Model): # Retornar la acción de imprimir el reporte para las muestras activas return report.report_action(active_samples) + + # Fields for lab results report + can_print_results = fields.Boolean( + string="Puede Imprimir Resultados", + compute='_compute_can_print_results', + help="Indica si todas las pruebas están validadas y se puede imprimir el informe" + ) + + lab_test_ids = fields.One2many( + 'lims.test', + 'sale_order_id', + string="Pruebas de Laboratorio", + readonly=True, + help="Todas las pruebas de laboratorio asociadas a esta orden" + ) + + referring_doctor_id = fields.Many2one( + 'res.partner', + string="Médico Solicitante", + related='doctor_id', + readonly=True, + help="Médico que solicitó los análisis" + ) + + lab_notes = fields.Text( + string="Observaciones del Laboratorio", + help="Observaciones generales sobre la orden o los resultados" + ) + + @api.depends('lab_test_ids.state') + def _compute_can_print_results(self): + """Compute if results can be printed (all tests validated)""" + for order in self: + tests = order.lab_test_ids + order.can_print_results = ( + tests and + all(test.state == 'validated' for test in tests) + ) + + def action_print_lab_results(self): + """Generate and print lab results report""" + self.ensure_one() + + # Verify all tests are validated + if not self.can_print_results: + raise UserError(_("No se puede imprimir el informe: hay pruebas sin validar")) + + # Ensure this is a lab request + if not self.is_lab_request: + raise UserError(_("Esta no es una orden de laboratorio")) + + # Generate the report + return self.env.ref('lims_management.action_report_lab_results').report_action(self) diff --git a/lims_management/reports/lab_results_report.xml b/lims_management/reports/lab_results_report.xml new file mode 100644 index 0000000..1c209c0 --- /dev/null +++ b/lims_management/reports/lab_results_report.xml @@ -0,0 +1,274 @@ + + + + + + + + \ No newline at end of file diff --git a/lims_management/reports/lab_results_report_data.xml b/lims_management/reports/lab_results_report_data.xml new file mode 100644 index 0000000..2f6a4bd --- /dev/null +++ b/lims_management/reports/lab_results_report_data.xml @@ -0,0 +1,30 @@ + + + + + Formato Resultados de Laboratorio + A4 + Portrait + 40 + 25 + 10 + 10 + 35 + 90 + + + + + Informe de Resultados + sale.order + qweb-pdf + lims_management.report_lab_results + lims_management.report_lab_results + 'Resultados_Lab_' + object.name + '.pdf' + + 'Resultados_Lab_' + object.name + '.pdf' + True + + report + + \ No newline at end of file diff --git a/lims_management/views/lims_test_views.xml b/lims_management/views/lims_test_views.xml index 18518ea..f038c09 100644 --- a/lims_management/views/lims_test_views.xml +++ b/lims_management/views/lims_test_views.xml @@ -64,7 +64,7 @@ - + - - + + + + + + + + + + -
- - - -
diff --git a/lims_management/views/sale_order_views.xml b/lims_management/views/sale_order_views.xml index b496ddf..6da7e41 100644 --- a/lims_management/views/sale_order_views.xml +++ b/lims_management/views/sale_order_views.xml @@ -16,6 +16,12 @@ class="btn-primary" invisible="not is_lab_request or state != 'sale' or not all_sample_ids" icon="fa-print"/> +