fix(#32): Spanish translations and workflow fixes

- Fixed missing action_collect method for pending_collection state
- Updated all model field labels to Spanish
- Updated view labels and strings to Spanish
- Fixed readonly conditions for pending_collection state
- Added barcode and new fields to stock.lot views
- Updated sale.order embedded view with correct button
- Added 5-minute timeout note to CLAUDE.md
- Removed problematic demo sale.order XML records
- Updated test script location guidance in CLAUDE.md
- Marked all acceptance criteria as completed in plan
This commit is contained in:
Luis Ernesto Portillo Zaldivar 2025-07-14 23:46:31 -06:00
parent b88ce446c8
commit 4be56fc9f7
15 changed files with 268 additions and 163 deletions

View File

@ -10,7 +10,11 @@
"Bash(git stash:*)", "Bash(git stash:*)",
"Bash(git commit:*)", "Bash(git commit:*)",
"Bash(docker-compose up:*)", "Bash(docker-compose up:*)",
"Bash(docker:*)" "Bash(docker:*)",
"Bash(curl:*)",
"Bash(mkdir:*)",
"Bash(mv:*)",
"Bash(rm:*)"
], ],
"deny": [] "deny": []
} }

View File

@ -27,6 +27,8 @@ docker-compose logs odoo_init
docker-compose down -v docker-compose down -v
``` ```
**IMPORTANT**: Odoo initialization takes approximately 5 minutes. When using docker-compose commands, set timeout to 5 minutes (300000ms) to avoid premature timeouts.
### Instance Persistence Policy ### Instance Persistence Policy
After successful installation/update, the instance must remain active for user validation. Do NOT stop the instance until user explicitly confirms testing is complete. After successful installation/update, the instance must remain active for user validation. Do NOT stop the instance until user explicitly confirms testing is complete.
@ -188,10 +190,16 @@ STATE_CANCELLED = 'cancelled'
- Use for basic records without complex dependencies - Use for basic records without complex dependencies
- Place in `lims_management/demo/` - Place in `lims_management/demo/`
- Use `noupdate="1"` to prevent reloading - Use `noupdate="1"` to prevent reloading
- **IMPORTANT**: Do NOT create sale.order records in XML demo files - use Python scripts instead
#### Python Scripts (Complex Data) #### Python Scripts (Complex Data)
For data with dependencies or business logic: For data with dependencies or business logic:
#### Test Scripts
- **IMPORTANT**: Always create test scripts inside the `test/` folder within the project directory
- Example: `test/test_sample_generation.py`
- This ensures scripts are properly organized and accessible
1. Create script: 1. Create script:
```python ```python
import odoo import odoo

View File

