fase 1 productizacion: capa de configuracion (glosario multi-giro, sucursales y categorias configurables, ajustes de clinica por instancia, colores de marca, horarios, URLs de pago dinamicas) + script de provisioning de instancia nueva con datos demo + frontend consumiendo la config

This commit is contained in:
2026-10-11 05:50:18 +00:00
parent 28118ce732
commit f92018f88e
50 changed files with 4314 additions and 210 deletions

View File

@@ -2,6 +2,8 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.addons.skeen_citas.clinic_settings import get_settings as _clinic_settings
from odoo.addons.skeen_citas.clinic_settings import url_publica as _clinic_url_publica
import requests
import json
@@ -61,7 +63,24 @@ class SkeenPago(models.Model):
if not vals.get('name'):
vals['name'] = self.env['ir.sequence'].next_by_code('skeen.pago') or 'PAGO-001'
return super(SkeenPago, self).create(vals_list)
def _payment_base(self, fallback):
"""URL pública base de la instancia para los enlaces de pago.
Sale de los ajustes de la clínica (url_publica); si la instancia no está
configurada, de `web.base.url` cuando es público y, en último caso, del
valor que ya usaba el código (`fallback`).
"""
return _clinic_url_publica(self.env, fallback=fallback)
def _product_name(self):
"""Nombre del producto en la pasarela: '<clínica> - <servicio>'."""
nombre = (_clinic_settings(self.env).get('nombre') or '').strip() or 'SKEEN Derma Experts'
servicio = ''
if self.cita_id and self.cita_id.servicio_id:
servicio = self.cita_id.servicio_id.name or ''
return '%s - %s' % (nombre, servicio) if servicio else nombre
def action_generate_stripe_link(self):
"""Generar link de pago Stripe"""
self.ensure_one()
@@ -70,6 +89,7 @@ class SkeenPago(models.Model):
raise ValidationError(_('Stripe no está configurado!'))
# Crear sesión de checkout
base = self._payment_base('https://app.skeen.mx')
headers = {
'Authorization': f'Bearer {stripe_key}',
'Content-Type': 'application/x-www-form-urlencoded',
@@ -77,12 +97,12 @@ class SkeenPago(models.Model):
data = {
'payment_method_types[]': 'card',
'line_items[0][price_data][currency]': 'mxn',
'line_items[0][price_data][product_data][name]': f'Servicio SKEEN - {self.cita_id.servicio_id.name}',
'line_items[0][price_data][product_data][name]': self._product_name(),
'line_items[0][price_data][unit_amount]': int(self.amount * 100),
'line_items[0][quantity]': 1,
'mode': 'payment',
'success_url': f'https://app.skeen.mx/pagos/exitoso?ref={self.name}',
'cancel_url': f'https://app.skeen.mx/pagos/cancelado?ref={self.name}',
'success_url': f'{base}/pagos/exitoso?ref={self.name}',
'cancel_url': f'{base}/pagos/cancelado?ref={self.name}',
'metadata[payment_id]': str(self.id),
'metadata[patient_phone]': self.partner_id.phone or '',
}
@@ -111,22 +131,24 @@ class SkeenPago(models.Model):
if not mp_token:
raise ValidationError(_('MercadoPago no está configurado!'))
# Crear preferencia de checkout
base = self._payment_base('https://app.skeen.mx')
headers = {
'Authorization': f'Bearer {mp_token}',
'Content-Type': 'application/json',
}
data = {
'items': [{
'title': f'Servicio SKEEN - {self.cita_id.servicio_id.name}',
'title': self._product_name(),
'quantity': 1,
'unit_price': int(self.amount),
}],
'back_urls': {
'success': f'https://app.skeen.mx/pagos/exitoso?ref={self.name}',
'failure': f'https://app.skeen.mx/pagos/fallido?ref={self.name}',
'pending': f'https://app.skeen.mx/pagos/pendiente?ref={self.name}',
'success': f'{base}/pagos/exitoso?ref={self.name}',
'failure': f'{base}/pagos/fallido?ref={self.name}',
'pending': f'{base}/pagos/pendiente?ref={self.name}',
},
'notification_url': 'https://skeen.mx/webhooks/mercadopago',
'notification_url': self._payment_base('https://skeen.mx') + '/webhooks/mercadopago',
'metadata': {
'payment_id': str(self.id),
'patient_phone': self.partner_id.phone or '',