Initial commit - Horux Despachos NL
This commit is contained in:
90
apps/api/src/services/notify-upload.service.ts
Normal file
90
apps/api/src/services/notify-upload.service.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user