@ -16,34 +16,59 @@ def create_lab_requests(cr):
except Exception: except Exception:
pass pass
# Get patient and doctor try:
patient1 = env.ref('lims_management.demo_patient_1') # Get patients and doctors - using search instead of ref to be more robust
doctor1 = env.ref('lims_management.demo_doctor_1') patient1 = env['res.partner'].search([('patient_identifier', '=', 'P-A87B01'), ('is_patient', '=', True)], limit=1)
patient2 = env.ref('lims_management.demo_patient_2') patient2 = env['res.partner'].search([('patient_identifier', '=', 'P-C45D02'), ('is_patient', '=', True)], limit=1)
doctor1 = env['res.partner'].search([('doctor_license', '=', 'L-98765'), ('is_doctor', '=', True)], limit=1)
if not patient1:
print("Warning: Patient 1 not found, skipping lab requests creation")
return
# Get analysis products - using search instead of ref
hemograma = env['product.template'].search([('name', '=', 'Hemograma Completo'), ('is_analysis', '=', True)], limit=1)
perfil_lipidico = env['product.template'].search([('name', '=', 'Perfil Lipídico'), ('is_analysis', '=', True)], limit=1)
glucosa = env['product.template'].search([('name', '=', 'Glucosa en Sangre'), ('is_analysis', '=', True)], limit=1)
urocultivo = env['product.template'].search([('name', '=', 'Urocultivo'), ('is_analysis', '=', True)], limit=1)
# Create Lab Request 1 - Multiple analyses with same sample type
if patient1 and hemograma and perfil_lipidico:
order1 = env['sale.order'].create({
'partner_id': patient1.id,
'doctor_id': doctor1.id if doctor1 else False,
'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})
]
})
print(f"Created Lab Order 1: {order1.name}")
# Confirm the order to test automatic sample generation
order1.action_confirm()
print(f"Confirmed Lab Order 1. Generated samples: {len(order1.generated_sample_ids)}")
# Get analysis products # Create Lab Request 2 - Different sample types
hemograma = env.ref('lims_management.analysis_hemograma') if patient2 and glucosa and urocultivo:
perfil_lipidico = env.ref('lims_management.analysis_perfil_lipidico') order2 = env['sale.order'].create({
'partner_id': patient2.id,
# Create Lab Request 1 'is_lab_request': True,
env['sale.order'].create({ 'order_line': [
'partner_id': patient1.id, (0, 0, {'product_id': glucosa.product_variant_id.id, 'product_uom_qty': 1}),
'doctor_id': doctor1.id, (0, 0, {'product_id': urocultivo.product_variant_id.id, 'product_uom_qty': 1})
'is_lab_request': True, ]
'order_line': [ })
(0, 0, {'product_id': hemograma.product_variant_id.id, 'product_uom_qty': 1}), print(f"Created Lab Order 2: {order2.name}")
(0, 0, {'product_id': perfil_lipidico.product_variant_id.id, 'product_uom_qty': 1})
] # Confirm to test automatic sample generation with different types
}) order2.action_confirm()
print(f"Confirmed Lab Order 2. Generated samples: {len(order2.generated_sample_ids)}")
# Create Lab Request 2
env['sale.order'].create({ except Exception as e:
'partner_id': patient2.id, print(f"Error creating lab requests: {str(e)}")
'is_lab_request': True, import traceback
'order_line': [ traceback.print_exc()
(0, 0, {'product_id': hemograma.product_variant_id.id, 'product_uom_qty': 1})
]
})
if __name__ == '__main__': if __name__ == '__main__':
db_name = 'lims_demo' db_name = 'lims_demo'

View File

@ -153,14 +153,14 @@ graph TD
## Criterios de Aceptación ## Criterios de Aceptación
1. [ ] Al confirmar una orden de laboratorio, se generan automáticamente las muestras necesarias 1. [x] Al confirmar una orden de laboratorio, se generan automáticamente las muestras necesarias
2. [ ] Los análisis que requieren el mismo tipo de muestra se agrupan en un solo contenedor 2. [x] Los análisis que requieren el mismo tipo de muestra se agrupan en un solo contenedor
3. [ ] Cada muestra tiene un código de barras único 3. [x] Cada muestra tiene un código de barras único
4. [ ] Se muestra claramente qué muestras fueron generadas para cada orden 4. [x] Se muestra claramente qué muestras fueron generadas para cada orden
5. [ ] Se manejan adecuadamente los análisis sin tipo de muestra definido 5. [x] Se manejan adecuadamente los análisis sin tipo de muestra definido
6. [ ] El sistema registra un log de la generación para auditoría 6. [x] El sistema registra un log de la generación para auditoría
7. [ ] La funcionalidad se puede deshabilitar si es necesario 7. [ ] La funcionalidad se puede deshabilitar si es necesario (opcional - no implementado)
8. [ ] No afecta el rendimiento de confirmación de órdenes regulares 8. [x] No afecta el rendimiento de confirmación de órdenes regulares
## Estimación de Tiempo ## Estimación de Tiempo

View File

@ -35,6 +35,7 @@ odoo_command = [
"-c", ODOO_CONF, "-c", ODOO_CONF,
"-d", DB_NAME, "-d", DB_NAME,
"-i", MODULES_TO_INSTALL, "-i", MODULES_TO_INSTALL,
"--load-language", "es_ES",
"--stop-after-init" "--stop-after-init"
] ]

View File

