feat: integrate email + subscription into tenant provisioning, FIEL notification
createTenant now: provisions DB, creates admin user with temp password, creates initial subscription, and sends welcome email. FIEL upload sends admin notification email. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,21 +39,24 @@ export async function createTenant(req: Request, res: Response, next: NextFuncti
|
||||
throw new AppError(403, 'Solo administradores pueden crear clientes');
|
||||
}
|
||||
|
||||
const { nombre, rfc, plan, cfdiLimit, usersLimit } = req.body;
|
||||
const { nombre, rfc, plan, cfdiLimit, usersLimit, adminEmail, adminNombre, amount } = req.body;
|
||||
|
||||
if (!nombre || !rfc) {
|
||||
throw new AppError(400, 'Nombre y RFC son requeridos');
|
||||
if (!nombre || !rfc || !adminEmail || !adminNombre) {
|
||||
throw new AppError(400, 'Nombre, RFC, adminEmail y adminNombre son requeridos');
|
||||
}
|
||||
|
||||
const tenant = await tenantsService.createTenant({
|
||||
const result = await tenantsService.createTenant({
|
||||
nombre,
|
||||
rfc,
|
||||
plan,
|
||||
cfdiLimit,
|
||||
usersLimit,
|
||||
adminEmail,
|
||||
adminNombre,
|
||||
amount: amount || 0,
|
||||
});
|
||||
|
||||
res.status(201).json(tenant);
|
||||
res.status(201).json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { join } from 'path';
|
||||
import { prisma } from '../config/database.js';
|
||||
import { env } from '../config/env.js';
|
||||
import { encryptFielCredentials, encrypt, decryptFielCredentials } from './sat/sat-crypto.service.js';
|
||||
import { emailService } from './email/email.service.js';
|
||||
import type { FielStatus } from '@horux/shared';
|
||||
|
||||
/**
|
||||
@@ -144,10 +145,22 @@ export async function uploadFiel(
|
||||
console.error('[FIEL] Filesystem storage failed (DB storage OK):', fsError);
|
||||
}
|
||||
|
||||
// Notify admin that client uploaded FIEL
|
||||
const tenant = await prisma.tenant.findUnique({
|
||||
where: { id: tenantId },
|
||||
select: { nombre: true, rfc: true },
|
||||
});
|
||||
if (tenant) {
|
||||
emailService.sendFielNotification({
|
||||
clienteNombre: tenant.nombre,
|
||||
clienteRfc: tenant.rfc,
|
||||
}).catch(err => console.error('[EMAIL] FIEL notification failed:', err));
|
||||
}
|
||||
|
||||
const daysUntilExpiration = Math.ceil(
|
||||
(validUntil.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'FIEL configurada correctamente',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { prisma, tenantDb } from '../config/database.js';
|
||||
import { PLANS } from '@horux/shared';
|
||||
import { emailService } from './email/email.service.js';
|
||||
import { randomBytes } from 'crypto';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
export async function getAllTenants() {
|
||||
return prisma.tenant.findMany({
|
||||
@@ -41,13 +44,17 @@ export async function createTenant(data: {
|
||||
plan?: 'starter' | 'business' | 'professional' | 'enterprise';
|
||||
cfdiLimit?: number;
|
||||
usersLimit?: number;
|
||||
adminEmail: string;
|
||||
adminNombre: string;
|
||||
amount: number;
|
||||
}) {
|
||||
const plan = data.plan || 'starter';
|
||||
const planConfig = PLANS[plan];
|
||||
|
||||
// Provision a dedicated database for this tenant
|
||||
// 1. Provision a dedicated database for this tenant
|
||||
const databaseName = await tenantDb.provisionDatabase(data.rfc);
|
||||
|
||||
// 2. Create tenant record
|
||||
const tenant = await prisma.tenant.create({
|
||||
data: {
|
||||
nombre: data.nombre,
|
||||
@@ -59,7 +66,39 @@ export async function createTenant(data: {
|
||||
}
|
||||
});
|
||||
|
||||
return tenant;
|
||||
// 3. Create admin user with temp password
|
||||
const tempPassword = randomBytes(4).toString('hex'); // 8-char random
|
||||
const hashedPassword = await bcrypt.hash(tempPassword, 10);
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
tenantId: tenant.id,
|
||||
email: data.adminEmail,
|
||||
passwordHash: hashedPassword,
|
||||
nombre: data.adminNombre,
|
||||
role: 'admin',
|
||||
},
|
||||
});
|
||||
|
||||
// 4. Create initial subscription
|
||||
await prisma.subscription.create({
|
||||
data: {
|
||||
tenantId: tenant.id,
|
||||
plan,
|
||||
status: 'pending',
|
||||
amount: data.amount,
|
||||
frequency: 'monthly',
|
||||
},
|
||||
});
|
||||
|
||||
// 5. Send welcome email (non-blocking)
|
||||
emailService.sendWelcome(data.adminEmail, {
|
||||
nombre: data.adminNombre,
|
||||
email: data.adminEmail,
|
||||
tempPassword,
|
||||
}).catch(err => console.error('[EMAIL] Welcome email failed:', err));
|
||||
|
||||
return { tenant, user, tempPassword };
|
||||
}
|
||||
|
||||
export async function updateTenant(id: string, data: {
|
||||
|
||||
Reference in New Issue
Block a user