import { prisma } from '../config/database.js'; export async function getAllTenants() { return prisma.tenant.findMany({ where: { active: true }, select: { id: true, nombre: true, rfc: true, plan: true, schemaName: true, createdAt: true, _count: { select: { users: true } } }, orderBy: { nombre: 'asc' } }); } export async function getTenantById(id: string) { return prisma.tenant.findUnique({ where: { id }, select: { id: true, nombre: true, rfc: true, plan: true, schemaName: true, cfdiLimit: true, usersLimit: true, createdAt: true, } }); } export async function createTenant(data: { nombre: string; rfc: string; plan?: 'starter' | 'business' | 'professional' | 'enterprise'; cfdiLimit?: number; usersLimit?: number; }) { const schemaName = `tenant_${data.rfc.toLowerCase().replace(/[^a-z0-9]/g, '')}`; // Create tenant record const tenant = await prisma.tenant.create({ data: { nombre: data.nombre, rfc: data.rfc.toUpperCase(), plan: data.plan || 'starter', schemaName, cfdiLimit: data.cfdiLimit || 500, usersLimit: data.usersLimit || 3, } }); // Create schema and tables for the tenant await prisma.$executeRawUnsafe(`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`); // Create CFDIs table await prisma.$executeRawUnsafe(` CREATE TABLE IF NOT EXISTS "${schemaName}"."cfdis" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), uuid_fiscal VARCHAR(36) UNIQUE NOT NULL, tipo VARCHAR(20) NOT NULL, serie VARCHAR(25), folio VARCHAR(40), fecha_emision TIMESTAMP NOT NULL, fecha_timbrado TIMESTAMP NOT NULL, rfc_emisor VARCHAR(13) NOT NULL, nombre_emisor VARCHAR(300) NOT NULL, rfc_receptor VARCHAR(13) NOT NULL, nombre_receptor VARCHAR(300) NOT NULL, subtotal DECIMAL(18,2) NOT NULL, descuento DECIMAL(18,2) DEFAULT 0, iva DECIMAL(18,2) DEFAULT 0, isr_retenido DECIMAL(18,2) DEFAULT 0, iva_retenido DECIMAL(18,2) DEFAULT 0, total DECIMAL(18,2) NOT NULL, moneda VARCHAR(3) DEFAULT 'MXN', tipo_cambio DECIMAL(10,4) DEFAULT 1, metodo_pago VARCHAR(3), forma_pago VARCHAR(2), uso_cfdi VARCHAR(4), estado VARCHAR(20) DEFAULT 'vigente', xml_url TEXT, pdf_url TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); // Create IVA monthly table await prisma.$executeRawUnsafe(` CREATE TABLE IF NOT EXISTS "${schemaName}"."iva_mensual" ( id SERIAL PRIMARY KEY, año INT NOT NULL, mes INT NOT NULL, iva_trasladado DECIMAL(18,2) NOT NULL, iva_acreditable DECIMAL(18,2) NOT NULL, iva_retenido DECIMAL(18,2) DEFAULT 0, resultado DECIMAL(18,2) NOT NULL, acumulado DECIMAL(18,2) NOT NULL, estado VARCHAR(20) DEFAULT 'pendiente', fecha_declaracion TIMESTAMP, UNIQUE(año, mes) ) `); // Create alerts table await prisma.$executeRawUnsafe(` CREATE TABLE IF NOT EXISTS "${schemaName}"."alertas" ( id SERIAL PRIMARY KEY, tipo VARCHAR(50) NOT NULL, titulo VARCHAR(200) NOT NULL, mensaje TEXT NOT NULL, prioridad VARCHAR(20) DEFAULT 'media', fecha_vencimiento TIMESTAMP, leida BOOLEAN DEFAULT FALSE, resuelta BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); // Create calendario_fiscal table await prisma.$executeRawUnsafe(` CREATE TABLE IF NOT EXISTS "${schemaName}"."calendario_fiscal" ( id SERIAL PRIMARY KEY, titulo VARCHAR(200) NOT NULL, descripcion TEXT, tipo VARCHAR(20) NOT NULL, fecha_limite TIMESTAMP NOT NULL, recurrencia VARCHAR(20) DEFAULT 'mensual', completado BOOLEAN DEFAULT FALSE, notas TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); return tenant; } export async function updateTenant(id: string, data: { nombre?: string; rfc?: string; plan?: 'starter' | 'business' | 'professional' | 'enterprise'; cfdiLimit?: number; usersLimit?: number; active?: boolean; }) { return prisma.tenant.update({ where: { id }, data: { ...(data.nombre && { nombre: data.nombre }), ...(data.rfc && { rfc: data.rfc.toUpperCase() }), ...(data.plan && { plan: data.plan }), ...(data.cfdiLimit !== undefined && { cfdiLimit: data.cfdiLimit }), ...(data.usersLimit !== undefined && { usersLimit: data.usersLimit }), ...(data.active !== undefined && { active: data.active }), }, select: { id: true, nombre: true, rfc: true, plan: true, schemaName: true, cfdiLimit: true, usersLimit: true, active: true, createdAt: true, } }); } export async function deleteTenant(id: string) { // Soft delete - just mark as inactive return prisma.tenant.update({ where: { id }, data: { active: false } }); }