Merge pull request 'feature/4-patient-doctor-management' (#24) from feature/4-patient-doctor-management into dev

Reviewed-on: luis_portillo/clinical_laboratory#24
This commit is contained in:
luis_portillo 2025-07-14 04:53:50 +00:00
commit 6a83cc5173
10 changed files with 267 additions and 29 deletions

BIN
.gitignore vendored Normal file

Binary file not shown.

View File

@ -21,10 +21,13 @@ tea issue create --title "Título del Issue" --description "Descripción detalla
Para agregar un comentario a un issue existente, se utiliza el comando `comment` seguido del número del issue y el texto del comentario entre comillas.
**Formato correcto:**
```bash
tea comment <NÚMERO_ISSUE> "Tu comentario aquí"
```
**Ejemplo:**
```bash
tea comment 3 "Comentario de prueba"
```
@ -37,22 +40,45 @@ tea comment 3 "Comentario de prueba"
Debido a problemas de interpretación de comillas en el shell de ejecución, el uso de `git commit -m "mensaje"` puede fallar. Para evitar estos problemas, se debe pasar el mensaje del commit a través de la entrada estándar (`stdin`).
### Política de Mensajes de Commit
**Es mandatorio que el título de cada commit referencie el número del issue que resuelve.** Esto se hace para mantener una trazabilidad clara entre el código y las tareas.
**Formato del Título:**
```
<tipo>(#<issue_id>): <descripción breve>
```
- **`<tipo>`:** `feat` (nueva funcionalidad), `fix` (corrección de bug), `docs` (cambios en documentación), `style` (formato), `refactor`, `test`, `chore` (otras tareas).
- **`(<issue_id>)`:** El número del issue entre paréntesis y precedido de `#`.
**Ejemplo:**
```
feat(#4): Agregar campos de género y fecha de nacimiento al paciente
```
### Método Recomendado
Utiliza el comando `echo` y una tubería (`|`) para enviar el mensaje a `git commit -F -`.
**Commit de una sola línea:**
```bash
echo "feat(scope): Tu mensaje de commit conciso" | git commit -F -
echo "feat(#4): Tu mensaje de commit conciso" | git commit -F -
```
**Commit multilínea:**
Para mensajes de commit multilínea, la forma más segura es usar `printf` que maneja mejor los saltos de línea (`\n`):
Para mensajes de commit multilínea, la forma más segura es usar `printf` que maneja mejor los saltos de línea (`
`):
```bash
printf "feat(scope): Título del commit\n\nCuerpo del mensaje con una descripción más detallada.\n\n- Un punto importante.\n- Otro punto importante.\n\nResolves: #123" | git commit -F -
printf "feat(#4): Título del commit
Cuerpo del mensaje con descripción detallada." | git commit -F -
```
Esto asegura que el formato del mensaje del commit se preserve correctamente.
---
## Crear un Pull Request
@ -60,11 +86,13 @@ Esto asegura que el formato del mensaje del commit se preserve correctamente.
Para crear un pull request (PR), se utiliza el comando `tea pulls create`. Debes especificar la rama base (hacia donde van los cambios) y la rama `head` (tu rama actual), junto con un título que referencie el issue que resuelve.
**Formato del comando:**
```bash
tea pulls create --base "<rama_base>" --head "<tu_rama>" --title "<Tipo>(#issue): Título descriptivo"
```
**Ejemplo:**
```bash
tea pulls create --base "dev" --head "feature/3-core-setup" --title "feat(#3): Actualiza instrucciones en GEMINI.md"
```
@ -86,14 +114,29 @@ Al iniciar cada sesión de trabajo, es **mandatorio** leer los siguientes docume
Para levantar la instancia efímera de Odoo 18 junto con la base de datos de PostgreSQL, se utiliza Docker Compose.
**Comando:**
**Comando de inicio:**
```bash
docker-compose up -d
```
Este comando levantará los servicios definidos en el archivo `docker-compose.yml` en modo "detached" (`-d`).
Este comando levantará los servicios definidos en el archivo `docker-compose.yml` en modo "detached" (`-d`), lo que significa que se ejecutarán en segundo plano.
Para detener los servicios, utiliza:
**Comando de detención y limpieza:**
Para detener los servicios y asegurar un estado limpio, **siempre se deben eliminar los volúmenes**, a menos que se indique lo contrario.
```bash
docker-compose down
```
docker-compose down -v
```
### Verificación de la Inicialización
Después de levantar la instancia, es **mandatorio** verificar los registros del contenedor de inicialización para confirmar que los módulos se instalaron o actualizaron correctamente.
**Comando para ver los logs:**
```bash
docker-compose logs odoo_init
```
Busca errores en la salida. Si encuentras alguno, debes presentar un resumen del problema y sus posibles causas, como:
- **Dependencias faltantes:** Un módulo no se puede instalar porque requiere otro que no está presente.
- **Errores de sintaxis:** Problemas en archivos Python (`.py`) o XML (`.views`, `.xml`).
- **Permisos incorrectos:** Problemas de acceso a archivos o directorios.
- **Datos incorrectos:** Errores en los archivos de datos de demostración o iniciales.

View File

@ -16,13 +16,19 @@
'website': "https://gitea.grupoconsiti.com/luis_portillo/clinical_laboratory",
'category': 'Industries',
'version': '18.0.1.0.0',
'depends': ['base', 'sale_management', 'stock', 'account'],
'depends': ['base'],
'data': [
'security/lims_security.xml',
'security/ir.model.access.csv',
'data/ir_sequence.xml',
'views/partner_views.xml',
'views/menus.xml',
],
'demo': [
'data/lims_demo.xml',
],
'installable': True,
'application': True,
'auto_install': False,
'license': 'LGPL-3',
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Secuencia para el Identificador de Paciente -->
<record id="seq_patient_identifier" model="ir.sequence">
<field name="name">Patient Identifier Sequence</field>
<field name="code">res.partner.patient_identifier</field>
<field name="prefix">P</field>
<field name="padding">6</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Datos de Demostración para Pacientes -->
<record id="demo_patient_1" model="res.partner">
<field name="name">Ana Torres</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-A87B01</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1985-05-15</field>
<field name="gender">female</field>
<field name="phone">+1-202-555-0174</field>
<field name="email">ana.torres@example.com</field>
</record>
<record id="demo_patient_2" model="res.partner">
<field name="name">Carlos Ruiz</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-C45D02</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date">1992-11-20</field>
<field name="gender">male</field>
<field name="phone">+1-202-555-0192</field>
<field name="email">carlos.ruiz@example.com</field>
</record>
<!-- Datos de Demostración para Médicos -->
<record id="demo_doctor_1" model="res.partner">
<field name="name">Dr. Luis Herrera</field>
<field name="is_doctor" eval="True"/>
<field name="doctor_license">L-98765</field>
<field name="phone">+1-202-555-0145</field>
<field name="email">luis.herrera@hospital.com</field>
</record>
<record id="demo_doctor_2" model="res.partner">
<field name="name">Dra. Sofia Vargas</field>
<field name="is_doctor" eval="True"/>
<field name="doctor_license">L-54321</field>
<field name="phone">+1-202-555-0133</field>
<field name="email">sofia.vargas@clinic.com</field>
</record>
<!-- Datos de Demostración para Tutor y Paciente Menor de Edad -->
<record id="demo_tutor_1" model="res.partner">
<field name="name">Laura Mendoza</field>
<field name="phone">+1-202-555-0188</field>
<field name="email">laura.mendoza@example.com</field>
</record>
<record id="demo_patient_minor_1" model="res.partner">
<field name="name">Pedro Infante Jr.</field>
<field name="is_patient" eval="True"/>
<field name="patient_identifier">P-M12E03</field>
<field name="origin">Carga Inicial</field>
<field name="birthdate_date" eval="(datetime.now() - relativedelta(years=5)).strftime('%Y-%m-%d')"/>
<field name="gender">male</field>
<field name="parent_id" ref="demo_tutor_1"/>
</record>
</data>
</odoo>

View File

@ -1 +1,2 @@
# -*- coding: utf-8 -*-
from . import partner

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class ResPartner(models.Model):
_inherit = 'res.partner'
is_patient = fields.Boolean(string="Es Paciente")
patient_identifier = fields.Char(string="Identificador de Paciente", copy=False)
origin = fields.Char(
string="Origen",
default='Manual',
help="Este campo indica el origen del registro del paciente (ej. Manual, Carga Inicial)."
)
birthdate_date = fields.Date(string="Fecha de Nacimiento")
gender = fields.Selection([
('male', 'Masculino'),
('female', 'Femenino'),
('other', 'Otro')
], string="Género")
is_doctor = fields.Boolean(string="Es Médico")
doctor_license = fields.Char(string="Licencia Médica", copy=False)
_sql_constraints = [
('patient_identifier_unique', 'unique(patient_identifier)', 'El identificador del paciente debe ser único.'),
('doctor_license_unique', 'unique(doctor_license)', 'La licencia médica debe ser única.')
]
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('is_patient') and not vals.get('patient_identifier'):
vals['patient_identifier'] = self.env['ir.sequence'].next_by_code('res.partner.patient_identifier')
return super(ResPartner, self).create(vals_list)

View File

@ -7,34 +7,50 @@
name="Laboratorio"
sequence="10"/>
<!-- Submenú de Órdenes de Laboratorio -->
<menuitem
id="lims_menu_orders"
name="Órdenes de Laboratorio"
parent="lims_menu_root"
sequence="10"/>
<!-- Acción de Ventana para Pacientes -->
<record id="action_lims_patient" model="ir.actions.act_window">
<field name="name">Pacientes</field>
<field name="res_model">res.partner</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="lims_management.view_patient_tree"/>
<field name="domain">[('is_patient', '=', True)]</field>
<field name="context">{'default_is_patient': True}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Crea un nuevo paciente
</p>
</field>
</record>
<!-- Submenú de Pacientes -->
<menuitem
id="lims_menu_patients"
name="Pacientes"
parent="lims_menu_root"
action="base.action_partner_customer_form"
action="action_lims_patient"
sequence="20"/>
<!-- Submenú de Configuración -->
<!-- Acción de Ventana para Doctores -->
<record id="action_lims_doctor" model="ir.actions.act_window">
<field name="name">Doctores</field>
<field name="res_model">res.partner</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="lims_management.view_doctor_tree"/>
<field name="domain">[('is_doctor', '=', True)]</field>
<field name="context">{'default_is_doctor': True}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Crea un nuevo doctor
</p>
</field>
</record>
<!-- Submenú de Doctores -->
<menuitem
id="lims_menu_config"
name="Configuración"
id="lims_menu_doctors"
name="Doctores"
parent="lims_menu_root"
sequence="100"/>
<!-- Submenú de Catálogo de Pruebas -->
<menuitem
id="lims_menu_tests_catalog"
name="Catálogo de Pruebas"
parent="lims_menu_config"
sequence="10"/>
action="action_lims_doctor"
sequence="30"/>
</data>
</odoo>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Vista de árbol para Pacientes -->
<record id="view_patient_tree" model="ir.ui.view">
<field name="name">res.partner.tree.patient</field>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<list string="Pacientes">
<field name="patient_identifier"/>
<field name="name"/>
<field name="gender"/>
<field name="birthdate_date"/>
</list>
</field>
</record>
<!-- Vista de lista para Doctores -->
<record id="view_doctor_tree" model="ir.ui.view">
<field name="name">res.partner.tree.doctor</field>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<list string="Doctores">
<field name="name"/>
<field name="doctor_license"/>
<field name="phone"/>
<field name="email"/>
</list>
</field>
</record>
<!-- Hereda la vista de formulario para añadir una pestaña de "Información Clínica" -->
<record id="view_partner_form_lims" model="ir.ui.view">
<field name="name">res.partner.form.lims</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='internal_notes']" position="before">
<page string="Información Clínica" name="clinical_info">
<group>
<group>
<field name="is_patient"/>
<field name="patient_identifier" invisible="not is_patient" readonly="patient_identifier"/>
<field name="origin" readonly="id" invisible="not is_patient"/>
<field name="birthdate_date" invisible="not is_patient"/>
<field name="gender" invisible="not is_patient"/>
</group>
<group>
<field name="is_doctor"/>
<field name="doctor_license" invisible="not is_doctor"/>
</group>
</group>
<group string="Relación Tutor/Paciente" name="tutor_info">
<field name="parent_id" string="Tutor / Responsable"/>
<field name="child_ids" string="Pacientes a Cargo"/>
</group>
</page>
</xpath>
</field>
</record>
</data>
</odoo>