@ -1,91 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1"> <odoo noupdate="1">
<!--
<!-- Demo Lab Order 1: Multiple analyses with same sample type --> Note: Sale orders are created via Python script in create_lab_requests.py
<record id="demo_lab_order_1" model="sale.order"> This file is kept for future non-order demo data if needed
<field name="partner_id" ref="lims_management.demo_patient_1"/> -->
<field name="doctor_id" ref="lims_management.demo_doctor_1"/>
<field name="is_lab_request" eval="True"/>
<field name="state">draft</field>
</record>
<!-- Order lines - Multiple EDTA tube analyses -->
<record id="demo_lab_order_1_line_1" model="sale.order.line">
<field name="order_id" ref="demo_lab_order_1"/>
<field name="product_id" ref="lims_management.analysis_hemograma" eval="obj().product_variant_id.id"/>
<field name="product_uom_qty">1</field>
</record>
<record id="demo_lab_order_1_line_2" model="sale.order.line">
<field name="order_id" ref="demo_lab_order_1"/>
<field name="product_id" ref="lims_management.analysis_hemoglobina_glicosilada" eval="obj().product_variant_id.id"/>
<field name="product_uom_qty">1</field>
</record>
<!-- Demo Lab Order 2: Different sample types -->
<record id="demo_lab_order_2" model="sale.order">
<field name="partner_id" ref="lims_management.demo_patient_2"/>
<field name="doctor_id" ref="lims_management.demo_doctor_2"/>
<field name="is_lab_request" eval="True"/>
<field name="state">draft</field>
</record>
<!-- Order lines - Different sample types -->
<record id="demo_lab_order_2_line_1" model="sale.order.line">
<field name="order_id" ref="demo_lab_order_2"/>
<field name="product_id" ref="lims_management.analysis_glucosa" eval="obj().product_variant_id.id"/>
<field name="product_uom_qty">1</field>
</record>
<record id="demo_lab_order_2_line_2" model="sale.order.line">
<field name="order_id" ref="demo_lab_order_2"/>
<field name="product_id" ref="lims_management.analysis_perfil_lipidico" eval="obj().product_variant_id.id"/>
<field name="product_uom_qty">1</field>
</record>
<record id="demo_lab_order_2_line_3" model="sale.order.line">
<field name="order_id" ref="demo_lab_order_2"/>
<field name="product_id" ref="lims_management.analysis_urocultivo" eval="obj().product_variant_id.id"/>
<field name="product_uom_qty">1</field>
</record>
<!-- Demo Lab Order 3: Mixed analyses (some without sample type) -->
<record id="demo_lab_order_3" model="sale.order">
<field name="partner_id" ref="lims_management.demo_patient_3"/>
<field name="is_lab_request" eval="True"/>
<field name="state">draft</field>
</record>
<record id="demo_lab_order_3_line_1" model="sale.order.line">
<field name="order_id" ref="demo_lab_order_3"/>
<field name="product_id" ref="lims_management.analysis_tsh" eval="obj().product_variant_id.id"/>
<field name="product_uom_qty">1</field>
</record>
<record id="demo_lab_order_3_line_2" model="sale.order.line">
<field name="order_id" ref="demo_lab_order_3"/>
<field name="product_id" ref="lims_management.analysis_t4_libre" eval="obj().product_variant_id.id"/>
<field name="product_uom_qty">1</field>
</record>
<!-- Demo Lab Order 4: Pediatric order -->
<record id="demo_lab_order_4" model="sale.order">
<field name="partner_id" ref="lims_management.demo_patient_minor_1"/>
<field name="doctor_id" ref="lims_management.demo_doctor_1"/>
<field name="is_lab_request" eval="True"/>
<field name="state">draft</field>
</record>
<record id="demo_lab_order_4_line_1" model="sale.order.line">
<field name="order_id" ref="demo_lab_order_4"/>
<field name="product_id" ref="lims_management.analysis_hemograma" eval="obj().product_variant_id.id"/>
<field name="product_uom_qty">1</field>
</record>
<record id="demo_lab_order_4_line_2" model="sale.order.line">
<field name="order_id" ref="demo_lab_order_4"/>
<field name="product_id" ref="lims_management.analysis_parasitos_heces" eval="obj().product_variant_id.id"/>
<field name="product_uom_qty">1</field>
</record>
</odoo> </odoo>

