From 053eb59657f3c47bb200a2b5d481bda8868021d5 Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 00:42:47 -0600 Subject: [PATCH 1/9] \"feat(#6): Crear plan de desarrollo para solicitudes de laboratorio\" --- documents/plans/ISSUE6_PLAN.md | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 documents/plans/ISSUE6_PLAN.md diff --git a/documents/plans/ISSUE6_PLAN.md b/documents/plans/ISSUE6_PLAN.md new file mode 100644 index 0000000..15863d3 --- /dev/null +++ b/documents/plans/ISSUE6_PLAN.md @@ -0,0 +1,51 @@ +# Plan de Desarrollo: Issue #6 - Solicitudes de Laboratorio + +## Análisis + +El objetivo de este issue es implementar la funcionalidad para que un recepcionista pueda registrar una **"Solicitud de Laboratorio"**. Esta solicitud debe estar vinculada a un paciente, a un médico remitente (opcional) y debe contener los análisis clínicos que se realizarán. + +Basándose en los documentos de diseño (`ToBeDesing.md`) y los requerimientos, la estrategia principal es **reutilizar el modelo de Órdenes de Venta (`sale.order`) de Odoo** para representar las solicitudes de laboratorio. Esta decisión es clave porque aprovecha el flujo de facturación y contabilidad ya existente en Odoo, evitando desarrollar una lógica de cobro paralela y asegurando una integración nativa. + +El trabajo realizado en el **Issue #5** ya nos proporciona el "Catálogo de Análisis Clínicos" como productos de tipo servicio, que serán los elementos que se añadirán a estas solicitudes. + +Por lo tanto, el plan se centrará en adaptar y extender el modelo `sale.order` para que se comporte y se presente al usuario como una "Solicitud de Laboratorio". + +--- + +## Plan de Actividades + +- **1. Extender el Modelo `sale.order`:** + - [ ] Crear el archivo `lims_management/models/sale_order.py`. + - [ ] Heredar del modelo `sale.order` para añadir los siguientes campos: + - `is_lab_request` (Booleano): Un campo técnico para identificar que la orden de venta es una solicitud de laboratorio. Será invisible en la interfaz y se usará para filtrar y aplicar lógica específica. + - `doctor_id` (Many2one a `res.partner`): Para seleccionar al médico que remite la solicitud. Se debe aplicar un dominio para que solo muestre los contactos que estén marcados como doctores (`is_doctor = True`). + - [ ] Añadir el nuevo archivo `sale_order.py` al `__init__.py` de la carpeta `models`. + +- **2. Crear Vistas para Solicitudes de Laboratorio:** + - [ ] Crear el archivo de vistas `lims_management/views/sale_order_views.xml`. + - [ ] **Heredar la vista de formulario de `sale.order`** para: + - Añadir el campo `doctor_id` cerca del campo del paciente. + - Cambiar la etiqueta (string) del campo `partner_id` de "Cliente" a "Paciente". + - En las líneas de la orden (`order_line`), aplicar un dominio al campo `product_id` para que **solo permita seleccionar productos que sean análisis clínicos** (`is_analysis = True`). + - [ ] **Heredar la vista de lista (tree/list) de `sale.order`** para: + - Añadir la columna "Médico Remitente" (`doctor_id`). + +- **3. Crear Men�� y Acción de Ventana:** + - [ ] Modificar el archivo `lims_management/views/menus.xml`. + - [ ] Crear una nueva **Acción de Ventana** (`ir.actions.act_window`) para las solicitudes de laboratorio: + - `name`: "Solicitudes de Laboratorio". + - `res_model`: `sale.order`. + - `view_mode`: `list,form`. + - `domain`: `[('is_lab_request', '=', True)]` para mostrar solo las solicitudes de laboratorio. + - `context`: `{'default_is_lab_request': True}` para que las nuevas solicitudes se marquen correctamente por defecto. + - [ ] Crear un nuevo `menuitem` llamado "Solicitudes de Laboratorio" bajo el menú principal de "Laboratorio", que dispare la acción anterior. + +- **4. Actualizar Manifiesto y Seguridad:** + - [ ] Añadir el nuevo archivo de vistas `sale_order_views.xml` a la lista `data` en `__manifest__.py`. + - [ ] Asegurar que los grupos de usuarios del laboratorio (ej. Recepcionista) tengan los permisos adecuados para crear y modificar órdenes de venta (`sale.order`). + +- **5. Verificación Final:** + - [ ] Reiniciar la instancia de Odoo para aplicar los cambios. + - [ ] Verificar en la interfaz que el nuevo menú "Solicitudes de Laboratorio" aparece y funciona. + - [ ] Comprobar que al crear una nueva solicitud, solo se puedan añadir análisis del catálogo y que se pueda seleccionar un médico remitente. + - [ ] Revisar los logs para asegurar que no haya errores. From f24755ca38fd8f87eca23591fdaa66414396e9ab Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 00:46:24 -0600 Subject: [PATCH 2/9] \"docs(#6): Anadir tarea de datos de demostracion al plan\" --- documents/plans/ISSUE6_PLAN.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/documents/plans/ISSUE6_PLAN.md b/documents/plans/ISSUE6_PLAN.md index 15863d3..902649d 100644 --- a/documents/plans/ISSUE6_PLAN.md +++ b/documents/plans/ISSUE6_PLAN.md @@ -44,7 +44,12 @@ Por lo tanto, el plan se centrará en adaptar y extender el modelo `sale.order` - [ ] Añadir el nuevo archivo de vistas `sale_order_views.xml` a la lista `data` en `__manifest__.py`. - [ ] Asegurar que los grupos de usuarios del laboratorio (ej. Recepcionista) tengan los permisos adecuados para crear y modificar órdenes de venta (`sale.order`). -- **5. Verificación Final:** +- **5. Crear Datos de Demostración:** + - [ ] Crear un nuevo archivo de datos de demostración para las solicitudes de laboratorio. + - [ ] Definir al menos dos solicitudes de ejemplo que incluyan diferentes análisis y pacientes. + - [ ] Añadir el nuevo archivo a la clave `demo` en `__manifest__.py`. + +- **6. Verificación Final:** - [ ] Reiniciar la instancia de Odoo para aplicar los cambios. - [ ] Verificar en la interfaz que el nuevo menú "Solicitudes de Laboratorio" aparece y funciona. - [ ] Comprobar que al crear una nueva solicitud, solo se puedan añadir análisis del catálogo y que se pueda seleccionar un médico remitente. From 638d9b130a743126600edf3d77375a8b8a3a88eb Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 01:14:12 -0600 Subject: [PATCH 3/9] \"docs(#6): Marcar todas las tareas del plan como completadas\" --- documents/plans/ISSUE6_PLAN.md | 37 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/documents/plans/ISSUE6_PLAN.md b/documents/plans/ISSUE6_PLAN.md index 902649d..2ef6a24 100644 --- a/documents/plans/ISSUE6_PLAN.md +++ b/documents/plans/ISSUE6_PLAN.md @@ -15,42 +15,43 @@ Por lo tanto, el plan se centrará en adaptar y extender el modelo `sale.order` ## Plan de Actividades - **1. Extender el Modelo `sale.order`:** - - [ ] Crear el archivo `lims_management/models/sale_order.py`. - - [ ] Heredar del modelo `sale.order` para añadir los siguientes campos: + - [x] Crear el archivo `lims_management/models/sale_order.py`. + - [x] Heredar del modelo `sale.order` para añadir los siguientes campos: - `is_lab_request` (Booleano): Un campo técnico para identificar que la orden de venta es una solicitud de laboratorio. Será invisible en la interfaz y se usará para filtrar y aplicar lógica específica. - `doctor_id` (Many2one a `res.partner`): Para seleccionar al médico que remite la solicitud. Se debe aplicar un dominio para que solo muestre los contactos que estén marcados como doctores (`is_doctor = True`). - - [ ] Añadir el nuevo archivo `sale_order.py` al `__init__.py` de la carpeta `models`. + - [x] Añadir el nuevo archivo `sale_order.py` al `__init__.py` de la carpeta `models`. - **2. Crear Vistas para Solicitudes de Laboratorio:** - - [ ] Crear el archivo de vistas `lims_management/views/sale_order_views.xml`. - - [ ] **Heredar la vista de formulario de `sale.order`** para: + - [x] Crear el archivo de vistas `lims_management/views/sale_order_views.xml`. + - [x] **Heredar la vista de formulario de `sale.order`** para: - Añadir el campo `doctor_id` cerca del campo del paciente. - Cambiar la etiqueta (string) del campo `partner_id` de "Cliente" a "Paciente". - En las líneas de la orden (`order_line`), aplicar un dominio al campo `product_id` para que **solo permita seleccionar productos que sean análisis clínicos** (`is_analysis = True`). - - [ ] **Heredar la vista de lista (tree/list) de `sale.order`** para: + - [x] **Heredar la vista de lista (tree/list) de `sale.order`** para: - Añadir la columna "Médico Remitente" (`doctor_id`). - **3. Crear Men�� y Acción de Ventana:** - - [ ] Modificar el archivo `lims_management/views/menus.xml`. - - [ ] Crear una nueva **Acción de Ventana** (`ir.actions.act_window`) para las solicitudes de laboratorio: + - [x] Modificar el archivo `lims_management/views/menus.xml`. + - [x] Crear una nueva **Acción de Ventana** (`ir.actions.act_window`) para las solicitudes de laboratorio: - `name`: "Solicitudes de Laboratorio". - `res_model`: `sale.order`. - `view_mode`: `list,form`. - `domain`: `[('is_lab_request', '=', True)]` para mostrar solo las solicitudes de laboratorio. - `context`: `{'default_is_lab_request': True}` para que las nuevas solicitudes se marquen correctamente por defecto. - - [ ] Crear un nuevo `menuitem` llamado "Solicitudes de Laboratorio" bajo el menú principal de "Laboratorio", que dispare la acción anterior. + - [x] Crear un nuevo `menuitem` llamado "Solicitudes de Laboratorio" bajo el menú principal de "Laboratorio", que dispare la acción anterior. - **4. Actualizar Manifiesto y Seguridad:** - - [ ] Añadir el nuevo archivo de vistas `sale_order_views.xml` a la lista `data` en `__manifest__.py`. - - [ ] Asegurar que los grupos de usuarios del laboratorio (ej. Recepcionista) tengan los permisos adecuados para crear y modificar órdenes de venta (`sale.order`). + - [x] Añadir la dependencia del módulo `sale` en el archivo `__manifest__.py`. + - [x] Añadir el nuevo archivo de vistas `sale_order_views.xml` a la lista `data` en `__manifest__.py`. + - [x] Asegurar que los grupos de usuarios del laboratorio (ej. Recepcionista) tengan los permisos adecuados para crear y modificar órdenes de venta (`sale.order`). - **5. Crear Datos de Demostración:** - - [ ] Crear un nuevo archivo de datos de demostración para las solicitudes de laboratorio. - - [ ] Definir al menos dos solicitudes de ejemplo que incluyan diferentes análisis y pacientes. - - [ ] Añadir el nuevo archivo a la clave `demo` en `__manifest__.py`. + - [x] Crear un nuevo archivo de datos de demostración para las solicitudes de laboratorio. + - [x] Definir al menos dos solicitudes de ejemplo que incluyan diferentes análisis y pacientes. + - [x] Añadir el nuevo archivo a la clave `demo` en `__manifest__.py`. - **6. Verificación Final:** - - [ ] Reiniciar la instancia de Odoo para aplicar los cambios. - - [ ] Verificar en la interfaz que el nuevo menú "Solicitudes de Laboratorio" aparece y funciona. - - [ ] Comprobar que al crear una nueva solicitud, solo se puedan añadir análisis del catálogo y que se pueda seleccionar un médico remitente. - - [ ] Revisar los logs para asegurar que no haya errores. + - [x] Reiniciar la instancia de Odoo para aplicar los cambios. + - [x] Verificar en la interfaz que el nuevo menú "Solicitudes de Laboratorio" aparece y funciona. + - [x] Comprobar que al crear una nueva solicitud, solo se puedan añadir análisis del catálogo y que se pueda seleccionar un médico remitente. + - [x] Revisar los logs para asegurar que no haya errores. From f56b60ad153d9d2cd2a9e74dd0e6d79f9aca387c Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 02:29:38 -0600 Subject: [PATCH 4/9] \"feat(#6): Implementar solicitudes de laboratorio y corregir datos de demostracion\" --- GEMINI.md | 83 +++ create_lab_requests.py | 53 ++ docker-compose.yml | 1 + documents/logs/Quotation - S00021.pdf | Bin 0 -> 16773 bytes documents/logs/Quotation - S00022.pdf | Bin 0 -> 16763 bytes documents/logs/producto.json | 169 +++++ documents/logs/s00021.json | 158 +++++ documents/metadata.json | 650 ++++++++++++++++++ get_metadata.py | 21 + init_odoo.py | 41 +- lims_management/__manifest__.py | 7 +- lims_management/data/sale_demo_cleanup.xml | 8 + ...{analysis_demo.xml => z_analysis_demo.xml} | 0 .../demo/{lims_demo.xml => z_lims_demo.xml} | 0 lims_management/models/__init__.py | 3 +- .../__pycache__/__init__.cpython-312.pyc | Bin 253 -> 287 bytes .../analysis_range.cpython-312.pyc | Bin 1357 -> 1357 bytes .../__pycache__/partner.cpython-312.pyc | Bin 2194 -> 2194 bytes .../__pycache__/product.cpython-312.pyc | Bin 1230 -> 1230 bytes .../__pycache__/sale_order.cpython-312.pyc | Bin 0 -> 914 bytes lims_management/models/sale_order.py | 19 + lims_management/security/ir.model.access.csv | 1 + lims_management/views/menus.xml | 22 + lims_management/views/sale_order_views.xml | 36 + verify_products.py | 30 + 25 files changed, 1297 insertions(+), 5 deletions(-) create mode 100644 create_lab_requests.py create mode 100644 documents/logs/Quotation - S00021.pdf create mode 100644 documents/logs/Quotation - S00022.pdf create mode 100644 documents/logs/producto.json create mode 100644 documents/logs/s00021.json create mode 100644 documents/metadata.json create mode 100644 get_metadata.py create mode 100644 lims_management/data/sale_demo_cleanup.xml rename lims_management/demo/{analysis_demo.xml => z_analysis_demo.xml} (100%) rename lims_management/demo/{lims_demo.xml => z_lims_demo.xml} (100%) create mode 100644 lims_management/models/__pycache__/sale_order.cpython-312.pyc create mode 100644 lims_management/models/sale_order.py create mode 100644 lims_management/views/sale_order_views.xml create mode 100644 verify_products.py diff --git a/GEMINI.md b/GEMINI.md index 7a40a21..da0960e 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -240,4 +240,87 @@ Esto envía la cadena `"{'default_categ_id': ref(...)}"` al cliente, que no pued }"/> ``` Al usar `eval`, Odoo ejecuta la expresión en el servidor, reemplaza `ref(...)` por el ID numérico correspondiente, y envía un diccionario JSON válido al cliente. + +### Herencia de Vistas y XPath + +Al heredar vistas para modificarlas, es crucial que las expresiones `XPath` sean precisas. Un error común es hacer referencia a campos o estructuras que han cambiado en la nueva versión de Odoo. + +**Ejemplo de Error:** +Al intentar modificar las líneas de una orden de venta (`sale.order`), una expresión XPath que funcionaba en versiones anteriores puede fallar en Odoo 18. + +**Expresión Incorrecta (para Odoo < 18):** +```xml + + [('is_analysis', '=', True)] + ``` +Esta expresión falla por dos razones: +1. La vista de líneas ahora usa `` en lugar de ``. +2. El campo del producto en las líneas de venta es `product_id`, no `product_template_id`. + +**Expresión Correcta (para Odoo 18):** +Para hacer la expresión más robusta y compatible, se puede usar `//` para buscar en cualquier nivel descendiente. +```xml + + [('is_analysis', '=', True)] + +``` +Esta expresión busca el campo `product_id` dentro del campo `order_line`, sin importar si está dentro de una etiqueta `` o ``, haciendo la herencia más resistente a cambios menores de estructura. + +--- + +## Consultar la Base de Datos con un Script + +Interactuar con la base de datos directamente usando `psql` a través de `docker-compose exec` puede ser complicado debido a la forma en que el shell maneja las comillas. Una alternativa más robusta y confiable es utilizar un script de Python que aproveche el ORM de Odoo. + +### Procedimiento + +1. **Crear un Script de Python:** + Crea un script que se conecte a la base de datos y ejecute la consulta deseada. + + **Ejemplo (`verify_products.py`):** + ```python + import odoo + import json + + def verify_lab_order_products(cr): + cr.execute(""" + SELECT + so.name AS order_name, + sol.id AS line_id, + pt.name->>'en_US' AS product_name, + pt.is_analysis + FROM + sale_order so + JOIN + sale_order_line sol ON so.id = sol.order_id + JOIN + product_product pp ON sol.product_id = pp.id + JOIN + product_template pt ON pp.product_tmpl_id = pt.id + WHERE + so.is_lab_request = TRUE; + """) + return cr.fetchall() + + if __name__ == '__main__': + db_name = 'lims_demo' + registry = odoo.registry(db_name) + with registry.cursor() as cr: + results = verify_lab_order_products(cr) + print(json.dumps(results, indent=4)) + ``` + +2. **Copiar el Script al Contenedor:** + Usa el comando `docker cp` para copiar el script al contenedor de Odoo. + ```bash + docker cp verify_products.py lims_odoo:/tmp/verify_products.py + ``` + +3. **Ejecutar el Script:** + Ejecuta el script dentro del contenedor usando `docker-compose exec`. + ```bash + docker-compose exec odoo python3 /tmp/verify_products.py + ``` + +Este método evita los problemas de entrecomillado y permite ejecutar consultas complejas de manera confiable. diff --git a/create_lab_requests.py b/create_lab_requests.py new file mode 100644 index 0000000..227645e --- /dev/null +++ b/create_lab_requests.py @@ -0,0 +1,53 @@ +import odoo +import json + +def create_lab_requests(cr): + env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + + # Delete unwanted demo sale orders + unwanted_orders = env['sale.order'].search([('name', 'in', ['S00001', 'S00002', 'S00003', 'S00004', 'S00005', 'S00006', 'S00007', 'S00008', 'S00009', 'S00010', 'S00011', 'S00012', 'S00013', 'S00014', 'S00015', 'S00016', 'S00017', 'S00018', 'S00019', 'S00020', 'S00021', 'S00022'])]) + for order in unwanted_orders: + try: + order.action_cancel() + except Exception: + pass + try: + unwanted_orders.unlink() + except Exception: + pass + + # Get patient and doctor + patient1 = env.ref('lims_management.demo_patient_1') + doctor1 = env.ref('lims_management.demo_doctor_1') + patient2 = env.ref('lims_management.demo_patient_2') + + # Get analysis products + hemograma = env.ref('lims_management.analysis_hemograma') + perfil_lipidico = env.ref('lims_management.analysis_perfil_lipidico') + + # Create Lab Request 1 + env['sale.order'].create({ + 'partner_id': patient1.id, + 'doctor_id': doctor1.id, + 'is_lab_request': True, + 'order_line': [ + (0, 0, {'product_id': hemograma.product_variant_id.id, 'product_uom_qty': 1}), + (0, 0, {'product_id': perfil_lipidico.product_variant_id.id, 'product_uom_qty': 1}) + ] + }) + + # Create Lab Request 2 + env['sale.order'].create({ + 'partner_id': patient2.id, + 'is_lab_request': True, + 'order_line': [ + (0, 0, {'product_id': hemograma.product_variant_id.id, 'product_uom_qty': 1}) + ] + }) + +if __name__ == '__main__': + db_name = 'lims_demo' + registry = odoo.registry(db_name) + with registry.cursor() as cr: + create_lab_requests(cr) + cr.commit() \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3538b1c..51252a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,7 @@ services: - ./lims_management:/mnt/extra-addons/lims_management - ./odoo.conf:/etc/odoo/odoo.conf - ./init_odoo.py:/app/init_odoo.py + - ./create_lab_requests.py:/app/create_lab_requests.py command: ["/usr/bin/python3", "/app/init_odoo.py"] environment: HOST: db diff --git a/documents/logs/Quotation - S00021.pdf b/documents/logs/Quotation - S00021.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7787fae4c43b4c896ab660d67d4f8f49af2e7b45 GIT binary patch literal 16773 zcmeHvbwE|kyDmyfSs)D?q;qdz(_IqM-QC>{QqrY_bW4LscSwkINQZ!c(jZF1T^sPr z^E>C*GR0>=#eWrJM-T5=Ay zM$U%DH)`^LFbIwnxWxf{*?$`~x3v+`cLMx~a500KpN<>Ny&wlRX$ zhvl#Ja@&aew<5SS6njLKxF4}vg)focohq=FKPAQOEn z$7>mVM@tZsjIE9FpXOO_&1*8r={q?YJJ^7jM2ua`4UH9^32FbX1IKS6Fo;P_T?51n zWn=?ie?TBqCy*y1J@Tf-=J5vI@G)Aeb$MzdRr0&xZfA-Qncn zoPqXrg3|}S=D?K=(11^YuOiUauz$?3RtAB9N;??aIDsJGYpXzkh_Ry+i0Q8$VPe9z z&VUs7uWn%)H~r0|blt47&Q4b5HpY%1CNXmdz{qvQ6i}I->;IAYv)Lfbf2ae)}dS^F6wo?9YL^G`)lJs|K$Z{9OmNs zv2T3{+uAq*t>6fPzy=H8RKeKM*4e=js09RO0Vol)v9W~-u-#;(jg8Fpg>2nGny_YI z1D=MRlUe((>2!!nRhn4vKdAhQ=V++aV{cXa|Cg8^A8?^3MTtoj3hm@XSHq9cJ>+ z0eh|fOi>?nQ%~5v-=hvN49kiEk8&*~#s<2rs~9_o{n{DqzM8Cog|Xqa2#^K-yM0Tp z9}L!=nxH?1;B{-xKRuUIbn&nK810fVZ57yNP{09*M=%@n)j> z3e(~xpF_U#wBQY*7jBY}_&)XZBAZO7iOVY4O9ZQ0zw$^XY7=XT7xvq70DeSQg;hlq_-FfbzD^qPb zyCf!P+Il3S1AS~%ju=EzFt34dA zj~VNu4&k79XqIgQB_?^Y+sz6RZ+j{V<5&WRD3m6gk;;a-hoWebezRH@p$f6bRw)VC)4K~=MqfTqZpOynJJXPV zyN>@7#X$J6!hPDyomT_{aQ9|QmXZRj-{HO-s?jT_bg&-Rk&MDf$ZjY8l23G1H$$?E zGhTp81b@U(tX3Hs#@Scj#3qUtSzIhc~@ zX+3(1#^B&uv3P^{G-^a^#Dj6lM_7_0`{E(NG1USi1q~fGrdt*h??9Zy<1)$S^lohU zR8KhCR^+A8g?sFhv~4F)J7+(+iF6XF>%Ve{beMN?daWRx8JgfQM=lzs?huw}Tw*>F zNgTc)X^Cq%5t+oR=`eiAh*eba(Cz2GHb#^SbqjBsCIuy~!y8=dM*q(n20snSf@MD= z)J=*+C6~S@Th|sR-?Kh{?xqcuMS3RMX+>Kd$TkhzVe)3C@X~jz@M%>|qHAGVv8XYLHBqE*g!X8=5|_`hjF2 zZA!Eb#}D6`A`G2rC+Kj?({TJ2u%b6Bl&b51&#lC1Nj3K@Es+WLiZ8mNOW#IWD4HNI zdo1*W5O{oK56N?&9vRbd9Ew0VbjN2Q{0PsU=!2{eak zqtq9>5GzcQQAw}NcN-g<_-r!DZ?m4x;?vI|k_V6|O{a_J-UT#t3N=B(E7jvq5l1GJ zM%gcr;-9xRj^GzhM5t+te;c;!u-5G0?0od2;QZ9Ak}$GY_=tqWG@=K#M%+&9zgLUj zyB-KD2iqS@2yB(PSuQ}gOFC>d0yY$e4(4{i7Vu_ulF_#Y)^2$PLEuC89M~E%C>WbM zTj|4YTj`rR0xRQ|_2eUwcwTnpt*hQEd3fhE>Zfr)5Wk)%FxxmIM+dA+Ip12eEyJ{cR-?*$$pG2vUU5ITqgC`eF=nCZZ-0- zcu$8g&VZz&wE@qgdDq?D%N_U!Vki zt#)y!Q&GZZ%u;UG65GqpRow1$r>p$UmP#}?Zio{(XNGG2Sd2%&XA`huRgA}T8iitX zkCZN}T=8^Yh7G<|hRLJ(5)y3HvHaCyd zploN|wySxmm?SW~GmdA*Pzt5baM64Y!Y~}=-Vz)%PwZWlOzH>Q9R-E-QM@*1Kd5t*V*~q#1|RpF892Ad~_VLuXwZ1DbOUO7)wjF z5?)o^L%TP0#r6%$Aulx%qga`XNAmJB>X|wbS@)h9=KZ-) z1LR+4;g)xOkQyg_8QoOAiCCKvlD@6bIX5`j+*C_@l|iDWOE4v{zxmac=jaC_hr-N^ zA>+ii+yy=S%AL#0XM^XYiE3LG_v+OGMm`p>KAJpUo_jjm7`CL6LmaIpE?HoCaGIh? zR$;}LzK4AHvQ{tFS*DSS5Z!H#G5E(m(d*u--U!hXr?+|2CJ(K-6+aq2ALSyF)(?uX zBDjYHejdNcAX~DX_=*09PfEZ@FZLTUbya@?OpWa-725b-^HBq*3xQlxX%lv|WF!o< zR&+X=I}GGnTsYK|lwKvv7d7H%v#s7|!m&@Bly$CxhVK_I3DB5fm&+<%Wn!FlIXj1J zZVXRax0XdRZ!U~NsyC-93ez;X)M`_-D&ZNO+K;u14*A)`aWnJ)aj8#A3R>Cdo@o(#Te#p5$D+P>+3b5Bndd!a z6xn@xy7?<7@N@O}cD{+AvTdn^MtoR9!(v%XxYB-U0CI|Y?K}utv>kPv9>pN7$LA1q z(v??3pU~Gklnhb%Xiw{?XHD`g$M*uqpCo`!p^CJ?e=zceD(~{Z(ceVmYMTDO;Fgbc%Y> z`T3q#Eg4v^_Fw4ce}E6jtn4^_c9*^1;=WMfI8vo;KdF3Ttv&gMjH0!x?*)NcesN=0 zvmyKhb}|KReZmC9#nLhJDKWa*a|`SVg60-+`fgDJoz{gH1KoC(u9IJvzlk#zu*ODA zSo}a-){h=Ck}t&YXl2J=suS;AM`U7F&(=E~qno9=pQxm8PbHQ-M*+S87vqbVv`VoG z+8C8HS0W4H&ZoDpr0=|b$ekK0^VBciAyP6=iPqU8ybHTZ4Ud5}4>xUogz(c*b#-mv zbCdgMn|#_Q>2>s{j21$xS4}%dLIN0Q_V9vibt00!{Nz?jpPk5BOiz1%OqTJw`ylN& zHwb{!zIU1G{8YE;eR-D1ef zG;#9T`Iu!Q6T@J>V&UQWa$aBgcE(Jlb8Zf9DIwe@)bKHeTZ|0-pl-|YX%~E-x3TYo zmCV+;TCXq5L96ia-t7XQDRw(OJbxMwti65jtgs&Bm&?@lM2lY%?i&!CUsMnbuYa0Y z*r_4tyHnp#WxX{l@~omBQDu*He{QB-F^B=gQsJ_0xEOmWhijM;A0F>{0ukCIdFV@* zEG9_oso?c|71VJT$(%j2uw{d@=`%UCPF5Cs$~#FJ*(JSIk~GZ0B2(%}Ax6c*M@t5( zN7DxT5XmyErm5l{B8#A=gY)`o(}pdXLM76GFTuTO(v*gUKUg}|dknX5r`(xWFCpj6U z1!C{vOY9?mKrNERYw7ND^}qN0W6R_TOSPO7qAp`ND8VX4Ed>`cLcVjArO|2Tt;*G^ zuT+dO-Sap{>{Z7(L9^?bkw<{H(V6noy1@;Nea1^&vQV|ucHs*)J6W%B!Pq+Hz;a9R zoabsM=P!t6l*-ba%R*sNY5B6^d66ts;UBD*=lFtwBw)RNq9X4h z&rYd>*W(&1tq>%OV*>e%XE^%--`>YctiFH2m^)}c^SyOxpzCXyP?q_byj#3_oGskoHbE6$M%cCAZi$BF+cNWM$7o%6xdB|75k`;u8ROt!pH>-Zk{0YA9QVa#8% zn5m&1S1c(qZxyEx6EJ<;X6WsWY2@LVY?jLTgVS7nUpo4KvJQGqI!67OOrM?4 z`)nef&i`s`^mK^X55=%hZScqiYfta)o`CL(eiM@2=hEUC$M$RzY%fskrwJXE%}BqY z2WQF_*p*_#wABSGTWl(}!*deqxC~EP4DVhPDi5!pmQUzni;MD$qJFe5nz#F0kW2oH zW`J_?%S^aM@jMrWhNGX3O?WdoA{&YtJrA10@JFQ-oSwdp_ibqIq~|m~QN2nrZo2~{ zec1hxiIXxG(6}#~fnjZALbS&-^Pv&0RarG0U&C%wG6}DwsaP?G88x17UVXY;?LN>a$|! zteE554rk&N&H0vQhRv!K0Y%r{{{C14wjPRPjdc9G%NVig9b$$Yf_r z7?+QN&)aI~qNnH6t!8ViS*YF~Il0vHs+EGjl|1=~ygtGI=t*1m2Qyrv!m|yNDT|Qi zduhZ8%vQg&;d8uWAh~SygIe{w2m4lo-#;3Q1>>xX)vms8!>_FW9Ddy zA2?QIrVR1&R?`jhKzuyv^0^rggP6$=z)`JW~?Th9PIS6ct5#& zMX2;0^JrChgOc>pzNp8&mL^9IDYuV(Hwm8$M{r0in+t6Z5-O4E18d+Apk@^Xd691+ zrX8F%WvH`94b1gs99PR5KfV)hRh570Pe>fNl}MSV_5zWXcg3EnuH8@T$*8TEFkF?@+U&8Nf}?Im9vH7ex1NZjAPtTj=Tr?}Il<6B{Q z_I2U$tQZUDL~t}mUf^>v;Z&^YX5z$@$l^VE-RPJ{1@|4wjzswdT{`E7CX0X6$x7_*TW=#dnQvjo zRg5L{McHtJwKtw0D8+B#r>G@u(e-eIZ)@X^8igElOQwBS)6`sCM-E15;^JSNoE-n^ zU>e$C-MBllB#uy7Hn@C#o{!_%m-TM&MS$O(bCNWb#|yOx1$IR5eh{^Ba9fC;Y}kxY z9(%x{HI+?yqnT)9t0NpnUu?9vGC;3 z+`P&8Yy2l&gMBq_1TO}bs-N+nda_fVu-GLnr1eryjxQTj4_54lkHHHURt;QcC+PFW zn(e4ns0nsrC);y9E&VQAUF#QBUS1x)P-4(pOM9_~zPLFPp<1>Xc(fYXz2~rs-E{V~ z|GDbO`p?raZ59?Py7Ca4Wj6zponEx^W|yUoEh`?s@)PgXg#am>3?92otzT#DXUG%2 zG{@2(`PenTi;bJI$F_P-|GXgd<3~!367sQFFBCS+(IjIIjT;}X>RLlHL1N+5ePEC) zxPFwcI4|l$Cn_~2hN!Bc#>33cmB3V|E(A2|bY1)OK`m&98zn6}Lq-@hO(tB+j zpKIyrX5KRpq7Z!UqHg7O)ndyKch*tr!Hdw!99^;TNlJ*u$oWZ3d06>e z#(tc0l$Tv;VIfN$OuX+I7>OwNc<@5^f-XEX{MRp;?TziV>?e;eiv`}b`ns?7q6Mpb z3b+KxKj85=nzLDWNhE`Q$diUoIK|+0zEe+yS%!{L8z{u^L6{EN{mBKr=X6E(Bf{)x`^ zJGO(lsMuAd7U`rHw%6JhO{6>xh5Dz;S3+5G8z@MN3F7hbl_~@~%DohILZufX_coe0 zhE-d#RcI|ewG5DWD4X4*ogC+iw-Xl}(+6hrH=fHLrRNz0YY#OD4`{^^EK%8xt`K~w z_7pzngNQD+$=kf8dyV$vtVn7;OOA~_(PH_?I<8u~_JFmu%j0d1fW`5}>iLY|a{aT} zA5$wdbIll2xW!tJ&Vz^jx`*#MA(95{3U(j1@$%nGzbm;z#cOcoTb*|Zo?HBg?NzNk z?^$JGIMcfs^r2!;Uw|MV!T&o`CzO+G|IN36>cy@5L336Wj>I4d7=A41e*%9KaqmRbWh&T=(%`dUnS}E z4JYlSml}Fs6h`zp2Q0G6b~m2BFML5_mb38X2i0pL?ZPVl0w~IaNp}w_Vl;9i?{?5O zcLHkC^GAG1)zDHQiNomu6R9^DuFRaQP9@w)hkg?JIla5lJ!9J6joOMQzyI`#hO_xL z%tg~5o*}3?LeDiZMQUT`^mfOn@FlWphQq7VK|8};v3E(KlY4KDE?%1AziH?pz;8r4 zs(Fb?L17V}%$CAq!#K0O$q@g_oGRRU;<<&>*UY=c$}eSp9t;a)iCW47Tjoao2hK-7 zo!6O52xil!=Z-r`v9lC5xuMYvTa99`Wjw(ToSlLUUGkGVrWBvs5wi;m5r4O156;#l zQ5L6&m@s2|LmsGY3aMqq;a)rQY#gJxPj`AZ@akw>nxUQ%IZH_aOH`7s>fvbdnwP%E z(Z?DuZh=7(L64{_>Gm!Jl1{bguA#uNt}XU72hpI)w-B*uKaW|2bSH>wOyCa`0VtH#y9qGg{Ykgknve7RyI67&~&LrDn8xB~9*keLA`q&k=P_ z;B(Tr+jNwTuKQ}7%Ke@qej;5GAz`c(#lfl)*K00nN^pYs>eGPU+`hc55Xb0rE_Z{U zog0O3l#1kH`!nVme&!rq?Mz7Li7?9dVJ&~_T`8uD%HUGhG}eEjqOM|0&87OYiLtJ3 zLU2pVQ`_l>2BNlRV`txt?g`(ejjjg1QM%)x~VpLVA<2Bw+ z4EN7yCy9hF4oh^!7s(DPU6L%^smMdLvwgfkP=(RFXvoy71ix)+q#$WHtVt*a-xh$N z8DTOpwjpO}gk?KHTk$EIrGTbz%RVRS3S0Q)Dy^6)Gk1q$tQz%JEgem~SXthHSYv#A z@9~=;H}|6T{dQk(!|1o$izoa>R+lALMGg|@NSRQvWzYCGSNX!mn0_Or{1umh$B`DY z1pKM*;H$%;K=pgL4YW!*=I2;u7X%-XR^Nl*o#k0U9FeHYoU9nm(GOkaLgcc4bg0Tn zzIn!3x3{m^rSVhPcSs0_Yia@hG> z01}?=iy9Vi2HiL9Ll!UeD97Ur6bgGu5H=6vF?>5yP-(}ci22A5gGdgV^oRUpKHAx@ zni$ev`)HcPr6tVA5Uffa;H>g1e8bYjE~g`rZ^=LRlg#o^NK^KeyvU-=rLo{EK6$@3 zn;WxLZV@Xy1kxAoyZZiA)RWZTfYgn9?W#|qu}2&=Z-&=zd`hDgr_EdOLMJ4GL%S6x zOP@!p6-W3&>gQRj7~L^J!DAw^XTx|Q15#!rGtp5`sXAlau>vj8Mdh#tbp7OWuKyIyhy(DWgR)IDva1q0mq9eOVq7HcRgzjODkIVN~Q{9Lv@Mgr3?TN z$g>|-DG<4%cyJ(8%Y1!FY?`$Lo~u?lTR&3|bSeH>E2H#m?aW@qb8Qh{)O&dJp4EzY z^>%j)$yOGk1!a+KxzNRu#VmD1XGUbKyTnmDN1yVJ1FEFsJF@CaeCyN{4c?|9l&~Da zxEj@UpQafTMChb*9fEU{3Fc&;PEh0HVgJM$h>Y17hv6R5Rr-%~k)|lqV(3{>^PXY9 zN{`0WI~yqP{NyKUE>v$u!CAvSBRmTaVz)D}mgW`zniG8EzXNO6p&?idjg5_&{g1%f zpIo572G#)5lCy!+4RuCB1Qu$8@o1P)nS}Hmjjwqi0QKgt;Wc3i5k+@LAetp%V`2-C zL_h$~#nc>#?74%e1p%&;YZVY1q&f93iB75_8vc^l&cxM;AJx(%DcT1LXg93WN!yf#BcV<(V_xs8#-Ez1aG z0Ca;5DiGi_G6$$RH@WM-hSvIaKwR?ws^64_76093AR}WFz!1MiMGSz> zpAy$>ETArc2IQtPFm{h4jK1WcZv(KWcwo#mJ`j%>@VO=s{o_9Kjh2DQ-^FjWu9IsU z5Wof)%)-nE(gaf0n=G5?bt&6Tjv3|$42H7a6pCFZ?0^PP$OgU1v0Nw68wK{;!dnH- z>wC<%A~y-kjZK(enETsWu-{1Dcwo6vXSp%P!f_*cqt1FG!geFVdSiw4*58c?`;76=ZkmAQw(_@*ZWLH=JTTu_VP(5fxRGSNefFC| zHldq)fX!=0*YEBCLipFi>W{%-=mdT`dVt9H$MCeXwS!GX*WZ6nSumE_ zzoY(tnVewb2_RJdr&&qV1{glD8Rw4~gF*avgx*Ns#tAmpID#}G*VH~BzOQ7fY-0`^ zaiHt>PT@}u8}px>!oSXw|ABy@`X>QF$kxi}Kd=utI2c*jIXJ;=*X#o@BM``kT|*$O zER0}S2E@t2&IpFUND2QP``}NZ-_Pa*0@w#&fcXh!0p9$-u@C+#e8WCq2iON3oVPp$ zSc}}S519Yp0p1cbt_yB@<2T&^m`zy$cHi$T2$)L$SL_38HBT$eNq7GBgI96l`}Xec zCqCK-2NYrVLv1C5_M3D-MsrH0A!OCp&S9$i6 zR=5dpAKq=>cb(qIYU9f6nrQeC#wf}T5+0=|K$^||r5#pfJbm=#ApJ+$`q8QPB54Oo z7XIPgM<7-(mepy{w+|$DR`Ht@O%rO?n40%B7{>X9#ZLl)>X*0X;?ti57O?w0(S-B2 zl0Ls%<$KrKM*rlz@<%p4Mpdi-udUII=M{xdJ&;L1<5oTG>oOf8MbijoYi+U4tK&&L z?)vg$`0|cWCj~i3Go)2WC@^sG(aVP&6Bp%QGKM`7iXJ`)!}kY?-ywA)$l@mwQEV(#~HLx@Fh6TwxI^HploZuaDHB@w<55vr2wt&F#uYZLj^o4sWu?DVU-(I3a~4^kS#JU%@C8DP{h~ zKpCJSEk%JVm0P7@3f4G8iq{~+UZ}?zLet#6V3=O7)i8%{qCnZGcyz+0OO*?0pe)`C z=4@to)nWP(9#LZ!@9N&F(3W-8d`2coMsElRPu7!1eGzb#M@D}gyX|B zOYR?)1HEf&)AebVyZvdv+c&(hpu`oh%DlerV2>d~zP{$_ve3u_olkePOMN=Y$Ls8F z=|MZW^p(T(nT-X$epSm6A`N+D2qIr0IIk>~sHiFIzPXe4BA@Wfo-gfKK^|4VA?v)Fzrw)8tQ31%S{9!yT_JAE&NO+ru=~+xG`NKN zSiDU6On*FjcMYpupY_aT9qG?Uviuv5L+A{gYyzJ0wcYBA1r|VAo4+r1s z){iFiR%yFaq)}k$rlj<`Hl*TmYp0zg)tB?Kb5F**HfRlcP0nqt?nbAzQFUJ)pRE|W zc@fTGxc_PawLD5+ZVrsof_ZU zOjtmZ(AOM;h<#%e09G|K9=h5?r8~-l%0z> z)`csK@(XKXx^_l&Vr>0q0OhhHIr#3|qUHRWMGK{ZlGORlUuQW_>s?w&hK+H~?iz0Q zCQy#z;u=JHh#?*uycfw1N*|fWTb@-ML1F#S2yI&*Xy#qgg?m&SQyI|_%<1%AI{HB^ zI?0wa*=hUW;ByFZcvms+6zfHs`NL>K0h}acdd09WO^PQ z@3f5R(sJ|m>@(ZI-xp~!f8?3#SON8#-3wVkRmvMy`dI?ul}GS`o6mCB||8Kc;k zJCH4|{)8LPQ!u2Opl4->ebsRkbqJ7Y-eNJ+Ka!#pDiQ7qvE(Rg+}v9Bg|Z#1r6hM3 zqL=riWcECXDXF4Y-CMXgdiy2XW$0nY{8^`E2e+%#4}Q9|2DF?z8@*roeg#1oHygH& zSKUzJm@DtbsZQb6guTF%}t>W_G*|@s8C0k0E`oG zI!e6Fy~&DP8O5z%XW$-ZCz+upH+z?s;mtyJ{A9^zp+VGv<_u%QF_sx`K32K&(qdcM z&L2JUdn{v3?+q?z{zmAXrGZNb zB59ErIyGxko1z)@}RJQ;E1o5`y+9 z6Y-oFk(sDI^6zhUFrAcOR|zzq2pq5Sat?cYW}s=bRTdu<-!(md@J31MT<@5dnt(6D zOfq=oCHZ$jo>~KbSR=fA@a!!!Nu_@%{MxHkcs5D zyTk>!rR`ShLd`8m=)YDS5a6@qz-^pWFRo(EuD)2&Kf(y)S%}P5&_FW>A<8kt7sPQz zwik{K$7zYtK=F}hu{J(ckrQlSctmV#8Ti+d7b@wfQY}^Hz7x*LQDUxt^j;w0=NXH) z^|aowUYkM)KbLjauI(MkkP|gsF%(JJ7L^TZZ?Vf?ldUb~RK930qYgZuIH(;;J7EoE zItCjR<=w|y_OVqvA0{q-k7Y)DWM;2EipdAbEoOdOEEayhh-MMFWDGwiz529?p)9>@ z$E|W+zp{okID(n$Tj>bKrna?Y|;hN#H$J{0T-Me8)q zvt2zQE~35$>j(Wm#UzMK5+2(pY28;@zjPBOjiru9&(SA8#Ay zR>vW1rT%{CkU{b&n;#Y3 zKO}rE%eht2Ijvf0Eu;Eu=kROyNocPF*K-BQ6eyON6jTUKj)t3yNo(t)y+Wwq?hhk{ zpI^A0A0-Ld9*pZyz&K1|!&v8I9^eh1q{$%}mUjkMgTyN~t*I7`0?$Y{pPth)SUcCq*B*U;p4CQI=X zLC8*>Fj$c!QE(YN$~f+klq9{MKT32Flx`G0UlCrJ6kaITHmD$3;?JGSk1LBLq@q&z z=zMqUo0{juc4z8iIXACDmUtP~XUnm#rZaQFu?D!%6*)O1LwYWby}8vI`l0f9Ogw!l z>b4mr$>(GA1Skg~^NmLoJ`Wo~<)Zuf>L2g?^4KKD|M*)A2`I?;C*wH zo}N@6Ik5aZt7oN4sI+?pGuW`gY-)R=eZ?g>2ht```z(io?_w!(^KrhvkMG`i=T8Fu zChv>x&OrVU`(b{F{mSX~E+hS0W-j=bbc&SDF8kcJOpI??xSDkmC72k=E`9Obe(*PY zyDN;G@}Fn0%JSuDW-AMrMu^;(cyo1>!x}BOjpivQQoI_E$>-sKdLrUj@Ve>c#F8|c z(?{W#!w;5(ak82tTm548h#Mqavgl8^yrMf&RflUp!8N_aUjyxJQCp(CzFKFth9B(x z`oN8{+jrbkfYYKswN9C^?BWG~6~Q5?Ma4(T_YehP)4$li^{Ki;%z50o(DC~{X?dN) zmO91r$>qh*giIMq(Gw5USHFhlBMrbG=)Hf^@EJvU1NXClWvh(9yiy@r454E@SEy25 zp#n~}U~2KWsDN*?!qr2K_T3!JD%1)a;Ix3K6H?4Jk5K_}h_B8uTh;@Mz)9Ug=v{04 z?is!DC-{L$QZb$-nd+H@f+^qd%&aq|hCj9Mk|hTaUvk68h41m+-<%zWWZJehV%E0S zAIveTnddb*@|V0rS0Z@-Axt#0RER-NCqvg1v)fw9#5%!sZL%U>b+bRu0Rde4 zaRu_Ek5>>@} z*7ZI^m6&p}TG7n?QMJ-kCwPk#us^@PGxF63qL=#g{2zYpVKs?_8-GeV@bdX-;GQ@Q zuQ8MO=_ww%U$b?Gi?tiVry08oXp0l*~-0Gt9~CMy_7S(vZoSXlsl&Od?A-vB5x03yJy z{{eFTd7u&W`yd0%(*FlJ|AvYF2app2;B_#N^Y17n+by1X3m)DApf_ONEe?7Mfd0l8 zZxFuUK;I1r_#17!!4RQ003q8Al*oRQyTwnLZyfyt*1UxoZ$U=@rTl*i7yj3Wg#Jgk z@aCoaUt)#VFSx?rvBK+fynn$8|Cj=PA3wf6b&Sd+Wp3mMLe)gQmIq+|+oR7A;H2#> zbPt^L77+sgMq@Vs&zGau6sd3_JZ>85qnA z%qRaWV_^m6%74f~!T&B}0RlPyl4Aq<`X9QSz{#zDlmRiGf3^V_3=8D^Lk`UOZ+pzZ zk*t5pF>?U3(m!-Lq3r*%$N68>g%b+>r#%Rm<6m_F&Yt~S4hjYWTz~fqfwHjtvyT8x zAPDt`oRfn-aE{F3`v01sXzl@k+U&p)HduV__K=u@tu1hr{^p?i^<1m(;B-B-fFS{8 ML8YJ&l@&w%KPTX!$^ZZW literal 0 HcmV?d00001 diff --git a/documents/logs/Quotation - S00022.pdf b/documents/logs/Quotation - S00022.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7a423fce867f8a299d42b4e9edd284ed394fbbb8 GIT binary patch literal 16763 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�ps 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 cQTR;Dib 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 + + + + + + + diff --git a/lims_management/demo/analysis_demo.xml b/lims_management/demo/z_analysis_demo.xml similarity index 100% rename from lims_management/demo/analysis_demo.xml rename to lims_management/demo/z_analysis_demo.xml diff --git a/lims_management/demo/lims_demo.xml b/lims_management/demo/z_lims_demo.xml similarity index 100% rename from lims_management/demo/lims_demo.xml rename to lims_management/demo/z_lims_demo.xml diff --git a/lims_management/models/__init__.py b/lims_management/models/__init__.py index cdf3ffc..4ffd5f7 100644 --- a/lims_management/models/__init__.py +++ b/lims_management/models/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- from . import partner from . import product -from . import analysis_range \ No newline at end of file +from . import analysis_range +from . import sale_order \ No newline at end of file diff --git a/lims_management/models/__pycache__/__init__.cpython-312.pyc b/lims_management/models/__pycache__/__init__.cpython-312.pyc index ddb34cfc64fec65d95a91bc32b17e30edeeb4674..51f1d6557bbc98485406e20e5a35a9de312b7a04 100644 GIT binary patch delta 122 zcmey%IG>64G%qg~0}uqQD9Nat$ScWcGErSqAcY}?C5IuGC5nZSp^{aTZDNS7iYDVN zuHwX;)cE|Ol++?WP1aisMa)2*MJynK6-2O2oaC*{4P-F_aj_tf_`uA_$oP~&^eza= M++&a^;slBU0MmOKegFUf delta 88 zcmbQw^p}zMG%qg~0}$9xD#=Kg$ScXHF;QKUrIJOHbz*|9pr0noErudypnMSvh+v(# h(OUteo)L(P`GCX+W=2NFrwpQZ8AR_f$P{q^g#b`o5sm-= diff --git a/lims_management/models/__pycache__/analysis_range.cpython-312.pyc b/lims_management/models/__pycache__/analysis_range.cpython-312.pyc index dc8ed33c9343294f628f00f8267b2f2c9db4b983..5cdb964b68c340ed724802615283a71209bb5d45 100644 GIT binary patch delta 19 ZcmX@hb(V|kG%qg~0}z~Fwvo%36#zFQ1t0(b delta 19 ZcmX@hb(V|kG%qg~0}z-`+Q{Y13IH=N1atrZ diff --git a/lims_management/models/__pycache__/partner.cpython-312.pyc b/lims_management/models/__pycache__/partner.cpython-312.pyc index 8282316dccbbeefd1d2127a7041286533e11e48a..cb379f77e55b67a891bf8f27276ba019eefccb04 100644 GIT binary patch delta 19 ZcmbOvI7yJ}G%qg~0}z~FwvnrY0{}2m1poj5 delta 19 ZcmbOvI7yJ}G%qg~0}!YeZ{+IW001jv1SJ3f diff --git a/lims_management/models/__pycache__/product.cpython-312.pyc b/lims_management/models/__pycache__/product.cpython-312.pyc index 6e5e283b4c089f4d4b136918a863dc43d4e98ec8..b04b7884ebab6dc50fa20d493aabeacb959715b3 100644 GIT binary patch delta 19 ZcmX@dd5)9oG%qg~0}z~Fwvp=y3jjDK1)u-` delta 19 ZcmX@dd5)9oG%qg~0}$9u+Q@Z;1pqWq1o;2} diff --git a/lims_management/models/__pycache__/sale_order.cpython-312.pyc b/lims_management/models/__pycache__/sale_order.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82b79cf563130e9f256a8880177c93d266636f1e GIT binary patch literal 914 zcmZuvPjAyO6nD}zO}lp45FP&x7;)Glpp`gqf(Z~BOt4LY!6ArQrgj%kN!&T9AUzFf zA7DH6D}c6d!WW2qLgIk9P1?BZgq^GmBpl`W_x%3-{QSPQS`DDq^UrC}F#-6lmC34} zf#XF5#()4~gdj3}BQkx{048_}h`9?$g&JG-Bw}&)ImqttLv=_vz zheKFg1Ew4@LgSS6GsyZd4Jg!!V37ziohLlcBg#A-&yseM3Knn58linW3{z?K_#l&3 zK*PakO3Y20&k1&gc?)|D)mkvTPjwnL1fslI3VW@Ev zq3t0K&nymNaXDjMAxWJ1qSA#~~FkrKNgcdzX{`_{g> z_h!7YzmdBO`Aj=+UC3LnPwLe=foto>U7|o%>~j7R42%8tP9jD{)jY4C4p5eqy + + + Solicitudes de Laboratorio + sale.order + list,form + [('is_lab_request', '=', True)] + {'default_is_lab_request': True} + +

