fix(impuestos): desactivar JIT en queries con subplans correlacionados
- Agrega helper withJitOff en impuestos.service.ts - Ejecuta getResumenIva, getIvaMensual y readResumenIvaFromCache con SET LOCAL jit = off - Evita compilación JIT de ~17s en queries con costo estimado alto feat(contribuyentes): auto-asignar a cartera del supervisor - Al crear contribuyente con supervisorUserId, se agrega automáticamente a todas las carteras top-level del supervisor feat(permisos): restricciones de UI por rol en contribuyentes - Oculta botón Add-ons para roles distintos de owner/cfo - Oculta botón Eliminar contribuyente para no-owner - Oculta botón Agregar RFC para auxiliar/visor/cliente/contador feat(cfdi): ver CFDI desde conceptos y forma de pago en Excel - Agrega botón Ver CFDI en cada fila de la tabla de Conceptos - Agrega columna Forma de Pago en export Excel de CFDIs - Agrega columna Forma de Pago en export individual de CFDI chore(migraciones): índices GIN para relaciones de activos - 048: índices btree parciales para activos - 049: índices GIN para cfdis_relacionados y uuid_relacionado
This commit is contained in:
@@ -7,6 +7,8 @@ import { AppError } from '../middlewares/error.middleware.js';
|
||||
/**
|
||||
* Valida que el auxiliar pertenezca al supervisor (o que el caller sea owner).
|
||||
* Owner puede asignar a cualquier auxiliar del tenant.
|
||||
* La relación se infiere desde carteras (directas y subcarteras) con fallback
|
||||
* a la tabla legacy auxiliar_supervisores.
|
||||
*/
|
||||
async function validarAuxiliarDelSupervisor(
|
||||
pool: import('pg').Pool,
|
||||
@@ -17,10 +19,22 @@ async function validarAuxiliarDelSupervisor(
|
||||
if (callerRole === 'owner') return;
|
||||
|
||||
const { rows } = await pool.query(
|
||||
`SELECT 1 FROM auxiliar_supervisores
|
||||
WHERE auxiliar_user_id = $1 AND supervisor_user_id = $2
|
||||
LIMIT 1`,
|
||||
[auxiliarUserId, supervisorUserId],
|
||||
`SELECT 1 FROM (
|
||||
SELECT c.auxiliar_user_id
|
||||
FROM carteras c
|
||||
WHERE c.supervisor_user_id = $1
|
||||
AND c.auxiliar_user_id = $2
|
||||
UNION
|
||||
SELECT sub.auxiliar_user_id
|
||||
FROM carteras sub
|
||||
JOIN carteras p ON p.id = sub.parent_id
|
||||
WHERE p.supervisor_user_id = $1
|
||||
AND sub.auxiliar_user_id = $2
|
||||
UNION
|
||||
SELECT auxiliar_user_id FROM auxiliar_supervisores
|
||||
WHERE supervisor_user_id = $1 AND auxiliar_user_id = $2
|
||||
) t LIMIT 1`,
|
||||
[supervisorUserId, auxiliarUserId],
|
||||
);
|
||||
|
||||
if (rows.length === 0) {
|
||||
@@ -28,10 +42,30 @@ async function validarAuxiliarDelSupervisor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida que el auxiliar tenga al contribuyente en alguna de sus subcarteras.
|
||||
* Si no hay ningún auxiliar con ese contribuyente en su subcartera, la asignación
|
||||
* se rechaza (el supervisor debe agregar el contribuyente a una subcartera primero).
|
||||
*/
|
||||
async function validarAuxiliarEnSubcartera(
|
||||
pool: import('pg').Pool,
|
||||
contribuyenteId: string,
|
||||
auxiliarUserId: string,
|
||||
): Promise<void> {
|
||||
const elegibles = await asignacionesService.getAuxiliaresElegibles(pool, contribuyenteId);
|
||||
if (elegibles.length === 0) {
|
||||
throw new AppError(403, 'Ningún auxiliar tiene este contribuyente en su subcartera');
|
||||
}
|
||||
if (!elegibles.includes(auxiliarUserId)) {
|
||||
throw new AppError(403, 'El auxiliar no tiene este contribuyente en ninguna de sus subcarteras');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Obligaciones ──
|
||||
|
||||
export async function asignarObligacion(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const contribuyenteId = String(req.params.id);
|
||||
const obligacionId = String(req.params.obligacionId);
|
||||
const schema = z.object({ auxiliarUserId: z.string().uuid() });
|
||||
const { auxiliarUserId } = schema.parse(req.body);
|
||||
@@ -42,6 +76,11 @@ export async function asignarObligacion(req: Request, res: Response, next: NextF
|
||||
auxiliarUserId,
|
||||
req.user!.role,
|
||||
);
|
||||
await validarAuxiliarEnSubcartera(
|
||||
req.tenantPool!,
|
||||
contribuyenteId,
|
||||
auxiliarUserId,
|
||||
);
|
||||
|
||||
await asignacionesService.asignarObligacion(
|
||||
req.tenantPool!,
|
||||
@@ -80,6 +119,19 @@ export async function asignarTarea(req: Request, res: Response, next: NextFuncti
|
||||
req.user!.role,
|
||||
);
|
||||
|
||||
// Obtener contribuyenteId de la tarea para validar subcartera
|
||||
const { rows } = await req.tenantPool!.query<{ contribuyente_id: string }>(
|
||||
`SELECT contribuyente_id FROM tareas_catalogo WHERE id = $1 LIMIT 1`,
|
||||
[tareaId],
|
||||
);
|
||||
if (rows.length > 0) {
|
||||
await validarAuxiliarEnSubcartera(
|
||||
req.tenantPool!,
|
||||
rows[0].contribuyente_id,
|
||||
auxiliarUserId,
|
||||
);
|
||||
}
|
||||
|
||||
await asignacionesService.asignarTarea(
|
||||
req.tenantPool!,
|
||||
tareaId,
|
||||
@@ -135,3 +187,11 @@ export async function listSinAsignar(req: Request, res: Response, next: NextFunc
|
||||
res.json({ obligaciones, tareas });
|
||||
} catch (err) { next(err); }
|
||||
}
|
||||
|
||||
export async function listAuxiliaresElegibles(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const contribuyenteId = String(req.params.contribuyenteId);
|
||||
const auxIds = await asignacionesService.getAuxiliaresElegibles(req.tenantPool!, contribuyenteId);
|
||||
res.json({ auxiliares: auxIds });
|
||||
} catch (err) { next(err); }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import { z } from 'zod';
|
||||
import * as contribuyenteService from '../services/contribuyente.service.js';
|
||||
import * as carteraService from '../services/cartera.service.js';
|
||||
import { AppError } from '../middlewares/error.middleware.js';
|
||||
import { getEntidadesVisibles } from '../utils/entidades-visibles.js';
|
||||
import { adjustDespachoOverage } from '../services/payment/addon.service.js';
|
||||
@@ -77,6 +78,19 @@ export async function create(req: Request, res: Response, next: NextFunction) {
|
||||
|
||||
const row = await contribuyenteService.createContribuyente(req.tenantPool!, data);
|
||||
|
||||
// Si se asignó un supervisor, agregar el contribuyente a todas las carteras
|
||||
// top-level de ese supervisor para que aparezca directamente en su vista.
|
||||
if (data.supervisorUserId) {
|
||||
try {
|
||||
const carteras = await carteraService.listCarteras(req.tenantPool!, data.supervisorUserId);
|
||||
await Promise.all(
|
||||
carteras.map(c => carteraService.addEntidadToCartera(req.tenantPool!, c.id, row.id))
|
||||
);
|
||||
} catch (err: any) {
|
||||
console.error('[Contribuyente] Auto-assign to cartera failed (non-blocking):', err.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
// Ajuste de overage despacho: si el tenant pasa de 100 a 101+ RFCs, crea
|
||||
// el addon y devuelve paymentUrl para que el frontend redirija al usuario.
|
||||
// Fail-soft: si falla el addon, el contribuyente queda creado y se loguea.
|
||||
|
||||
Reference in New Issue
Block a user