# -*- coding: utf-8 -*- from odoo import models, fields, api class ResPartner(models.Model): _inherit = 'res.partner' # Campos de paciente is_patient = fields.Boolean(string='Es Paciente', default=False) patient_id = fields.Char(string='ID Paciente', readonly=True, copy=False) legacy_id = fields.Char(string='ID Legacy', help='ID del sistema legacy SKEEN', index=True, copy=False) legacy_ref = fields.Char(string='Referencia Legacy', help='Referencia externa del sistema legacy', index=True, copy=False) # Información médica básica birth_date = fields.Date(string='Fecha de Nacimiento') age = fields.Integer(string='Edad', compute='_compute_age', store=True) gender = fields.Selection([ ('male', 'Masculino'), ('female', 'Femenino'), ('other', 'Otro'), ], string='Género') blood_type = fields.Selection([ ('a+', 'A+'), ('a-', 'A-'), ('b+', 'B+'), ('b-', 'B-'), ('ab+', 'AB+'), ('ab-', 'AB-'), ('o+', 'O+'), ('o-', 'O-'), ], string='Tipo de Sangre') allergies = fields.Text(string='Alergias') medical_history = fields.Text(string='Historial Médico') current_medication = fields.Text(string='Medicación Actual') # Información de contacto preferida preferred_contact = fields.Selection([ ('whatsapp', 'WhatsApp'), ('phone', 'Teléfono'), ('email', 'Email'), ], string='Contacto Preferido', default='whatsapp') # Datos personales extendidos (basado en expediente SKEEN actual) birthplace = fields.Char(string='Lugar de Nacimiento') occupation = fields.Char(string='Empleo / Ocupación') marital_status = fields.Selection([ ('single', 'Soltero/a'), ('married', 'Casado/a'), ('divorced', 'Divorciado/a'), ('widowed', 'Viudo/a'), ('union', 'Unión libre'), ('other', 'Otro'), ], string='Estado Civil') emergency_contact = fields.Char(string='Contacto de Emergencia') emergency_phone = fields.Char(string='Teléfono de Emergencia') home_phone = fields.Char(string='Teléfono Casa') mobile = fields.Char(string='Celular') whatsapp = fields.Char(string='WhatsApp') address_notes = fields.Text(string='Dirección Completa') referred_by = fields.Char(string='Recomendado Por') patient_comments = fields.Text(string='Comentarios del Paciente') internal_notes = fields.Text(string='Notas Internas', help='Notas internas del equipo (no visibles para el paciente)') # Historia clínica — cuestionario SKEEN is_pregnant = fields.Boolean(string='¿Está embarazada?') is_breastfeeding = fields.Boolean(string='¿Está lactando?') uses_contraceptives = fields.Boolean(string='¿Usa anticonceptivos?') children_count = fields.Integer(string='Número de Hijos', default=0) # Historia clínica — antecedentes patológicos kidney_problems = fields.Boolean(string='Problemas de riñón') back_pain = fields.Boolean(string='Dolor de espalda') heart_disease = fields.Boolean(string='Enfermedades cardíacas') respiratory_problems = fields.Boolean(string='Problemas respiratorios') blood_pressure = fields.Boolean(string='Presión arterial') diabetes = fields.Boolean(string='Diabetes') thyroid = fields.Boolean(string='Problemas de tiroides') colitis = fields.Boolean(string='Colitis') constipation = fields.Boolean(string='Estreñimiento') liver_problems = fields.Boolean(string='Problemas de hígado') surgeries = fields.Boolean(string='Cirugías') surgeries_notes = fields.Text(string='Detalle de cirugías') varicose_veins = fields.Boolean(string='Varices') migraine = fields.Boolean(string='Migraña') faints_with_needles = fields.Boolean(string='¿Se desmaya con agujas?') medical_notes = fields.Text(string='Notas Médicas Adicionales') # Campos para sincronización WACRM wacrm_contact_id = fields.Char(string='WACRM Contact ID') wacrm_synced = fields.Boolean(string='Sincronizado con WACRM', default=False) # Médico principal y etiquetas primary_doctor_id = fields.Many2one('hr.employee', string='Médico Principal') tag_ids = fields.Many2many('skeen.patient.tag', string='Etiquetas') is_vip = fields.Boolean(string='VIP', default=False) # Estadísticas last_visit = fields.Date(string='Última Visita') total_visits = fields.Integer(string='Total Visitas', default=0) total_spent = fields.Float(string='Total Gastado', default=0.0) # Adjuntos (expediente escaneado y galería de imágenes) adjunto_ids = fields.One2many('skeen.patient.adjunto', 'partner_id', string='Documentos') # Completitud de expediente expediente_completion = fields.Integer(string='Completitud Expediente', compute='_compute_expediente_completion', store=True) expediente_missing = fields.Text(string='Datos Faltantes Expediente', compute='_compute_expediente_completion', store=True) # Fuente de captación source = fields.Selection([ ('whatsapp', 'WhatsApp'), ('facebook', 'Facebook'), ('instagram', 'Instagram'), ('google', 'Google'), ('referral', 'Referido'), ('walkin', 'Presencial'), ('other', 'Otro'), ], string='Fuente', default='whatsapp') @api.depends('birth_date') def _compute_age(self): for rec in self: if rec.birth_date: today = fields.Date.today() rec.age = today.year - rec.birth_date.year - ( (today.month, today.day) < (rec.birth_date.month, rec.birth_date.day) ) else: rec.age = 0 CLINICAL_BOOL_FIELDS = [ 'is_pregnant', 'is_breastfeeding', 'uses_contraceptives', 'kidney_problems', 'back_pain', 'heart_disease', 'respiratory_problems', 'blood_pressure', 'diabetes', 'thyroid', 'colitis', 'constipation', 'liver_problems', 'surgeries', 'varicose_veins', 'migraine', 'faints_with_needles', ] @api.depends('email', 'birth_date', 'gender', 'mobile', 'home_phone', 'address_notes', 'emergency_contact', 'emergency_phone', 'occupation', 'marital_status', 'blood_type', 'allergies', 'current_medication', 'medical_history', 'medical_notes', 'is_pregnant', 'is_breastfeeding', 'uses_contraceptives', 'kidney_problems', 'back_pain', 'heart_disease', 'respiratory_problems', 'blood_pressure', 'diabetes', 'thyroid', 'colitis', 'constipation', 'liver_problems', 'surgeries', 'varicose_veins', 'migraine', 'faints_with_needles') def _compute_expediente_completion(self): for rec in self: checks = [ ('Email', bool(rec.email)), ('Fecha de nacimiento', bool(rec.birth_date)), ('Género', bool(rec.gender)), ('Teléfono', bool(rec.mobile or rec.home_phone)), ('Dirección', bool(rec.address_notes and rec.address_notes.strip())), ('Contacto de emergencia', bool( rec.emergency_contact and rec.emergency_contact.strip() and rec.emergency_phone and rec.emergency_phone.strip())), ('Ocupación', bool(rec.occupation and rec.occupation.strip())), ('Estado civil', bool(rec.marital_status)), ('Tipo de sangre', bool(rec.blood_type)), ('Alergias', bool(rec.allergies and rec.allergies.strip())), ('Medicación actual', bool(rec.current_medication and rec.current_medication.strip())), ('Cuestionario clínico', bool( any(rec[fname] for fname in self.CLINICAL_BOOL_FIELDS) or (rec.medical_history and rec.medical_history.strip()) or (rec.medical_notes and rec.medical_notes.strip()))), ] puntos = sum(1 for _, ok in checks if ok) rec.expediente_completion = round(puntos * 100 / len(checks)) rec.expediente_missing = '|'.join(label for label, ok in checks if not ok) @api.model_create_multi def create(self, vals_list): for vals in vals_list: if vals.get('is_patient') and not vals.get('patient_id'): vals['patient_id'] = self.env['ir.sequence'].next_by_code('skeen.patient') or 'PAT-001' return super(ResPartner, self).create(vals_list) def action_view_appointments(self): self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': 'Citas', 'res_model': 'skeen.cita', 'domain': [('partner_id', '=', self.id)], 'view_mode': 'tree,form', } class SkeenPatientTag(models.Model): _name = 'skeen.patient.tag' _description = 'Etiqueta de Paciente' name = fields.Char(string='Nombre', required=True) color = fields.Integer(string='Color') patient_ids = fields.Many2many('res.partner', string='Pacientes')