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:
@@ -24,44 +24,62 @@
|
||||
* el de activos aplica también pero algunos predicados son no-op funcional
|
||||
* en subqueries que filtran por tipo_comprobante específico (Postgres los
|
||||
* optimiza away).
|
||||
*
|
||||
* OPTIMIZACIÓN: los subqueries de exclusiones de activos se reescribieron
|
||||
* para usar subqueries NO-correlacionados donde sea posible (casos 1-3).
|
||||
* Esto permite a PostgreSQL ejecutar el subquery una sola vez por query
|
||||
* principal, en lugar de una vez por cada fila. Solo el caso 4 (anticipo
|
||||
* referenciado por I07) requiere un correlated EXISTS.
|
||||
*/
|
||||
|
||||
const ACTIVOS_USOS = "('I01','I02','I03','I04','I05','I06','I07','I08')";
|
||||
|
||||
/**
|
||||
* Subquery no-correlacionado que devuelve todos los UUIDs de facturas tipo I
|
||||
* con uso de activo. Usado para lookups P→I y E→I.
|
||||
*/
|
||||
const UUIDS_ACTIVOS = `SELECT LOWER(uuid) AS uuid FROM cfdis WHERE tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS_USOS}`;
|
||||
|
||||
/**
|
||||
* Subquery no-correlacionado que devuelve todos los UUIDs de E's que
|
||||
* referencian un activo (directamente I-activo, o indirectamente P→I-activo).
|
||||
*
|
||||
* Usa JOIN + UNION en lugar de EXISTS + OR para que PostgreSQL pueda usar
|
||||
* índices de forma más efectiva (especialmente el GIN en cfdis_relacionados).
|
||||
*/
|
||||
const UUIDS_E_DE_ACTIVOS = `
|
||||
SELECT e.uuid
|
||||
FROM cfdis e
|
||||
JOIN cfdis r_act ON LOWER(r_act.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
|
||||
WHERE e.tipo_comprobante = 'E'
|
||||
AND e.cfdis_relacionados IS NOT NULL
|
||||
AND r_act.tipo_comprobante = 'I'
|
||||
AND r_act.uso_cfdi IN ${ACTIVOS_USOS}
|
||||
UNION ALL
|
||||
SELECT e.uuid
|
||||
FROM cfdis e
|
||||
JOIN cfdis r_act ON LOWER(r_act.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
|
||||
JOIN cfdis pi_act ON LOWER(pi_act.uuid) = LOWER(r_act.uuid_relacionado)
|
||||
WHERE e.tipo_comprobante = 'E'
|
||||
AND e.cfdis_relacionados IS NOT NULL
|
||||
AND r_act.tipo_comprobante = 'P'
|
||||
AND pi_act.tipo_comprobante = 'I'
|
||||
AND pi_act.uso_cfdi IN ${ACTIVOS_USOS}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Predicado SQL que detecta si el row actual (sin alias de tabla, asume
|
||||
* `FROM cfdis`) referencia un activo directamente (I), indirectamente vía
|
||||
* pago (P→I), o transitivamente vía relación (E→I, E→P→I).
|
||||
*
|
||||
* IMPORTANTE — qualifying outer refs: dentro de los subqueries `cfdis i_act`
|
||||
* y `cfdis r_act`, la tabla interna también tiene columnas `uuid_relacionado`
|
||||
* y `cfdis_relacionados`. Una referencia no-qualificada las resolvería a las
|
||||
* columnas internas (NO al row outer), volviendo el predicado a no-op.
|
||||
* Por eso usamos `cfdis.uuid_relacionado` y `cfdis.cfdis_relacionados`
|
||||
* explícitamente — fuerza la resolución al outer.
|
||||
*/
|
||||
function activosExclusionNoAlias(): string {
|
||||
return `
|
||||
AND NOT (tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS_USOS})
|
||||
AND NOT (tipo_comprobante = 'P' AND EXISTS (
|
||||
SELECT 1 FROM cfdis i_act
|
||||
WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado)
|
||||
AND i_act.tipo_comprobante = 'I'
|
||||
AND i_act.uso_cfdi IN ${ACTIVOS_USOS}
|
||||
))
|
||||
AND NOT (tipo_comprobante = 'E' AND cfdis.cfdis_relacionados IS NOT NULL AND EXISTS (
|
||||
SELECT 1 FROM cfdis r_act
|
||||
WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(cfdis.cfdis_relacionados), '|'))
|
||||
AND (
|
||||
(r_act.tipo_comprobante = 'I' AND r_act.uso_cfdi IN ${ACTIVOS_USOS})
|
||||
OR (r_act.tipo_comprobante = 'P' AND EXISTS (
|
||||
SELECT 1 FROM cfdis pi_act
|
||||
WHERE LOWER(pi_act.uuid) = LOWER(r_act.uuid_relacionado)
|
||||
AND pi_act.tipo_comprobante = 'I'
|
||||
AND pi_act.uso_cfdi IN ${ACTIVOS_USOS}
|
||||
))
|
||||
)
|
||||
SELECT 1 FROM (${UUIDS_ACTIVOS}) ua
|
||||
WHERE ua.uuid = ANY(string_to_array(LOWER(uuid_relacionado), '|'))
|
||||
))
|
||||
AND NOT (tipo_comprobante = 'E' AND uuid IN (${UUIDS_E_DE_ACTIVOS}))
|
||||
AND NOT (tipo_comprobante = 'I' AND EXISTS (
|
||||
-- Anticipo: CFDI tipo I (puede no tener uso_cfdi de activo) que es
|
||||
-- referenciado por una I/07 PPD con uso_cfdi de activo. La I/07 PPD
|
||||
@@ -87,24 +105,10 @@ function activosExclusionAlias(alias: string): string {
|
||||
return `
|
||||
AND NOT (${alias}.tipo_comprobante = 'I' AND ${alias}.uso_cfdi IN ${ACTIVOS_USOS})
|
||||
AND NOT (${alias}.tipo_comprobante = 'P' AND EXISTS (
|
||||
SELECT 1 FROM cfdis i_act
|
||||
WHERE LOWER(i_act.uuid) = LOWER(${alias}.uuid_relacionado)
|
||||
AND i_act.tipo_comprobante = 'I'
|
||||
AND i_act.uso_cfdi IN ${ACTIVOS_USOS}
|
||||
))
|
||||
AND NOT (${alias}.tipo_comprobante = 'E' AND ${alias}.cfdis_relacionados IS NOT NULL AND EXISTS (
|
||||
SELECT 1 FROM cfdis r_act
|
||||
WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(${alias}.cfdis_relacionados), '|'))
|
||||
AND (
|
||||
(r_act.tipo_comprobante = 'I' AND r_act.uso_cfdi IN ${ACTIVOS_USOS})
|
||||
OR (r_act.tipo_comprobante = 'P' AND EXISTS (
|
||||
SELECT 1 FROM cfdis pi_act
|
||||
WHERE LOWER(pi_act.uuid) = LOWER(r_act.uuid_relacionado)
|
||||
AND pi_act.tipo_comprobante = 'I'
|
||||
AND pi_act.uso_cfdi IN ${ACTIVOS_USOS}
|
||||
))
|
||||
)
|
||||
SELECT 1 FROM (${UUIDS_ACTIVOS}) ua
|
||||
WHERE ua.uuid = ANY(string_to_array(LOWER(${alias}.uuid_relacionado), '|'))
|
||||
))
|
||||
AND NOT (${alias}.tipo_comprobante = 'E' AND ${alias}.uuid IN (${UUIDS_E_DE_ACTIVOS}))
|
||||
AND NOT (${alias}.tipo_comprobante = 'I' AND EXISTS (
|
||||
SELECT 1 FROM cfdis i07_act
|
||||
WHERE i07_act.tipo_comprobante = 'I'
|
||||
|
||||
@@ -96,12 +96,32 @@ export async function getAsignacionesPorSupervisor(
|
||||
): Promise<{ obligaciones: AsignacionObligacion[]; tareas: AsignacionTarea[] }> {
|
||||
const isOwner = role === 'owner' || role === 'cfo' || role === 'contador';
|
||||
|
||||
// Relación supervisor → auxiliar se infiere desde carteras (directas y
|
||||
// subcarteras) con fallback a la tabla legacy auxiliar_supervisores.
|
||||
const supervisorFilter = isOwner
|
||||
? ''
|
||||
: `AND EXISTS (
|
||||
SELECT 1 FROM (
|
||||
SELECT c.auxiliar_user_id
|
||||
FROM carteras c
|
||||
WHERE c.supervisor_user_id = $1
|
||||
AND c.auxiliar_user_id IS NOT NULL
|
||||
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 IS NOT NULL
|
||||
UNION
|
||||
SELECT auxiliar_user_id FROM auxiliar_supervisores WHERE supervisor_user_id = $1
|
||||
) sup_aux WHERE sup_aux.auxiliar_user_id = __AUX_COL__
|
||||
)`;
|
||||
const whereObl = isOwner
|
||||
? 'WHERE 1=1'
|
||||
: 'WHERE EXISTS (SELECT 1 FROM auxiliar_supervisores asp WHERE asp.auxiliar_user_id = oa.auxiliar_user_id AND asp.supervisor_user_id = $1)';
|
||||
: `WHERE 1=1 ${supervisorFilter.replace(/__AUX_COL__/g, 'oa.auxiliar_user_id')}`;
|
||||
const whereTarea = isOwner
|
||||
? 'WHERE 1=1'
|
||||
: 'WHERE EXISTS (SELECT 1 FROM auxiliar_supervisores asp WHERE asp.auxiliar_user_id = ta.auxiliar_user_id AND asp.supervisor_user_id = $1)';
|
||||
: `WHERE 1=1 ${supervisorFilter.replace(/__AUX_COL__/g, 'ta.auxiliar_user_id')}`;
|
||||
const params = isOwner ? [] : [supervisorUserId];
|
||||
|
||||
const { rows: obligaciones } = await pool.query<AsignacionObligacion>(
|
||||
@@ -301,3 +321,23 @@ export async function getAuxiliarAsignadoTarea(
|
||||
const names = await resolveUserNames([auxId]);
|
||||
return { auxiliarUserId: auxId, auxiliarNombre: names.get(auxId) ?? null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuelve los userIds de auxiliares que tienen al contribuyente en alguna
|
||||
* de sus subcarteras (carteras con auxiliar_user_id no nulo que contienen
|
||||
* al contribuyente en cartera_entidades).
|
||||
*/
|
||||
export async function getAuxiliaresElegibles(
|
||||
pool: Pool,
|
||||
contribuyenteId: string,
|
||||
): Promise<string[]> {
|
||||
const { rows } = await pool.query<{ auxiliar_user_id: string }>(
|
||||
`SELECT DISTINCT c.auxiliar_user_id
|
||||
FROM carteras c
|
||||
JOIN cartera_entidades ce ON ce.cartera_id = c.id
|
||||
WHERE ce.entidad_id = $1
|
||||
AND c.auxiliar_user_id IS NOT NULL`,
|
||||
[contribuyenteId],
|
||||
);
|
||||
return rows.map(r => r.auxiliar_user_id);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Pool } from 'pg';
|
||||
import type { Pool, PoolClient } from 'pg';
|
||||
import type { IvaMensual, IsrMensual, ResumenIva, IvaRegimenDetalle, ResumenIsr } from '@horux/shared';
|
||||
import { getRegimenesIgnoradosClaves } from './regimen.service.js';
|
||||
import {
|
||||
@@ -106,32 +106,40 @@ const SUM_E_REFERENCING_TRAS = (
|
||||
esLadoE: string,
|
||||
considerarActivos: boolean,
|
||||
considerarNCs: boolean,
|
||||
) => `COALESCE((
|
||||
SELECT SUM(${IVA_TRAS_EXPR_ALIAS('e')})
|
||||
FROM cfdis e
|
||||
WHERE e.tipo_comprobante = 'E'
|
||||
AND e.metodo_pago = 'PUE'
|
||||
AND e.status NOT IN ('Cancelado', '0')
|
||||
AND ${esLadoE}
|
||||
AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
|
||||
AND date_trunc('month', COALESCE(e.fecha_efectiva, e.fecha_emision - interval '1 hour'))
|
||||
= date_trunc('month', COALESCE(cfdis.fecha_efectiva, cfdis.fecha_emision - interval '1 hour'))${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)}
|
||||
), 0)`;
|
||||
) => {
|
||||
if (!considerarNCs) return '0';
|
||||
return `COALESCE((
|
||||
SELECT SUM(${IVA_TRAS_EXPR_ALIAS('e')})
|
||||
FROM cfdis e
|
||||
WHERE e.tipo_comprobante = 'E'
|
||||
AND e.metodo_pago = 'PUE'
|
||||
AND e.status NOT IN ('Cancelado', '0')
|
||||
AND ${esLadoE}
|
||||
AND e.cfdis_relacionados IS NOT NULL
|
||||
AND string_to_array(LOWER(e.cfdis_relacionados), '|') @> ARRAY[LOWER(cfdis.uuid)]
|
||||
AND date_trunc('month', COALESCE(e.fecha_efectiva, e.fecha_emision - interval '1 hour'))
|
||||
= date_trunc('month', COALESCE(cfdis.fecha_efectiva, cfdis.fecha_emision - interval '1 hour'))${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)}
|
||||
), 0)`;
|
||||
};
|
||||
const SUM_E_REFERENCING_RET = (
|
||||
esLadoE: string,
|
||||
considerarActivos: boolean,
|
||||
considerarNCs: boolean,
|
||||
) => `COALESCE((
|
||||
SELECT SUM(${IVA_RET_EXPR_ALIAS('e')})
|
||||
FROM cfdis e
|
||||
WHERE e.tipo_comprobante = 'E'
|
||||
AND e.metodo_pago = 'PUE'
|
||||
AND e.status NOT IN ('Cancelado', '0')
|
||||
AND ${esLadoE}
|
||||
AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
|
||||
AND date_trunc('month', COALESCE(e.fecha_efectiva, e.fecha_emision - interval '1 hour'))
|
||||
= date_trunc('month', COALESCE(cfdis.fecha_efectiva, cfdis.fecha_emision - interval '1 hour'))${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)}
|
||||
), 0)`;
|
||||
) => {
|
||||
if (!considerarNCs) return '0';
|
||||
return `COALESCE((
|
||||
SELECT SUM(${IVA_RET_EXPR_ALIAS('e')})
|
||||
FROM cfdis e
|
||||
WHERE e.tipo_comprobante = 'E'
|
||||
AND e.metodo_pago = 'PUE'
|
||||
AND e.status NOT IN ('Cancelado', '0')
|
||||
AND ${esLadoE}
|
||||
AND e.cfdis_relacionados IS NOT NULL
|
||||
AND string_to_array(LOWER(e.cfdis_relacionados), '|') @> ARRAY[LOWER(cfdis.uuid)]
|
||||
AND date_trunc('month', COALESCE(e.fecha_efectiva, e.fecha_emision - interval '1 hour'))
|
||||
= date_trunc('month', COALESCE(cfdis.fecha_efectiva, cfdis.fecha_emision - interval '1 hour'))${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)}
|
||||
), 0)`;
|
||||
};
|
||||
// Régimen del contribuyente según su lado: emisor/receptor del CFDI.
|
||||
// Usa el RFC del contribuyente (via `ctx.esEmisor`/`ctx.esReceptor`) para
|
||||
// determinar el lado, no el `type` de BD.
|
||||
@@ -152,16 +160,20 @@ const HAS_E_REFERENCING_MISMO_MES = (
|
||||
esLadoE: string,
|
||||
considerarActivos: boolean,
|
||||
considerarNCs: boolean,
|
||||
) => `EXISTS (
|
||||
SELECT 1 FROM cfdis e
|
||||
WHERE e.tipo_comprobante = 'E'
|
||||
AND e.metodo_pago = 'PUE'
|
||||
AND e.status NOT IN ('Cancelado', '0')
|
||||
AND ${esLadoE}
|
||||
AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
|
||||
AND date_trunc('month', e.fecha_emision)
|
||||
= date_trunc('month', cfdis.fecha_emision)${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)}
|
||||
)`;
|
||||
) => {
|
||||
if (!considerarNCs) return 'FALSE';
|
||||
return `EXISTS (
|
||||
SELECT 1 FROM cfdis e
|
||||
WHERE e.tipo_comprobante = 'E'
|
||||
AND e.metodo_pago = 'PUE'
|
||||
AND e.status NOT IN ('Cancelado', '0')
|
||||
AND ${esLadoE}
|
||||
AND e.cfdis_relacionados IS NOT NULL
|
||||
AND string_to_array(LOWER(e.cfdis_relacionados), '|') @> ARRAY[LOWER(cfdis.uuid)]
|
||||
AND date_trunc('month', e.fecha_emision)
|
||||
= date_trunc('month', cfdis.fecha_emision)${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)}
|
||||
)`;
|
||||
};
|
||||
|
||||
// Atribución por lado usando RFC en lugar de `type`. Los buckets son
|
||||
// factories que reciben el context del contribuyente:
|
||||
@@ -397,8 +409,8 @@ export async function getIvaMensual(
|
||||
const añoEnd = `${año}-12-31`;
|
||||
const extra = buildExtraFilters(considerarActivos, considerarNCs);
|
||||
|
||||
const [{ rows: causadoRows }, { rows: acreditableRows }] = await Promise.all([
|
||||
pool.query<{ mes: number; trasladado: string; retencion: string }>(`
|
||||
const { rows: causadoRows } = await withJitOff(pool, (client) =>
|
||||
client.query<{ mes: number; trasladado: string; retencion: string }>(`
|
||||
SELECT EXTRACT(MONTH FROM ${FECHA_EFECTIVA})::int as mes,
|
||||
COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) as trasladado,
|
||||
COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) as retencion
|
||||
@@ -407,8 +419,10 @@ export async function getIvaMensual(
|
||||
AND ${VIGENTE} AND ${FR}${extra}
|
||||
AND (${REGIMEN_TENANT}) = ANY($3)
|
||||
GROUP BY mes
|
||||
`, [añoStart, añoEnd, TODOS_REGIMENES]),
|
||||
pool.query<{ mes: number; trasladado: string; retencion: string }>(`
|
||||
`, [añoStart, añoEnd, TODOS_REGIMENES])
|
||||
);
|
||||
const { rows: acreditableRows } = await withJitOff(pool, (client) =>
|
||||
client.query<{ mes: number; trasladado: string; retencion: string }>(`
|
||||
SELECT EXTRACT(MONTH FROM ${FECHA_EFECTIVA})::int as mes,
|
||||
COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) as trasladado,
|
||||
COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0) as retencion
|
||||
@@ -417,8 +431,8 @@ export async function getIvaMensual(
|
||||
AND ${VIGENTE} AND ${FR}${extra}
|
||||
AND (${REGIMEN_TENANT}) = ANY($3)
|
||||
GROUP BY mes
|
||||
`, [añoStart, añoEnd, TODOS_REGIMENES]),
|
||||
]);
|
||||
`, [añoStart, añoEnd, TODOS_REGIMENES])
|
||||
);
|
||||
|
||||
perMes = new Map();
|
||||
for (const row of causadoRows) {
|
||||
@@ -648,20 +662,22 @@ async function readResumenIvaFromCache(
|
||||
const añoInicio = new Date(fechaInicio + 'T00:00:00').getFullYear();
|
||||
const acumFR = conciliacion ? FECHA_RANGO_CONCILIACION : FECHA_RANGO;
|
||||
const REGIMEN_TENANT = regimenTenantExpr(ctx);
|
||||
const acumRow = (await pool.query(`
|
||||
SELECT
|
||||
COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
(
|
||||
COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0)
|
||||
) as total
|
||||
FROM cfdis
|
||||
WHERE ${VIGENTE}
|
||||
AND (${REGIMEN_TENANT}) = ANY($3)
|
||||
AND ${acumFR}
|
||||
AND (${ctx.esEmisor} OR ${ctx.esReceptor})
|
||||
`, [`${añoInicio}-01-01`, fechaFin, TODOS_REGIMENES])).rows[0];
|
||||
const acumRow = (await withJitOff(pool, (client) =>
|
||||
client.query(`
|
||||
SELECT
|
||||
COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
(
|
||||
COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0)
|
||||
) as total
|
||||
FROM cfdis
|
||||
WHERE ${VIGENTE}
|
||||
AND (${REGIMEN_TENANT}) = ANY($3)
|
||||
AND ${acumFR}
|
||||
AND (${ctx.esEmisor} OR ${ctx.esReceptor})
|
||||
`, [`${añoInicio}-01-01`, fechaFin, TODOS_REGIMENES])
|
||||
)).rows[0];
|
||||
|
||||
// Cache hit retorna 0/empty para los surface IVA No Acreditable. El cache
|
||||
// aún no persiste esos campos — si se hace crítico para BI, agregar columna
|
||||
@@ -698,6 +714,29 @@ async function readResumenIvaFromCache(
|
||||
*
|
||||
* Algebraicamente: T − A − R == dashboard.balance, céntimo por céntimo.
|
||||
*/
|
||||
/**
|
||||
* Ejecuta un callback con un client de pool con JIT desactivado (SET LOCAL jit = off).
|
||||
* Usa una transacción implícita para que el SET LOCAL se restaure automáticamente
|
||||
* al liberar la conexión. Esto evita que PostgreSQL compile JIT para queries con
|
||||
* muchos subplans (correlacionados), lo cual puede tardar >15s en queries con
|
||||
* costo estimado muy alto aunque la ejecución real sea rápida.
|
||||
*/
|
||||
async function withJitOff<T>(pool: Pool, fn: (client: PoolClient) => Promise<T>): Promise<T> {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
await client.query('SET LOCAL jit = off');
|
||||
const result = await fn(client);
|
||||
await client.query('COMMIT');
|
||||
return result;
|
||||
} catch (e) {
|
||||
await client.query('ROLLBACK').catch(() => {});
|
||||
throw e;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
export async function getResumenIva(
|
||||
pool: Pool,
|
||||
fechaInicio: string,
|
||||
@@ -725,10 +764,10 @@ export async function getResumenIva(
|
||||
if (cached) return cached;
|
||||
}
|
||||
|
||||
// Una query por lado (causado / acreditable). Filtro por RFC via
|
||||
// ctx.esEmisor/esReceptor (embedded en buckets/signed exprs).
|
||||
const [{ rows: causadoRows }, { rows: acreditableRows }] = await Promise.all([
|
||||
pool.query<{ regimen: string | null; trasladado: string; retencion: string }>(`
|
||||
// Queries con JIT off: evitan compilación JIT de >15s en queries con muchos
|
||||
// subplans correlacionados (activado por costo estimado >100k).
|
||||
const { rows: causadoRows } = await withJitOff(pool, (client) =>
|
||||
client.query<{ regimen: string | null; trasladado: string; retencion: string }>(`
|
||||
SELECT ${REGIMEN_TENANT} as regimen,
|
||||
COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) as trasladado,
|
||||
COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) as retencion
|
||||
@@ -737,8 +776,10 @@ export async function getResumenIva(
|
||||
AND ${VIGENTE} AND ${FR}${extra}
|
||||
AND (${REGIMEN_TENANT}) = ANY($3)
|
||||
GROUP BY ${REGIMEN_TENANT}
|
||||
`, [fechaInicio, fechaFin, TODOS_REGIMENES]),
|
||||
pool.query<{ regimen: string | null; trasladado: string; retencion: string }>(`
|
||||
`, [fechaInicio, fechaFin, TODOS_REGIMENES])
|
||||
);
|
||||
const { rows: acreditableRows } = await withJitOff(pool, (client) =>
|
||||
client.query<{ regimen: string | null; trasladado: string; retencion: string }>(`
|
||||
SELECT ${REGIMEN_TENANT} as regimen,
|
||||
COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) as trasladado,
|
||||
COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0) as retencion
|
||||
@@ -747,8 +788,8 @@ export async function getResumenIva(
|
||||
AND ${VIGENTE} AND ${FR}${extra}
|
||||
AND (${REGIMEN_TENANT}) = ANY($3)
|
||||
GROUP BY ${REGIMEN_TENANT}
|
||||
`, [fechaInicio, fechaFin, TODOS_REGIMENES]),
|
||||
]);
|
||||
`, [fechaInicio, fechaFin, TODOS_REGIMENES])
|
||||
);
|
||||
|
||||
// Combinar por régimen: el set de régimenes posibles es la unión de ambos lados.
|
||||
type Acc = { trasCausado: number; retCausado: number; trasAcreditable: number; retAcreditable: number };
|
||||
@@ -799,20 +840,22 @@ export async function getResumenIva(
|
||||
// Acumulado anual (misma fórmula T − A − R, pero rango = enero → fechaFin).
|
||||
const añoInicio = new Date(fechaInicio + 'T00:00:00').getFullYear();
|
||||
const acumFR = conciliacion ? FECHA_RANGO_CONCILIACION : FECHA_RANGO;
|
||||
const { rows: [acumRow] } = await pool.query(`
|
||||
SELECT
|
||||
COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
(
|
||||
COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0)
|
||||
) as total
|
||||
FROM cfdis
|
||||
WHERE ${VIGENTE}
|
||||
AND (${REGIMEN_TENANT}) = ANY($3)
|
||||
AND ${acumFR}${extra}
|
||||
AND (${ctx.esEmisor} OR ${ctx.esReceptor})
|
||||
`, [`${añoInicio}-01-01`, fechaFin, TODOS_REGIMENES]);
|
||||
const { rows: [acumRow] } = await withJitOff(pool, (client) =>
|
||||
client.query(`
|
||||
SELECT
|
||||
COALESCE(SUM(${signedCausadoTras(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
COALESCE(SUM(${signedAcreditableTras(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
(
|
||||
COALESCE(SUM(${signedCausadoRet(ctx, considerarActivos, considerarNCs)}), 0) -
|
||||
COALESCE(SUM(${signedAcreditableRet(ctx, considerarActivos, considerarNCs)}), 0)
|
||||
) as total
|
||||
FROM cfdis
|
||||
WHERE ${VIGENTE}
|
||||
AND (${REGIMEN_TENANT}) = ANY($3)
|
||||
AND ${acumFR}${extra}
|
||||
AND (${ctx.esEmisor} OR ${ctx.esReceptor})
|
||||
`, [`${añoInicio}-01-01`, fechaFin, TODOS_REGIMENES])
|
||||
);
|
||||
|
||||
// IVA No Acreditable surface (Art. 5 LIVA fracción I + Art. 27 fracción III LISR).
|
||||
// No participa en `resultado` — ya excluido del `acreditable` arriba via filtro
|
||||
|
||||
Reference in New Issue
Block a user