Initial commit - Horux Despachos NL
This commit is contained in:
148
apps/api/src/services/_shared/cfdi-filters.ts
Normal file
148
apps/api/src/services/_shared/cfdi-filters.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Helpers para construir fragmentos AND adicionales en WHERE clauses según
|
||||
* los toggles "Considerar activos" y "Considerar NCs" de la UI de impuestos.
|
||||
*
|
||||
* - considerarActivos === false → excluir facturas relacionadas a activos:
|
||||
* 1) I directo con uso_cfdi I01-I08.
|
||||
* 2) P pagando una I-activo (vía uuid_relacionado).
|
||||
* 3) E que referencia una I-activo o una P-de-activo (vía cfdis_relacionados,
|
||||
* cualquier tipoRel — cubre NCs tipoRel=01, devoluciones tipoRel=03, etc.).
|
||||
* 4) Anticipo (I PUE/PPD) que es referenciado por una I/07 PPD con uso_cfdi
|
||||
* de activo (la I/07 "aplica" el anticipo al activo, así que el anticipo
|
||||
* también es para un activo).
|
||||
* - considerarNCs === false → excluir TODAS las facturas tipo E (cualquier
|
||||
* tipo_relacion). Además, los callers que aplican la compensación I/07 PPD
|
||||
* ↔ E (ingresos Grupo 1 y deducciones) deben saltarla cuando este flag es
|
||||
* false (la compensación lee valores de E para compensar; sin E, no aplica).
|
||||
*
|
||||
* Cuando ambos son true (default backend = "include todo"), retorna string
|
||||
* vacío. Esto preserva el comportamiento histórico para callers que no pasan
|
||||
* los flags (ej. dashboard, reportes).
|
||||
*
|
||||
* Las versiones `Alias` se usan en subqueries con alias de tabla
|
||||
* (ej. `cfdis e` en SUM_E_REFERENCING_*). El filtro de NCs aplica directo;
|
||||
* 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).
|
||||
*/
|
||||
|
||||
const ACTIVOS_USOS = "('I01','I02','I03','I04','I05','I06','I07','I08')";
|
||||
|
||||
/**
|
||||
* 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}
|
||||
))
|
||||
)
|
||||
))
|
||||
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
|
||||
-- "aplica" el anticipo a la compra del activo, así que el anticipo
|
||||
-- también debe filtrarse cuando se desactiva "Considerar activos".
|
||||
SELECT 1 FROM cfdis i07_act
|
||||
WHERE i07_act.tipo_comprobante = 'I'
|
||||
AND i07_act.metodo_pago = 'PPD'
|
||||
AND COALESCE(i07_act.cfdi_tipo_relacion, '') = '07'
|
||||
AND i07_act.uso_cfdi IN ${ACTIVOS_USOS}
|
||||
AND i07_act.status NOT IN ('Cancelado', '0')
|
||||
AND i07_act.cfdis_relacionados IS NOT NULL
|
||||
AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(i07_act.cfdis_relacionados), '|'))
|
||||
))
|
||||
`.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Misma lógica que activosExclusionNoAlias pero referenciando columnas con
|
||||
* el alias de tabla externo (ej. 'e' en `FROM cfdis e`).
|
||||
*/
|
||||
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}
|
||||
))
|
||||
)
|
||||
))
|
||||
AND NOT (${alias}.tipo_comprobante = 'I' AND EXISTS (
|
||||
SELECT 1 FROM cfdis i07_act
|
||||
WHERE i07_act.tipo_comprobante = 'I'
|
||||
AND i07_act.metodo_pago = 'PPD'
|
||||
AND COALESCE(i07_act.cfdi_tipo_relacion, '') = '07'
|
||||
AND i07_act.uso_cfdi IN ${ACTIVOS_USOS}
|
||||
AND i07_act.status NOT IN ('Cancelado', '0')
|
||||
AND i07_act.cfdis_relacionados IS NOT NULL
|
||||
AND LOWER(${alias}.uuid) = ANY(string_to_array(LOWER(i07_act.cfdis_relacionados), '|'))
|
||||
))
|
||||
`.trim();
|
||||
}
|
||||
|
||||
export function buildExtraFilters(
|
||||
considerarActivos: boolean,
|
||||
considerarNCs: boolean,
|
||||
): string {
|
||||
const parts: string[] = [];
|
||||
if (!considerarActivos) {
|
||||
parts.push(activosExclusionNoAlias());
|
||||
}
|
||||
if (!considerarNCs) {
|
||||
parts.push(`AND NOT (tipo_comprobante = 'E')`);
|
||||
}
|
||||
return parts.length > 0 ? ' ' + parts.join(' ') : '';
|
||||
}
|
||||
|
||||
export function buildExtraFiltersAlias(
|
||||
alias: string,
|
||||
considerarActivos: boolean,
|
||||
considerarNCs: boolean,
|
||||
): string {
|
||||
const parts: string[] = [];
|
||||
if (!considerarActivos) {
|
||||
parts.push(activosExclusionAlias(alias));
|
||||
}
|
||||
if (!considerarNCs) {
|
||||
parts.push(`AND NOT (${alias}.tipo_comprobante = 'E')`);
|
||||
}
|
||||
return parts.length > 0 ? ' ' + parts.join(' ') : '';
|
||||
}
|
||||
Reference in New Issue
Block a user