feat: Funcion de sistema tenants
This commit is contained in:
7
scripts/check_relations.py
Normal file
7
scripts/check_relations.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from app.models.ticket import Ticket
|
||||
from app.models import relationships # ensure relationships are loaded
|
||||
from sqlalchemy import inspect
|
||||
|
||||
mapper = inspect(Ticket)
|
||||
print("Relationships:", [r.key for r in mapper.relationships])
|
||||
print("Columns:", [c.key for c in mapper.columns])
|
||||
40
scripts/check_sla.py
Normal file
40
scripts/check_sla.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""Check SLA state of tickets and categories"""
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, '/app')
|
||||
os.chdir('/app')
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy import text
|
||||
|
||||
|
||||
async def run():
|
||||
database_url = os.environ.get('DATABASE_URL', 'postgresql+asyncpg://postgres:postgres@db:5432/servicemanager')
|
||||
engine = create_async_engine(database_url)
|
||||
|
||||
async with engine.connect() as c:
|
||||
print("=== CATEGORIES SLA HOURS ===")
|
||||
r = await c.execute(text(
|
||||
"SELECT name, sla_response_hours, sla_resolution_hours "
|
||||
"FROM ticket_categories "
|
||||
"ORDER BY name"
|
||||
))
|
||||
for row in r.fetchall():
|
||||
print(f" {row[0]}: response={row[1]}h, resolution={row[2]}h")
|
||||
|
||||
print("\n=== TICKETS SLA DATES ===")
|
||||
r2 = await c.execute(text(
|
||||
"SELECT ticket_number, category_id, sla_response_due, sla_resolution_due "
|
||||
"FROM tickets "
|
||||
"ORDER BY created_at "
|
||||
"LIMIT 10"
|
||||
))
|
||||
for row in r2.fetchall():
|
||||
print(f" {row[0]}: cat={str(row[1])[:8] if row[1] else 'None'}, sla_resp={row[2]}, sla_res={row[3]}")
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
asyncio.run(run())
|
||||
41
scripts/debug_category.py
Normal file
41
scripts/debug_category.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""Debug: check if ticket's category_id maps to a valid category and what tenant it belongs to"""
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, '/app')
|
||||
os.chdir('/app')
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy import text
|
||||
|
||||
|
||||
async def run():
|
||||
database_url = os.environ.get('DATABASE_URL', 'postgresql+asyncpg://postgres:postgres@db:5432/servicemanager')
|
||||
engine = create_async_engine(database_url)
|
||||
|
||||
async with engine.connect() as c:
|
||||
# Check tickets and their category names via direct JOIN
|
||||
r = await c.execute(text("""
|
||||
SELECT t.ticket_number, t.category_id,
|
||||
cat.name as category_name, cat.tenant_id as cat_tenant,
|
||||
t.tenant_id as ticket_tenant
|
||||
FROM tickets t
|
||||
LEFT JOIN ticket_categories cat ON cat.id = t.category_id
|
||||
WHERE t.category_id IS NOT NULL
|
||||
LIMIT 10
|
||||
"""))
|
||||
print("=== TICKET -> CATEGORY JOIN ===")
|
||||
for row in r.fetchall():
|
||||
match = "✓ SAME TENANT" if row[3] == row[4] else "✗ DIFFERENT TENANT"
|
||||
print(f" {row[0]}: cat_id={str(row[1])[:8]}, cat_name={row[2]}, {match}")
|
||||
|
||||
# Check what tenant aduanasoft-demo is
|
||||
r2 = await c.execute(text("SELECT id, slug FROM tenants WHERE slug='aduanasoft-demo'"))
|
||||
tenant = r2.fetchone()
|
||||
print(f"\nTenant aduanasoft-demo: {tenant[0] if tenant else 'NOT FOUND'}")
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
asyncio.run(run())
|
||||
49
scripts/debug_orm.py
Normal file
49
scripts/debug_orm.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""Debug: check SQLAlchemy ORM category loading"""
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, '/app')
|
||||
os.chdir('/app')
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker, selectinload
|
||||
from sqlalchemy import select
|
||||
from app.models.ticket import Ticket
|
||||
from app.models.category import Category
|
||||
|
||||
|
||||
async def run():
|
||||
database_url = os.environ.get('DATABASE_URL', 'postgresql+asyncpg://postgres:postgres@db:5432/servicemanager')
|
||||
engine = create_async_engine(database_url, echo=True)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as session:
|
||||
# Test selectinload
|
||||
result = await session.execute(
|
||||
select(Ticket)
|
||||
.options(selectinload(Ticket.category))
|
||||
.where(Ticket.category_id != None)
|
||||
.limit(3)
|
||||
)
|
||||
tickets = result.scalars().all()
|
||||
|
||||
print(f"\n=== ORM RESULTS ({len(tickets)} tickets) ===")
|
||||
for t in tickets:
|
||||
print(f" {t.ticket_number}: category_id={t.category_id}, category={t.category}")
|
||||
if t.category:
|
||||
print(f" -> category.name={t.category.name}")
|
||||
else:
|
||||
print(f" -> category is None!")
|
||||
|
||||
# Check if Category model can be queried directly
|
||||
r2 = await session.execute(select(Category).limit(3))
|
||||
cats = r2.scalars().all()
|
||||
print(f"\n=== DIRECT CATEGORY QUERY ({len(cats)} categories) ===")
|
||||
for c in cats:
|
||||
print(f" id={c.id}, name={c.name}")
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
asyncio.run(run())
|
||||
82
scripts/fix_sla_dates.py
Normal file
82
scripts/fix_sla_dates.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
Backfill SLA response_due and resolution_due on all tickets that have
|
||||
a category but no SLA dates set.
|
||||
"""
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from datetime import timedelta
|
||||
|
||||
sys.path.insert(0, '/app')
|
||||
os.chdir('/app')
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import text
|
||||
|
||||
|
||||
async def run():
|
||||
database_url = os.environ.get(
|
||||
'DATABASE_URL',
|
||||
'postgresql+asyncpg://postgres:postgres@db:5432/servicemanager'
|
||||
)
|
||||
engine = create_async_engine(database_url)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as session:
|
||||
# Find all tickets with a category but missing SLA dates
|
||||
result = await session.execute(text("""
|
||||
SELECT
|
||||
t.id,
|
||||
t.created_at,
|
||||
c.sla_response_hours,
|
||||
c.sla_resolution_hours
|
||||
FROM tickets t
|
||||
JOIN ticket_categories c ON c.id = t.category_id
|
||||
WHERE t.sla_response_due IS NULL
|
||||
AND t.sla_resolution_due IS NULL
|
||||
"""))
|
||||
rows = result.fetchall()
|
||||
print(f"Tickets to update: {len(rows)}")
|
||||
|
||||
updated = 0
|
||||
for row in rows:
|
||||
ticket_id, created_at, resp_h, res_h = row
|
||||
# Ensure naive datetime for DB compatibility
|
||||
if hasattr(created_at, 'tzinfo') and created_at.tzinfo is not None:
|
||||
from datetime import timezone
|
||||
created_at = created_at.astimezone(timezone.utc).replace(tzinfo=None)
|
||||
sla_response_due = created_at + timedelta(hours=float(resp_h))
|
||||
sla_resolution_due = created_at + timedelta(hours=float(res_h))
|
||||
|
||||
await session.execute(text("""
|
||||
UPDATE tickets
|
||||
SET sla_response_due = :sla_resp,
|
||||
sla_resolution_due = :sla_res
|
||||
WHERE id = :ticket_id
|
||||
"""), {
|
||||
"sla_resp": sla_response_due,
|
||||
"sla_res": sla_resolution_due,
|
||||
"ticket_id": ticket_id,
|
||||
})
|
||||
updated += 1
|
||||
|
||||
await session.commit()
|
||||
print(f"Updated {updated} tickets with SLA dates.")
|
||||
|
||||
# Verify
|
||||
r2 = await session.execute(text("""
|
||||
SELECT ticket_number, sla_response_due, sla_resolution_due
|
||||
FROM tickets
|
||||
WHERE sla_response_due IS NOT NULL
|
||||
ORDER BY created_at
|
||||
LIMIT 10
|
||||
"""))
|
||||
print("\nSample of tickets with SLA dates now:")
|
||||
for row in r2.fetchall():
|
||||
print(f" {row[0]}: response_due={row[1]}, resolution_due={row[2]}")
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
asyncio.run(run())
|
||||
238
scripts/seed_data.py
Normal file
238
scripts/seed_data.py
Normal file
@@ -0,0 +1,238 @@
|
||||
"""
|
||||
Script de datos iniciales (seed) para ServiceManagerWeb.
|
||||
Crea categorías, sistemas, y usuarios de prueba en el tenant aduanasoft-demo.
|
||||
Ejecutar desde dentro del contenedor: python /scripts/seed_data.py
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import select
|
||||
|
||||
# Configurar path para importar el app
|
||||
sys.path.insert(0, '/app')
|
||||
|
||||
from app.core.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
from app.core.security import security
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.category import Category
|
||||
from app.models.system import System
|
||||
from app.models.user import User, UserRole
|
||||
|
||||
TENANT_SLUG = "aduanasoft-demo"
|
||||
|
||||
CATEGORIES = [
|
||||
{
|
||||
"name": "Soporte Técnico",
|
||||
"description": "Problemas técnicos con sistemas y aplicaciones empresariales",
|
||||
"color": "#EF4444",
|
||||
"sla_response_hours": 2,
|
||||
"sla_resolution_hours": 24,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"name": "Facturación",
|
||||
"description": "Consultas y problemas relacionados con facturación y pagos",
|
||||
"color": "#F59E0B",
|
||||
"sla_response_hours": 4,
|
||||
"sla_resolution_hours": 48,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"name": "Incidentes Críticos",
|
||||
"description": "Fallas graves que afectan la operación del negocio",
|
||||
"color": "#DC2626",
|
||||
"sla_response_hours": 1,
|
||||
"sla_resolution_hours": 8,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"name": "Consultas Generales",
|
||||
"description": "Preguntas generales sobre productos y servicios",
|
||||
"color": "#3B82F6",
|
||||
"sla_response_hours": 8,
|
||||
"sla_resolution_hours": 72,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"name": "Capacitación",
|
||||
"description": "Solicitudes de entrenamiento y capacitación en sistemas",
|
||||
"color": "#8B5CF6",
|
||||
"sla_response_hours": 24,
|
||||
"sla_resolution_hours": 96,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"name": "Infraestructura",
|
||||
"description": "Problemas de red, servidores y componentes de infraestructura",
|
||||
"color": "#06B6D4",
|
||||
"sla_response_hours": 2,
|
||||
"sla_resolution_hours": 16,
|
||||
"is_active": True,
|
||||
},
|
||||
]
|
||||
|
||||
SYSTEMS = [
|
||||
{
|
||||
"name": "ERP Aduanero",
|
||||
"description": "Sistema principal de gestión aduanera y comercio exterior",
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"name": "Portal Web",
|
||||
"description": "Portal de autogestión y consultas en línea para clientes",
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"name": "Gestión Documental",
|
||||
"description": "Sistema de administración y archivo de documentos aduaneros",
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"name": "App Móvil",
|
||||
"description": "Aplicación móvil para seguimiento de trámites en tiempo real",
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"name": "Reportes y BI",
|
||||
"description": "Plataforma de inteligencia de negocio y generación de reportes",
|
||||
"is_active": True,
|
||||
},
|
||||
]
|
||||
|
||||
USERS = [
|
||||
{
|
||||
"email": "agente@aduanasoft.com",
|
||||
"password": "agente123",
|
||||
"first_name": "Carlos",
|
||||
"last_name": "Agente",
|
||||
"role": UserRole.AGENT,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"email": "manager@aduanasoft.com",
|
||||
"password": "manager123",
|
||||
"first_name": "Laura",
|
||||
"last_name": "Gerente",
|
||||
"role": UserRole.SUPPORT_MANAGER,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"email": "cliente@empresa-demo.com",
|
||||
"password": "cliente123",
|
||||
"first_name": "Roberto",
|
||||
"last_name": "Cliente",
|
||||
"role": UserRole.CLIENT_USER,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"email": "admin-cliente@empresa-demo.com",
|
||||
"password": "clienteadmin123",
|
||||
"first_name": "Ana",
|
||||
"last_name": "Admin",
|
||||
"role": UserRole.CLIENT_ADMIN,
|
||||
"is_active": True,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as session:
|
||||
# 1. Obtener tenant
|
||||
result = await session.execute(
|
||||
select(Tenant).where(Tenant.slug == TENANT_SLUG)
|
||||
)
|
||||
tenant = result.scalar_one_or_none()
|
||||
|
||||
if not tenant:
|
||||
print(f"ERROR: Tenant '{TENANT_SLUG}' no encontrado. Ejecuta el seed de tenants primero.")
|
||||
return
|
||||
|
||||
tenant_id = tenant.id
|
||||
print(f"✅ Tenant: {tenant.name} (id={str(tenant_id)[:8]}...)")
|
||||
|
||||
# 2. Categorías
|
||||
print("\n📁 Creando categorías:")
|
||||
# Eliminar la categoría "Infraestructura" duplicada creada previamente si existe
|
||||
existing_cats = (await session.execute(
|
||||
select(Category).where(Category.tenant_id == tenant_id)
|
||||
)).scalars().all()
|
||||
existing_names = {c.name for c in existing_cats}
|
||||
|
||||
created = 0
|
||||
for cat_data in CATEGORIES:
|
||||
if cat_data["name"] in existing_names:
|
||||
print(f" ⏭ Ya existe: {cat_data['name']}")
|
||||
continue
|
||||
cat = Category(
|
||||
tenant_id=tenant_id,
|
||||
**cat_data,
|
||||
)
|
||||
session.add(cat)
|
||||
print(f" ✓ {cat_data['name']} (resp={cat_data['sla_response_hours']}h, resol={cat_data['sla_resolution_hours']}h)")
|
||||
created += 1
|
||||
|
||||
await session.flush()
|
||||
print(f" → {created} categorías creadas, {len(existing_names)} ya existían")
|
||||
|
||||
# 3. Sistemas
|
||||
print("\n🖥 Creando sistemas:")
|
||||
existing_sys = (await session.execute(
|
||||
select(System).where(System.tenant_id == tenant_id)
|
||||
)).scalars().all()
|
||||
existing_sys_names = {s.name for s in existing_sys}
|
||||
|
||||
created_sys = 0
|
||||
for sys_data in SYSTEMS:
|
||||
if sys_data["name"] in existing_sys_names:
|
||||
print(f" ⏭ Ya existe: {sys_data['name']}")
|
||||
continue
|
||||
sys_obj = System(
|
||||
tenant_id=tenant_id,
|
||||
**sys_data,
|
||||
)
|
||||
session.add(sys_obj)
|
||||
print(f" ✓ {sys_data['name']}")
|
||||
created_sys += 1
|
||||
|
||||
await session.flush()
|
||||
print(f" → {created_sys} sistemas creados")
|
||||
|
||||
# 4. Usuarios
|
||||
print("\n👤 Creando usuarios:")
|
||||
existing_users = (await session.execute(
|
||||
select(User).where(User.tenant_id == tenant_id)
|
||||
)).scalars().all()
|
||||
existing_emails = {u.email for u in existing_users}
|
||||
|
||||
created_users = 0
|
||||
for user_data in USERS:
|
||||
if user_data["email"] in existing_emails:
|
||||
print(f" ⏭ Ya existe: {user_data['email']}")
|
||||
continue
|
||||
pwd = user_data.pop("password")
|
||||
hashed_pwd = security.hash_password(pwd)
|
||||
user = User(
|
||||
tenant_id=tenant_id,
|
||||
password_hash=hashed_pwd,
|
||||
**user_data,
|
||||
)
|
||||
session.add(user)
|
||||
print(f" ✓ {user_data['email']} [{user_data['role'].value}] pwd={pwd}")
|
||||
created_users += 1
|
||||
|
||||
await session.commit()
|
||||
print(f" → {created_users} usuarios creados")
|
||||
|
||||
await engine.dispose()
|
||||
print("\n🎉 Seed completado exitosamente.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
215
scripts/seed_tickets.py
Normal file
215
scripts/seed_tickets.py
Normal file
@@ -0,0 +1,215 @@
|
||||
"""
|
||||
Seed de tickets de demostración para ServiceManagerWeb.
|
||||
Crea tickets variados con categorías, sistemas y prioridades diferentes.
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, '/app')
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User
|
||||
from app.models.category import Category
|
||||
from app.models.system import System
|
||||
from app.models.ticket import Ticket, TicketStatus, TicketPriority
|
||||
from app.models.comment import TicketComment
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
TENANT_SLUG = "aduanasoft-demo"
|
||||
|
||||
DEMO_TICKETS = [
|
||||
{
|
||||
"subject": "Error en módulo de importaciones del ERP",
|
||||
"description": "Al intentar registrar una nueva importación, el sistema arroja el error 'NullReferenceException' en el módulo de gestión aduanera. Esto bloquea completamente el flujo de trabajo de importaciones para todos los usuarios del área.",
|
||||
"priority": TicketPriority.URGENT,
|
||||
"category_name": "Incidentes Críticos",
|
||||
"system_name": "ERP Aduanero",
|
||||
"status": TicketStatus.IN_PROGRESS,
|
||||
},
|
||||
{
|
||||
"subject": "No puedo acceder al portal web desde el lunes",
|
||||
"description": "Desde el lunes 18 de febrero, el portal web no carga correctamente. La página queda en blanco y en la consola del navegador aparece un error 503. He probado desde diferentes equipos y navegadores con el mismo resultado.",
|
||||
"priority": TicketPriority.HIGH,
|
||||
"category_name": "Soporte Técnico",
|
||||
"system_name": "Portal Web",
|
||||
"status": TicketStatus.NEW,
|
||||
},
|
||||
{
|
||||
"subject": "Solicitud de capacitación en módulo de reportes",
|
||||
"description": "Nuestro equipo necesita una sesión de capacitación para el nuevo módulo de reportes y BI. Somos 8 personas del área de contabilidad. Por favor confirmar disponibilidad para la semana del 3 de marzo.",
|
||||
"priority": TicketPriority.LOW,
|
||||
"category_name": "Capacitación",
|
||||
"system_name": "Reportes y BI",
|
||||
"status": TicketStatus.NEW,
|
||||
},
|
||||
{
|
||||
"subject": "Discrepancia en factura #F-2024-0892",
|
||||
"description": "La factura F-2024-0892 emitida el 15 de febrero muestra un monto de $47,500 MXN pero según nuestros registros el importe correcto es $45,200 MXN. Favor revisar y emitir nota de crédito si corresponde.",
|
||||
"priority": TicketPriority.MEDIUM,
|
||||
"category_name": "Facturación",
|
||||
"system_name": None,
|
||||
"status": TicketStatus.WAITING_CUSTOMER,
|
||||
},
|
||||
{
|
||||
"subject": "La app móvil no sincroniza pedidos pendientes",
|
||||
"description": "En la aplicación móvil versión 3.2.1, los pedidos creados desde el celular no se sincronizan al ERP. Ya desinstalé y reinstalé la app. El problema persiste en iOS y Android.",
|
||||
"priority": TicketPriority.HIGH,
|
||||
"category_name": "Soporte Técnico",
|
||||
"system_name": "App Móvil",
|
||||
"status": TicketStatus.IN_PROGRESS,
|
||||
},
|
||||
{
|
||||
"subject": "Consulta sobre proceso de pedimento de exportación",
|
||||
"description": "¿Cuáles son los documentos necesarios para tramitar un pedimento de exportación bajo la clave A1? Necesito la lista actualizada con los cambios del SAT de enero 2025.",
|
||||
"priority": TicketPriority.LOW,
|
||||
"category_name": "Consultas Generales",
|
||||
"system_name": None,
|
||||
"status": TicketStatus.RESOLVED,
|
||||
},
|
||||
{
|
||||
"subject": "Servidores lentos en horario pico (11am-2pm)",
|
||||
"description": "Durante el horario de 11am a 2pm el ERP se vuelve extremadamente lento. Las consultas que normalmente tardan 2 segundos pueden llevar hasta 45 segundos. El problema afecta a todos los usuarios concurrentemente.",
|
||||
"priority": TicketPriority.URGENT,
|
||||
"category_name": "Infraestructura",
|
||||
"system_name": "ERP Aduanero",
|
||||
"status": TicketStatus.IN_PROGRESS,
|
||||
},
|
||||
{
|
||||
"subject": "Error al generar reporte mensual de exportaciones",
|
||||
"description": "El reporte de exportaciones del mes de enero no se genera correctamente. Al dar clic en 'Generar PDF' aparece un error: 'Timeout al procesar el reporte'. Necesito este reporte para la reunión del viernes.",
|
||||
"priority": TicketPriority.MEDIUM,
|
||||
"category_name": "Soporte Técnico",
|
||||
"system_name": "Reportes y BI",
|
||||
"status": TicketStatus.NEW,
|
||||
},
|
||||
]
|
||||
|
||||
DEMO_COMMENTS = {
|
||||
"Error en módulo de importaciones del ERP": [
|
||||
("admin@aduanasoft.com", "Hemos identificado el problema. Es un error en la versión 4.2.1 del ERP. Estamos aplicando el parche de emergencia. Estimamos resolución en 2 horas.", False),
|
||||
("cliente@empresa-demo.com", "Gracias por la respuesta rápida. ¿Podemos continuar con el registro manual mientras tanto?", False),
|
||||
],
|
||||
"No puedo acceder al portal web desde el lunes": [
|
||||
("agente@aduanasoft.com", "Hemos recibido tu ticket. ¿Puedes indicarnos el mensaje exacto que aparece en la consola del navegador? Esto nos ayudará a diagnosticar más rápido.", False),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def main():
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as session:
|
||||
# Obtener tenant
|
||||
result = await session.execute(select(Tenant).where(Tenant.slug == TENANT_SLUG))
|
||||
tenant = result.scalar_one_or_none()
|
||||
if not tenant:
|
||||
print(f"ERROR: Tenant '{TENANT_SLUG}' no encontrado.")
|
||||
return
|
||||
|
||||
tenant_id = tenant.id
|
||||
print(f"✅ Tenant: {tenant.name}")
|
||||
|
||||
# Obtener usuario cliente para asignar como creador
|
||||
client_user_result = await session.execute(
|
||||
select(User).where(User.email == "cliente@empresa-demo.com").limit(1)
|
||||
)
|
||||
client_user = client_user_result.scalar_one_or_none()
|
||||
if not client_user:
|
||||
print("ERROR: cliente@empresa-demo.com no encontrado")
|
||||
return
|
||||
|
||||
# Obtener agente para comentarios
|
||||
agent_result = await session.execute(
|
||||
select(User).where(User.email == "agente@aduanasoft.com").limit(1)
|
||||
)
|
||||
agent_user = agent_result.scalar_one_or_none()
|
||||
|
||||
admin_result = await session.execute(
|
||||
select(User).where(User.email == "admin@aduanasoft.com").limit(1)
|
||||
)
|
||||
admin_user = admin_result.scalar_one_or_none()
|
||||
|
||||
# Mapear categorías y sistemas
|
||||
cats_result = await session.execute(select(Category).where(Category.tenant_id == tenant_id))
|
||||
cats = {c.name: c for c in cats_result.scalars().all()}
|
||||
|
||||
sys_result = await session.execute(select(System).where(System.tenant_id == tenant_id))
|
||||
systems = {s.name: s for s in sys_result.scalars().all()}
|
||||
|
||||
# Verificar tickets existentes
|
||||
existing_tickets_result = await session.execute(
|
||||
select(Ticket).where(Ticket.tenant_id == tenant_id)
|
||||
)
|
||||
existing_subjects = {t.subject for t in existing_tickets_result.scalars().all()}
|
||||
|
||||
print(f"\n🎫 Creando tickets de demostración:")
|
||||
created = 0
|
||||
ticket_objects = {}
|
||||
|
||||
for ticket_data in DEMO_TICKETS:
|
||||
subject = ticket_data["subject"]
|
||||
if subject in existing_subjects:
|
||||
print(f" ⏭ Ya existe: {subject[:50]}...")
|
||||
continue
|
||||
|
||||
cat_name = ticket_data.pop("category_name", None)
|
||||
sys_name = ticket_data.pop("system_name", None)
|
||||
|
||||
category = cats.get(cat_name) if cat_name else None
|
||||
system = systems.get(sys_name) if sys_name else None
|
||||
|
||||
ticket = Ticket(
|
||||
tenant_id=tenant_id,
|
||||
category_id=category.id if category else None,
|
||||
affected_system_id=system.id if system else None,
|
||||
created_by=client_user.id,
|
||||
**ticket_data,
|
||||
)
|
||||
session.add(ticket)
|
||||
await session.flush() # Para obtener el ID
|
||||
ticket_objects[subject] = ticket
|
||||
print(f" ✓ [{ticket_data['priority'].value}] {subject[:60]}")
|
||||
created += 1
|
||||
|
||||
await session.flush()
|
||||
|
||||
# Crear comentarios de demostración
|
||||
print(f"\n💬 Añadiendo comentarios:")
|
||||
for subject, comments in DEMO_COMMENTS.items():
|
||||
ticket = ticket_objects.get(subject)
|
||||
if not ticket:
|
||||
continue
|
||||
for author_email, content, is_internal in comments:
|
||||
if author_email == "admin@aduanasoft.com":
|
||||
author = admin_user
|
||||
elif author_email == "agente@aduanasoft.com":
|
||||
author = agent_user
|
||||
else:
|
||||
author = client_user
|
||||
|
||||
if author:
|
||||
comment = TicketComment(
|
||||
ticket_id=ticket.id,
|
||||
author_id=author.id,
|
||||
content=content,
|
||||
is_internal=is_internal,
|
||||
)
|
||||
session.add(comment)
|
||||
print(f" ✓ Comentario en '{subject[:40]}...' por {author_email}")
|
||||
|
||||
await session.commit()
|
||||
print(f"\n✅ {created} tickets creados exitosamente.")
|
||||
|
||||
await engine.dispose()
|
||||
print("🎉 Seed de tickets completado.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user