91 lines
3.2 KiB
TypeScript
91 lines
3.2 KiB
TypeScript
import type { Pool } from 'pg';
|
|
import { prisma } from '../config/database.js';
|
|
import { emailService } from './email/email.service.js';
|
|
import { getTenantOwnerEmails, getUserEmailById } from '../utils/memberships.js';
|
|
import { env } from '../config/env.js';
|
|
import { getContribuyenteEmailPreferences } from './notification-preferences.service.js';
|
|
import type { DocumentoSubidoData } from './email/templates/documento-subido.js';
|
|
|
|
/**
|
|
* Notifica a los destinatarios relevantes cuando se sube una declaración
|
|
* o un documento extra. Destinatarios:
|
|
* - Owners activos del despacho (getTenantOwnerEmails)
|
|
* - Supervisor del contribuyente (entidades_gestionadas.supervisor_user_id),
|
|
* si existe y no coincide con un owner ya incluido
|
|
*
|
|
* El uploader mismo SE EXCLUYE (no tiene sentido notificarle su propia acción).
|
|
*
|
|
* Fire-and-forget: el caller hace `.catch()` y esta función no re-lanza.
|
|
* Fail-soft: si SMTP no está configurado, los envíos se loguean a consola
|
|
* vía el transport de @horux/core.
|
|
*/
|
|
export async function notifyDocumentoSubido(params: {
|
|
pool: Pool;
|
|
tenantId: string;
|
|
contribuyenteId: string | null;
|
|
subidoPor: string;
|
|
kind: DocumentoSubidoData['kind'];
|
|
declaracion?: DocumentoSubidoData['declaracion'];
|
|
extra?: DocumentoSubidoData['extra'];
|
|
}): Promise<void> {
|
|
const { pool, tenantId, contribuyenteId, subidoPor } = params;
|
|
|
|
// 1. Datos del contribuyente (desde BD tenant). Sin contribuyenteId no hay
|
|
// subject informativo ni supervisor — skip.
|
|
if (!contribuyenteId) return;
|
|
|
|
// Respeta preferencias de notificación del contribuyente. Si el user
|
|
// desactivó `documento_subido` para este contribuyente, no enviar.
|
|
const prefs = await getContribuyenteEmailPreferences(pool, contribuyenteId);
|
|
if (!prefs.documento_subido) return;
|
|
|
|
const { rows } = await pool.query<{
|
|
rfc: string;
|
|
nombre: string;
|
|
supervisor_user_id: string | null;
|
|
}>(
|
|
`SELECT c.rfc, eg.nombre, eg.supervisor_user_id
|
|
FROM contribuyentes c
|
|
JOIN entidades_gestionadas eg ON eg.id = c.entidad_id
|
|
WHERE c.entidad_id = $1`,
|
|
[contribuyenteId.replace(/[^a-f0-9-]/gi, '')],
|
|
);
|
|
if (rows.length === 0) return;
|
|
const contrib = rows[0];
|
|
|
|
// 2. Recipients. Owners primero; luego supervisor si aplica.
|
|
const owners = await getTenantOwnerEmails(tenantId);
|
|
const recipients = new Set<string>(owners);
|
|
|
|
if (contrib.supervisor_user_id) {
|
|
const supervisorEmail = await getUserEmailById(contrib.supervisor_user_id);
|
|
if (supervisorEmail) recipients.add(supervisorEmail);
|
|
}
|
|
|
|
// Excluir al uploader: no notificarle su propia acción.
|
|
recipients.delete(subidoPor.toLowerCase());
|
|
recipients.delete(subidoPor);
|
|
|
|
if (recipients.size === 0) return;
|
|
|
|
// 3. Datos del despacho (para mostrar en el body).
|
|
const tenant = await prisma.tenant.findUnique({
|
|
where: { id: tenantId },
|
|
select: { nombre: true },
|
|
});
|
|
|
|
// 4. Link al sistema. Usa FRONTEND_URL del env.
|
|
const link = `${env.FRONTEND_URL}/documentos`;
|
|
|
|
await emailService.sendDocumentoSubido(Array.from(recipients), {
|
|
kind: params.kind,
|
|
subidoPor,
|
|
contribuyenteRfc: contrib.rfc,
|
|
contribuyenteNombre: contrib.nombre,
|
|
despachoNombre: tenant?.nombre,
|
|
declaracion: params.declaracion,
|
|
extra: params.extra,
|
|
link,
|
|
});
|
|
}
|