feat: rewrite tenants service to use TenantConnectionManager
- Replace inline schema SQL with tenantDb.provisionDatabase - Delete now soft-deletes DB (rename) and invalidates pool - Use PLANS config for default limits per plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user