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. 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:** **Formato correcto:**
```bash ```bash
tea comment <NÚMERO_ISSUE> "Tu comentario aquí" tea comment <NÚMERO_ISSUE> "Tu comentario aquí"
``` ```
**Ejemplo:** **Ejemplo:**
```bash ```bash
tea comment 3 "Comentario de prueba" 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`). 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 ### Método Recomendado
Utiliza el comando `echo` y una tubería (`|`) para enviar el mensaje a `git commit -F -`. Utiliza el comando `echo` y una tubería (`|`) para enviar el mensaje a `git commit -F -`.
**Commit de una sola línea:** **Commit de una sola línea:**
```bash ```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:** **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 ```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. Esto asegura que el formato del mensaje del commit se preserve correctamente.
--- ---
## Crear un Pull Request ## 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. 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:** **Formato del comando:**
```bash ```bash
tea pulls create --base "<rama_base>" --head "<tu_rama>" --title "<Tipo>(#issue): Título descriptivo" tea pulls create --base "<rama_base>" --head "<tu_rama>" --title "<Tipo>(#issue): Título descriptivo"
``` ```
**Ejemplo:** **Ejemplo:**
```bash ```bash
tea pulls create --base "dev" --head "feature/3-core-setup" --title "feat(#3): Actualiza instrucciones en GEMINI.md" 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. 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 ```bash
docker-compose up -d 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. **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.
Para detener los servicios, utiliza:
```bash ```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", 'website': "https://gitea.grupoconsiti.com/luis_portillo/clinical_laboratory",
'category': 'Industries', 'category': 'Industries',
'version': '18.0.1.0.0', 'version': '18.0.1.0.0',
'depends': ['base', 'sale_management', 'stock', 'account'], 'depends': ['base'],
'data': [ 'data': [
'security/lims_security.xml', 'security/lims_security.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/ir_sequence.xml',
'views/partner_views.xml',
'views/menus.xml', 'views/menus.xml',
], ],
'demo': [
'data/lims_demo.xml',
],
'installable': True, 'installable': True,
'application': True, 'application': True,
'auto_install': False, '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 -*- # -*- 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" name="Laboratorio"
sequence="10"/> sequence="10"/>
<!-- Submenú de Órdenes de Laboratorio --> <!-- Acción de Ventana para Pacientes -->
<menuitem <record id="action_lims_patient" model="ir.actions.act_window">
id="lims_menu_orders" <field name="name">Pacientes</field>
name="Órdenes de Laboratorio" <field name="res_model">res.partner</field>
parent="lims_menu_root" <field name="view_mode">list,form</field>
sequence="10"/> <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 --> <!-- Submenú de Pacientes -->
<menuitem <menuitem
id="lims_menu_patients" id="lims_menu_patients"
name="Pacientes" name="Pacientes"
parent="lims_menu_root" parent="lims_menu_root"
action="base.action_partner_customer_form" action="action_lims_patient"
sequence="20"/> 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 <menuitem
id="lims_menu_config" id="lims_menu_doctors"
name="Configuración" name="Doctores"
parent="lims_menu_root" parent="lims_menu_root"
sequence="100"/> action="action_lims_doctor"
sequence="30"/>
<!-- Submenú de Catálogo de Pruebas -->
<menuitem
id="lims_menu_tests_catalog"
name="Catálogo de Pruebas"
parent="lims_menu_config"
sequence="10"/>
</data> </data>
</odoo> </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>