+ Crea una nueva solicitud de laboratorio +

+
+
+ + + + + + + + + + sale.order.form.inherit.lims + sale.order + + + + + + + Paciente + + + [('is_analysis', '=', True)] + + + + + + + sale.order.tree.inherit.lims + sale.order + + + + + + + + + + diff --git a/verify_products.py b/verify_products.py new file mode 100644 index 0000000..a2b3a4f --- /dev/null +++ b/verify_products.py @@ -0,0 +1,30 @@ +import odoo +import json + +def verify_lab_order_products(cr): + cr.execute(""" + SELECT + so.name AS order_name, + sol.id AS line_id, + pt.name->>'en_US' AS product_name, + pt.is_analysis + FROM + sale_order so + JOIN + sale_order_line sol ON so.id = sol.order_id + JOIN + product_product pp ON sol.product_id = pp.id + JOIN + product_template pt ON pp.product_tmpl_id = pt.id + WHERE + so.is_lab_request = TRUE; + """) + return cr.fetchall() + +if __name__ == '__main__': + db_name = 'lims_demo' + registry = odoo.registry(db_name) + with registry.cursor() as cr: + results = verify_lab_order_products(cr) + + print(json.dumps(results, indent=4)) From 93624bc11164b1ab420dd28678a0f1a056b24fce Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 02:31:23 -0600 Subject: [PATCH 5/9] \"docs: Anadir seccion sobre creacion de datos de demostracion complejos\" --- GEMINI.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/GEMINI.md b/GEMINI.md index da0960e..c496a6a 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -324,3 +324,91 @@ Interactuar con la base de datos directamente usando `psql` a través de `docker ``` Este método evita los problemas de entrecomillado y permite ejecutar consultas complejas de manera confiable. + +--- + +## Creación de Datos de Demostración Complejos + +Cuando los datos de demostración tienen dependencias complejas o requieren lógica de negocio (por ejemplo, cambiar el estado de un registro, o crear registros relacionados que dependen de otros), el uso de archivos XML puede ser limitado y propenso a errores de carga. + +En estos casos, es preferible utilizar un script de Python para crear los datos de demostración. + +### Procedimiento + +1. **Crear un Script de Python:** + Crea un script que utilice el ORM de Odoo para crear los registros de demostración. Esto permite utilizar la lógica de negocio de los modelos, como los métodos `create` y `write`, y buscar registros existentes con `search` y `ref`. + + **Ejemplo (`create_lab_requests.py`):** + ```python + import odoo + + def create_lab_requests(cr): + env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + + # Eliminar órdenes de venta de demostración no deseadas + unwanted_orders = env['sale.order'].search([('name', 'in', ['S00001', ...])]) + for order in unwanted_orders: + try: + order.action_cancel() + except Exception: + pass + try: + unwanted_orders.unlink() + except Exception: + pass + + # Crear solicitudes de laboratorio + patient1 = env.ref('lims_management.demo_patient_1') + doctor1 = env.ref('lims_management.demo_doctor_1') + hemograma = env.ref('lims_management.analysis_hemograma') + + env['sale.order'].create({ + 'partner_id': patient1.id, + 'doctor_id': doctor1.id, + 'is_lab_request': True, + 'order_line': [ + (0, 0, {'product_id': hemograma.product_variant_id.id, 'product_uom_qty': 1}) + ] + }) + + if __name__ == '__main__': + db_name = 'lims_demo' + registry = odoo.registry(db_name) + with registry.cursor() as cr: + create_lab_requests(cr) + cr.commit() + ``` + +2. **Integrar el Script en la Inicialización:** + Modifica el script `init_odoo.py` para que ejecute el script de creación de datos después de que Odoo haya terminado de instalar los módulos. + + **En `docker-compose.yml`**, asegúrate de que el script esté disponible en el contenedor `odoo_init`: + ```yaml + volumes: + - ./create_lab_requests.py:/app/create_lab_requests.py + ``` + + **En `init_odoo.py`**, añade la lógica para ejecutar el script: + ```python + # --- Lógica para crear datos de demostración personalizados --- + print("Creando datos de demostración complejos...") + sys.stdout.flush() + + with open("/app/create_lab_requests.py", "r") as f: + script_content = f.read() + + create_requests_command = f""" + odoo shell -c {ODOO_CONF} -d {DB_NAME} <<'EOF' +{script_content} +EOF + """ + + result = subprocess.run( + create_requests_command, + shell=True, + capture_output=True, + text=True, + check=False + ) + ``` +Este enfoque proporciona un control total sobre la creación de datos de demostración y evita los problemas de dependencia y orden de carga de los archivos XML. From e568d30f6b0418d7c9c002a5e0184d158c6dc5b1 Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 02:37:43 -0600 Subject: [PATCH 6/9] \"docs(#6): Actualizar plan con nuevas tareas de filtrado en vistas\" --- documents/plans/ISSUE6_PLAN.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/documents/plans/ISSUE6_PLAN.md b/documents/plans/ISSUE6_PLAN.md index 2ef6a24..0d51f58 100644 --- a/documents/plans/ISSUE6_PLAN.md +++ b/documents/plans/ISSUE6_PLAN.md @@ -23,10 +23,11 @@ Por lo tanto, el plan se centrará en adaptar y extender el modelo `sale.order` - **2. Crear Vistas para Solicitudes de Laboratorio:** - [x] Crear el archivo de vistas `lims_management/views/sale_order_views.xml`. - - [x] **Heredar la vista de formulario de `sale.order`** para: - - Añadir el campo `doctor_id` cerca del campo del paciente. - - Cambiar la etiqueta (string) del campo `partner_id` de "Cliente" a "Paciente". - - En las líneas de la orden (`order_line`), aplicar un dominio al campo `product_id` para que **solo permita seleccionar productos que sean análisis clínicos** (`is_analysis = True`). + - [ ] **Heredar la vista de formulario de `sale.order`** para: + - [ ] Añadir el campo `doctor_id` cerca del campo del paciente. + - [ ] Cambiar la etiqueta (string) del campo `partner_id` de "Cliente" a "Paciente". + - [ ] **(Nuevo)** Aplicar un dominio al campo `partner_id` para que solo muestre contactos que sean pacientes (`is_patient = True`). + - [ ] **(Nuevo)** Corregir y asegurar que el dominio en el campo `product_id` de las líneas de la orden restrinja la selección únicamente a análisis clínicos (`is_analysis = True`). - [x] **Heredar la vista de lista (tree/list) de `sale.order`** para: - Añadir la columna "Médico Remitente" (`doctor_id`). From abe27b919584c44cca5a4ced26a1cb4787febe90 Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 02:43:52 -0600 Subject: [PATCH 7/9] \"docs(#6): Actualizar plan con tareas de filtrado completadas\" --- documents/plans/ISSUE6_PLAN.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/documents/plans/ISSUE6_PLAN.md b/documents/plans/ISSUE6_PLAN.md index 0d51f58..354943d 100644 --- a/documents/plans/ISSUE6_PLAN.md +++ b/documents/plans/ISSUE6_PLAN.md @@ -23,11 +23,11 @@ Por lo tanto, el plan se centrará en adaptar y extender el modelo `sale.order` - **2. Crear Vistas para Solicitudes de Laboratorio:** - [x] Crear el archivo de vistas `lims_management/views/sale_order_views.xml`. - - [ ] **Heredar la vista de formulario de `sale.order`** para: - - [ ] Añadir el campo `doctor_id` cerca del campo del paciente. - - [ ] Cambiar la etiqueta (string) del campo `partner_id` de "Cliente" a "Paciente". - - [ ] **(Nuevo)** Aplicar un dominio al campo `partner_id` para que solo muestre contactos que sean pacientes (`is_patient = True`). - - [ ] **(Nuevo)** Corregir y asegurar que el dominio en el campo `product_id` de las líneas de la orden restrinja la selección únicamente a análisis clínicos (`is_analysis = True`). + - [x] **Heredar la vista de formulario de `sale.order`** para: + - [x] Añadir el campo `doctor_id` cerca del campo del paciente. + - [x] Cambiar la etiqueta (string) del campo `partner_id` de "Cliente" a "Paciente". + - [x] **(Nuevo)** Aplicar un dominio al campo `partner_id` para que solo muestre contactos que sean pacientes (`is_patient = True`). + - [x] **(Nuevo)** Corregir y asegurar que el dominio en el campo `product_id` de las líneas de la orden restrinja la selección únicamente a análisis clínicos (`is_analysis = True`). - [x] **Heredar la vista de lista (tree/list) de `sale.order`** para: - Añadir la columna "Médico Remitente" (`doctor_id`). From 55b399f5e4341aaf9ebde8fead6e3d727b5c5df9 Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 03:09:03 -0600 Subject: [PATCH 8/9] \"feat(#6): Implementar solicitudes de laboratorio y corregir filtros\" --- documents/plans/ISSUE6_PLAN.md | 2 +- get_view_arch.py | 14 ++++++++++++++ lims_management/views/sale_order_views.xml | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 get_view_arch.py diff --git a/documents/plans/ISSUE6_PLAN.md b/documents/plans/ISSUE6_PLAN.md index 354943d..69d4a0e 100644 --- a/documents/plans/ISSUE6_PLAN.md +++ b/documents/plans/ISSUE6_PLAN.md @@ -27,7 +27,7 @@ Por lo tanto, el plan se centrará en adaptar y extender el modelo `sale.order` - [x] Añadir el campo `doctor_id` cerca del campo del paciente. - [x] Cambiar la etiqueta (string) del campo `partner_id` de "Cliente" a "Paciente". - [x] **(Nuevo)** Aplicar un dominio al campo `partner_id` para que solo muestre contactos que sean pacientes (`is_patient = True`). - - [x] **(Nuevo)** Corregir y asegurar que el dominio en el campo `product_id` de las líneas de la orden restrinja la selección únicamente a análisis clínicos (`is_analysis = True`). + - [x] **(Nuevo)** Corregir y asegurar que el dominio en el campo `product_template_id` de las líneas de la orden restrinja la selección únicamente a análisis clínicos (`is_analysis = True`). - [x] **Heredar la vista de lista (tree/list) de `sale.order`** para: - Añadir la columna "Médico Remitente" (`doctor_id`). diff --git a/get_view_arch.py b/get_view_arch.py new file mode 100644 index 0000000..ce70c94 --- /dev/null +++ b/get_view_arch.py @@ -0,0 +1,14 @@ +import odoo + +def get_view_arch(cr, view_id): + cr.execute("SELECT arch_db FROM ir_ui_view WHERE id = %s", (view_id,)) + return cr.fetchone()[0] + +if __name__ == '__main__': + db_name = 'lims_demo' + registry = odoo.registry(db_name) + with registry.cursor() as cr: + env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + view = env.ref('sale.view_order_form') + print(f"View ID: {view.id}") + print(get_view_arch(cr, view.id)) \ No newline at end of file diff --git a/lims_management/views/sale_order_views.xml b/lims_management/views/sale_order_views.xml index 63eb556..7aeec47 100644 --- a/lims_management/views/sale_order_views.xml +++ b/lims_management/views/sale_order_views.xml @@ -13,8 +13,9 @@ Paciente + [('is_patient', '=', True)] - + [('is_analysis', '=', True)] From c557014cac8978ee93f52b149c1e24d6e96a32e7 Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Mon, 14 Jul 2025 03:16:59 -0600 Subject: [PATCH 9/9] \"chore: Anadir hook de pre-commit para evitar commits incompletos\" --- README.md | 26 ++++++++++++++++++++++++++ scripts/hooks/pre-commit | 13 +++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 README.md create mode 100644 scripts/hooks/pre-commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..b21d66c --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Proyecto de Laboratorio Clínico (LIMS) + +Este proyecto contiene el desarrollo de un módulo de gestión de laboratorios clínicos para Odoo 18. + +## Desarrollo + +### Hook de Pre-Commit + +Para asegurar la integridad de los commits y evitar que se suban cambios incompletos, este repositorio incluye un hook de `pre-commit`. + +**Propósito:** +El hook revisa automáticamente si existen archivos modificados que no han sido agregados al "staging area" cada vez que se intenta realizar un commit. Si se detectan cambios sin agregar, el commit es abortado. + +**Instalación (Obligatoria para todos los desarrolladores):** + +Para activar el hook en tu copia local del repositorio, ejecuta los siguientes comandos desde la raíz del proyecto: + +```bash +# Copia el hook desde el directorio de scripts a tu directorio local de git +cp scripts/hooks/pre-commit .git/hooks/ + +# Dale permisos de ejecución (necesario en macOS y Linux) +chmod +x .git/hooks/pre-commit +``` + +Una vez instalado, el hook se ejecutará en cada commit, ayudando a mantener un historial de cambios limpio y completo. diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit new file mode 100644 index 0000000..4f5852f --- /dev/null +++ b/scripts/hooks/pre-commit @@ -0,0 +1,13 @@ +#!/bin/sh +# +# Pre-commit hook que verifica si hay cambios sin agregar al staging area. +# Si se encuentran cambios sin agregar, el commit se aborta. + +# Revisa si hay archivos modificados pero no agregados (unstaged) +if ! git diff-index --quiet HEAD --; then + echo "Error: Hay cambios sin agregar al commit." + echo "Por favor, agrega todos los archivos relevantes con 'git add .' o 'git add ' antes de hacer commit." + exit 1 +fi + +exit 0