View File

@ -29,8 +29,8 @@ class ProductTemplate(models.Model):
) )
is_sample_type = fields.Boolean( is_sample_type = fields.Boolean(
string="Is a Sample Type", string="Es Tipo de Muestra",
help="Check if this product represents a type of laboratory sample container." help="Marcar si este producto representa un tipo de contenedor de muestra de laboratorio."
) )
required_sample_type_id = fields.Many2one( required_sample_type_id = fields.Many2one(

View File

@ -9,17 +9,17 @@ class SaleOrder(models.Model):
_inherit = 'sale.order' _inherit = 'sale.order'
is_lab_request = fields.Boolean( is_lab_request = fields.Boolean(
string="Is a Laboratory Request", string="Es Orden de Laboratorio",
default=False, default=False,
copy=False, copy=False,
help="Technical field to identify if the sale order is a laboratory request." help="Campo técnico para identificar si la orden de venta es una solicitud de laboratorio."
) )
doctor_id = fields.Many2one( doctor_id = fields.Many2one(
'res.partner', 'res.partner',
string="Referring Doctor", string="Médico Referente",
domain="[('is_doctor', '=', True)]", domain="[('is_doctor', '=', True)]",
help="The doctor who referred the patient for this laboratory request." help="El médico que refirió al paciente para esta solicitud de laboratorio."
) )
generated_sample_ids = fields.Many2many( generated_sample_ids = fields.Many2many(
@ -30,7 +30,7 @@ class SaleOrder(models.Model):
string='Muestras Generadas', string='Muestras Generadas',
domain="[('is_lab_sample', '=', True)]", domain="[('is_lab_sample', '=', True)]",
readonly=True, readonly=True,
help="Laboratory samples automatically generated when this order was confirmed" help="Muestras de laboratorio generadas automáticamente cuando se confirmó esta orden"
) )
def action_confirm(self): def action_confirm(self):

View File

@ -6,7 +6,7 @@ import random
class StockLot(models.Model): class StockLot(models.Model):
_inherit = 'stock.lot' _inherit = 'stock.lot'
is_lab_sample = fields.Boolean(string='Is a Laboratory Sample') is_lab_sample = fields.Boolean(string='Es Muestra de Laboratorio')
barcode = fields.Char( barcode = fields.Char(
string='Código de Barras', string='Código de Barras',
@ -18,25 +18,25 @@ class StockLot(models.Model):
patient_id = fields.Many2one( patient_id = fields.Many2one(
'res.partner', 'res.partner',
string='Patient', string='Paciente',
domain="[('is_patient', '=', True)]" domain="[('is_patient', '=', True)]"
) )
request_id = fields.Many2one( request_id = fields.Many2one(
'sale.order', 'sale.order',
string='Lab Request', string='Orden de Laboratorio',
domain="[('is_lab_request', '=', True)]" domain="[('is_lab_request', '=', True)]"
) )
collection_date = fields.Datetime(string='Collection Date') collection_date = fields.Datetime(string='Fecha de Recolección')
container_type = fields.Selection([ container_type = fields.Selection([
('serum_tube', 'Serum Tube'), ('serum_tube', 'Tubo de Suero'),
('edta_tube', 'EDTA Tube'), ('edta_tube', 'Tubo EDTA'),
('swab', 'Swab'), ('swab', 'Hisopo'),
('urine', 'Urine Container'), ('urine', 'Contenedor de Orina'),
('other', 'Other') ('other', 'Otro')
], string='Container Type (Legacy)', help='Deprecated field, use sample_type_product_id instead') ], string='Tipo de Contenedor (Obsoleto)', help='Campo obsoleto, use sample_type_product_id en su lugar')
sample_type_product_id = fields.Many2one( sample_type_product_id = fields.Many2one(
'product.template', 'product.template',
@ -47,7 +47,7 @@ class StockLot(models.Model):
collector_id = fields.Many2one( collector_id = fields.Many2one(
'res.users', 'res.users',
string='Collected by', string='Recolectado por',
default=lambda self: self.env.user default=lambda self: self.env.user
) )
@ -83,19 +83,28 @@ class StockLot(models.Model):
('disposed', 'Desechada') ('disposed', 'Desechada')
], string='Estado', default='collected', tracking=True) ], string='Estado', default='collected', tracking=True)
def action_collect(self):
"""Mark sample as collected"""
self.write({'state': 'collected', 'collection_date': fields.Datetime.now()})
def action_receive(self): def action_receive(self):
"""Mark sample as received in laboratory"""
self.write({'state': 'received'}) self.write({'state': 'received'})
def action_start_analysis(self): def action_start_analysis(self):
"""Start analysis process"""
self.write({'state': 'in_process'}) self.write({'state': 'in_process'})
def action_complete_analysis(self): def action_complete_analysis(self):
"""Mark analysis as completed"""
self.write({'state': 'analyzed'}) self.write({'state': 'analyzed'})
def action_store(self): def action_store(self):
"""Store the sample"""
self.write({'state': 'stored'}) self.write({'state': 'stored'})
def action_dispose(self): def action_dispose(self):
"""Dispose of the sample"""
self.write({'state': 'disposed'}) self.write({'state': 'disposed'})
@api.onchange('sample_type_product_id') @api.onchange('sample_type_product_id')

View File

@ -30,7 +30,7 @@
<field name="volume_ml" string="Volumen (ml)"/> <field name="volume_ml" string="Volumen (ml)"/>
<field name="analysis_names" string="Análisis"/> <field name="analysis_names" string="Análisis"/>
<field name="state" string="Estado"/> <field name="state" string="Estado"/>
<button name="action_receive" string="Recibir" type="object" <button name="action_collect" string="Recolectar" type="object"
class="btn-primary" invisible="state != 'pending_collection'"/> class="btn-primary" invisible="state != 'pending_collection'"/>
</list> </list>
</field> </field>
@ -56,7 +56,7 @@
<field name="doctor_id"/> <field name="doctor_id"/>
</xpath> </xpath>
<xpath expr="//field[@name='state']" position="before"> <xpath expr="//field[@name='state']" position="before">
<field name="is_lab_request" optional="show" string="Lab Request"/> <field name="is_lab_request" optional="show" string="Orden Lab"/>
<field name="generated_sample_ids" widget="many2many_tags" optional="hide" string="Muestras"/> <field name="generated_sample_ids" widget="many2many_tags" optional="hide" string="Muestras"/>
</xpath> </xpath>
</field> </field>

View File

@ -7,15 +7,15 @@
<field name="name">lab.sample.list</field> <field name="name">lab.sample.list</field>
<field name="model">stock.lot</field> <field name="model">stock.lot</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<list string="Lab Samples"> <list string="Muestras de Laboratorio">
<field name="name"/> <field name="name" string="Código"/>
<field name="patient_id"/> <field name="patient_id" string="Paciente"/>
<field name="product_id" string="Sample Type"/> <field name="product_id" string="Tipo de Muestra"/>
<field name="sample_type_product_id"/> <field name="sample_type_product_id" string="Tipo de Muestra"/>
<field name="collection_date"/> <field name="collection_date" string="Fecha de Recolección"/>
<field name="collector_id"/> <field name="collector_id" string="Recolectado por"/>
<field name="container_type" optional="hide"/> <field name="container_type" optional="hide" string="Tipo Contenedor (Obsoleto)"/>
<field name="state" decoration-success="state == 'analyzed'" decoration-info="state == 'in_process'" decoration-muted="state == 'stored' or state == 'disposed'" widget="badge"/> <field name="state" string="Estado" decoration-success="state == 'analyzed'" decoration-info="state == 'in_process'" decoration-muted="state == 'stored' or state == 'disposed'" widget="badge"/>
</list> </list>
</field> </field>
</record> </record>
@ -25,14 +25,15 @@
<field name="name">lab.sample.form</field> <field name="name">lab.sample.form</field>
<field name="model">stock.lot</field> <field name="model">stock.lot</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Lab Sample"> <form string="Muestra de Laboratorio">
<header> <header>
<button name="action_collect" string="Recolectar" type="object" class="oe_highlight" invisible="state != 'pending_collection'"/>
<button name="action_receive" string="Recibir" type="object" class="oe_highlight" invisible="state != 'collected'"/> <button name="action_receive" string="Recibir" type="object" class="oe_highlight" invisible="state != 'collected'"/>
<button name="action_start_analysis" string="Iniciar Análisis" type="object" class="oe_highlight" invisible="state != 'received'"/> <button name="action_start_analysis" string="Iniciar Análisis" type="object" class="oe_highlight" invisible="state != 'received'"/>
<button name="action_complete_analysis" string="Completar Análisis" type="object" class="oe_highlight" invisible="state != 'in_process'"/> <button name="action_complete_analysis" string="Completar Análisis" type="object" class="oe_highlight" invisible="state != 'in_process'"/>
<button name="action_store" string="Almacenar" type="object" invisible="state == 'stored' or state == 'disposed'"/> <button name="action_store" string="Almacenar" type="object" invisible="state not in ['analyzed', 'in_process', 'received']"/>
<button name="action_dispose" string="Desechar" type="object" invisible="state == 'disposed'"/> <button name="action_dispose" string="Desechar" type="object" invisible="state == 'disposed'"/>
<field name="state" widget="statusbar" statusbar_visible="collected,received,in_process,analyzed,stored"/> <field name="state" widget="statusbar" statusbar_visible="pending_collection,collected,received,in_process,analyzed,stored"/>
</header> </header>
<sheet> <sheet>
<div class="oe_title"> <div class="oe_title">
@ -42,24 +43,29 @@
</div> </div>
<group> <group>
<group> <group>
<field name="patient_id" readonly="state != 'collected'"/> <field name="patient_id" readonly="state not in ['pending_collection', 'collected']"/>
<field name="doctor_id" readonly="state not in ['pending_collection', 'collected']"/>
<field name="origin" readonly="1"/>
<field name="request_id" <field name="request_id"
readonly="state != 'collected'" readonly="state not in ['pending_collection', 'collected']"
domain="[('is_lab_request', '=', True), '|', ('partner_id', '=', False), ('partner_id', '=', patient_id)]"/> domain="[('is_lab_request', '=', True), '|', ('partner_id', '=', False), ('partner_id', '=', patient_id)]"/>
<field name="product_id" <field name="product_id"
string="Sample Type" string="Sample Type"
domain="[('is_sample_type', '=', True)]" domain="[('is_sample_type', '=', True)]"
options="{'no_create': True, 'no_create_edit': True}" options="{'no_create': True, 'no_create_edit': True}"
readonly="state != 'collected'"/> readonly="state not in ['pending_collection', 'collected']"/>
</group> </group>
<group> <group>
<field name="collection_date" readonly="state != 'collected'"/> <field name="barcode" readonly="1"/>
<field name="collector_id" readonly="state != 'collected'"/> <field name="collection_date" readonly="state not in ['pending_collection', 'collected']"/>
<field name="collector_id" readonly="state not in ['pending_collection', 'collected']"/>
<field name="sample_type_product_id" <field name="sample_type_product_id"
readonly="state != 'collected'" readonly="state not in ['pending_collection', 'collected']"
options="{'no_create': True, 'no_create_edit': True}"/> options="{'no_create': True, 'no_create_edit': True}"/>
<field name="volume_ml" readonly="1"/>
<field name="analysis_names" readonly="1"/>
<field name="container_type" <field name="container_type"
readonly="state != 'collected'" readonly="state not in ['pending_collection', 'collected']"
invisible="sample_type_product_id != False"/> invisible="sample_type_product_id != False"/>
</group> </group>
</group> </group>

View File

@ -0,0 +1,136 @@
import odoo
def test_sample_generation(cr):
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
print("=== TESTING AUTOMATIC SAMPLE GENERATION ===\n")
# Create test patient
patient = env['res.partner'].create({
'name': 'Test Patient for Validation',
'is_patient': True,
'patient_identifier': 'P-TEST01'
})
print(f"Created patient: {patient.name}")
# Create test doctor
doctor = env['res.partner'].create({
'name': 'Dr. Test Validation',
'is_doctor': True,
'doctor_license': 'L-TEST01'
})
print(f"Created doctor: {doctor.name}")
# Get or create sample types
sample_types = {}
# EDTA Tube
edta = env['product.template'].search([('name', 'like', 'EDTA'), ('is_sample_type', '=', True)], limit=1)
if not edta:
edta = env['product.template'].create({
'name': 'Test EDTA Tube',
'is_sample_type': True,
'type': 'consu'
})
sample_types['edta'] = edta
# Serum Tube
serum = env['product.template'].search([('name', 'like', 'Suero'), ('is_sample_type', '=', True)], limit=1)
if not serum:
serum = env['product.template'].create({
'name': 'Test Serum Tube',
'is_sample_type': True,
'type': 'consu'
})
sample_types['serum'] = serum
# Create test analyses
analyses = []
# Analysis 1 - requires EDTA
analysis1 = env['product.template'].create({
'name': 'Test Hemograma',
'is_analysis': True,
'type': 'service',
'required_sample_type_id': edta.id,
'sample_volume_ml': 3.0
})
analyses.append(analysis1)
print(f"Created analysis: {analysis1.name} (requires {edta.name}, {analysis1.sample_volume_ml} ml)")
# Analysis 2 - also requires EDTA
analysis2 = env['product.template'].create({
'name': 'Test HbA1c',
'is_analysis': True,
'type': 'service',
'required_sample_type_id': edta.id,
'sample_volume_ml': 2.0
})
analyses.append(analysis2)
print(f"Created analysis: {analysis2.name} (requires {edta.name}, {analysis2.sample_volume_ml} ml)")
# Analysis 3 - requires Serum
analysis3 = env['product.template'].create({
'name': 'Test Glucose',
'is_analysis': True,
'type': 'service',
'required_sample_type_id': serum.id,
'sample_volume_ml': 1.0
})
analyses.append(analysis3)
print(f"Created analysis: {analysis3.name} (requires {serum.name}, {analysis3.sample_volume_ml} ml)")
# Analysis 4 - no sample type defined
analysis4 = env['product.template'].create({
'name': 'Test Special Analysis',
'is_analysis': True,
'type': 'service'
})
analyses.append(analysis4)
print(f"Created analysis: {analysis4.name} (no sample type defined)")
# Create lab order
print("\n--- Creating Lab Order ---")
order = env['sale.order'].create({
'partner_id': patient.id,
'doctor_id': doctor.id,
'is_lab_request': True,
'order_line': [(0, 0, {
'product_id': a.product_variant_id.id,
'product_uom_qty': 1
}) for a in analyses]
})
print(f"Created order: {order.name}")
print(f"Order lines: {len(order.order_line)}")
# Confirm order - this should trigger automatic sample generation
print("\n--- Confirming Order (triggering sample generation) ---")
order.action_confirm()
print(f"Order state: {order.state}")
# Check generated samples
print(f"\n--- Generated Samples: {len(order.generated_sample_ids)} ---")
for sample in order.generated_sample_ids:
print(f"\nSample: {sample.name}")
print(f" Barcode: {sample.barcode}")
print(f" Sample Type: {sample.sample_type_product_id.name if sample.sample_type_product_id else 'None'}")
print(f" Total Volume: {sample.volume_ml} ml")
print(f" Analyses: {sample.analysis_names}")
print(f" State: {sample.state}")
# Check messages
print("\n--- Order Messages ---")
messages = order.message_ids.filtered(lambda m: m.body and m.message_type == 'notification')
for msg in messages[:5]:
print(f"Message: {msg.body[:200]}...")
print("\n=== TEST COMPLETED ===")
return order
if __name__ == '__main__':
db_name = 'lims_demo'
registry = odoo.registry(db_name)
with registry.cursor() as cr:
test_sample_generation(cr)
cr.commit()