diff --git a/apps/api/src/services/tenants.service.ts b/apps/api/src/services/tenants.service.ts index 806943e..9178ea9 100644 --- a/apps/api/src/services/tenants.service.ts +++ b/apps/api/src/services/tenants.service.ts @@ -1,4 +1,5 @@ -import { prisma } from '../config/database.js'; +import { prisma, tenantDb } from '../config/database.js'; +import { PLANS } from '@horux/shared'; export async function getAllTenants() { return prisma.tenant.findMany({ @@ -41,102 +42,23 @@ export async function createTenant(data: { cfdiLimit?: number; usersLimit?: number; }) { - const databaseName = `horux_${data.rfc.toLowerCase().replace(/[^a-z0-9]/g, '')}`; + const plan = data.plan || 'starter'; + const planConfig = PLANS[plan]; + + // Provision a dedicated database for this tenant + const databaseName = await tenantDb.provisionDatabase(data.rfc); - // Create tenant record const tenant = await prisma.tenant.create({ data: { nombre: data.nombre, rfc: data.rfc.toUpperCase(), - plan: data.plan || 'starter', + plan, databaseName, - cfdiLimit: data.cfdiLimit || 500, - usersLimit: data.usersLimit || 3, + cfdiLimit: data.cfdiLimit || planConfig.cfdiLimit, + usersLimit: data.usersLimit || planConfig.usersLimit, } }); - // Create schema and tables for the tenant - await prisma.$executeRawUnsafe(`CREATE SCHEMA IF NOT EXISTS "${databaseName}"`); - - // Create CFDIs table - await prisma.$executeRawUnsafe(` - CREATE TABLE IF NOT EXISTS "${databaseName}"."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 "${databaseName}"."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 "${databaseName}"."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 "${databaseName}"."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; } @@ -173,9 +95,20 @@ export async function updateTenant(id: string, data: { } export async function deleteTenant(id: string) { - // Soft delete - just mark as inactive - return prisma.tenant.update({ + const tenant = await prisma.tenant.findUnique({ + where: { id }, + select: { databaseName: true }, + }); + + // Soft-delete the tenant record + await prisma.tenant.update({ where: { id }, data: { active: false } }); + + // Soft-delete the database (rename with _deleted_ suffix) + if (tenant) { + await tenantDb.deprovisionDatabase(tenant.databaseName); + tenantDb.invalidatePool(id); + } }