feat(sat): factura global + fecha_efectiva, fallback tenant-contribuyente, fix anio_global typo
Factura Global & fecha_efectiva: - Migracion 045_factura_global.sql: periodicidad, meses_global, año_global, fecha_efectiva - sat-parser.service.ts: extrae InformacionGlobal del XML - sat.service.ts: calcFechaEfectiva con soporte bimestral (periodicidad 05) - metricas-compute, dashboard, impuestos, cfdi, export, conciliacion, alertas: reemplaza fecha_emision-1h por COALESCE(fecha_efectiva, fecha_emision-1h) - Script recalc-metricas.ts para recalculo manual Fallback datos fiscales tenant → contribuyente: - contribuyente.service.ts: fetchTenantFiscalData + mergeContribuyenteWithTenant rellena regimenFiscal, codigoPostal y domicilio cuando el contribuyente tiene el mismo RFC que el tenant y sus campos estan vacios - contribuyente.controller.ts y contribuyente-config.controller.ts: pasan req.user!.tenantId al servicio Fix critico SAT sync: - sat.service.ts: anio_global → año_global en INSERT/UPDATE de CFDIs (la migracion creo 'año_global' con tilde; el codigo usaba 'anio_global', causando fallo en 100% de inserciones de CFDI) - determineChunkMonths: salta sondeo si existe job previo con requestIds - MAX_POLL_ATTEMPTS: 45 → 500 (~8h) para syncs iniciales grandes Docs: - docs/sessions/2026-05-22-factura-global-contribuyente-fallback.md
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { Pool } from 'pg';
|
||||
import { prisma } from '../config/database.js';
|
||||
|
||||
export interface CreateContribuyenteData {
|
||||
rfc: string;
|
||||
@@ -23,7 +24,61 @@ export interface ContribuyenteRow {
|
||||
domicilio: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export async function listContribuyentes(pool: Pool, entidadIds?: string[]): Promise<ContribuyenteRow[]> {
|
||||
async function fetchTenantFiscalData(tenantId: string) {
|
||||
const tenant = await prisma.tenant.findUnique({
|
||||
where: { id: tenantId },
|
||||
select: {
|
||||
rfc: true,
|
||||
codigoPostal: true,
|
||||
calle: true,
|
||||
numExterior: true,
|
||||
numInterior: true,
|
||||
colonia: true,
|
||||
ciudad: true,
|
||||
municipio: true,
|
||||
estado: true,
|
||||
telefono: true,
|
||||
},
|
||||
});
|
||||
if (!tenant) return null;
|
||||
|
||||
const regimenes = await prisma.tenantRegimenActivo.findMany({
|
||||
where: { tenantId },
|
||||
select: { regimen: { select: { clave: true } } },
|
||||
});
|
||||
const regimenFiscal = regimenes.map(r => r.regimen.clave).join(',') || null;
|
||||
|
||||
const hasAnyAddress = tenant.calle || tenant.colonia || tenant.ciudad || tenant.municipio || tenant.estado || tenant.codigoPostal;
|
||||
const domicilio = hasAnyAddress
|
||||
? {
|
||||
calle: tenant.calle || '',
|
||||
numExterior: tenant.numExterior || '',
|
||||
numInterior: tenant.numInterior || '',
|
||||
colonia: tenant.colonia || '',
|
||||
ciudad: tenant.ciudad || '',
|
||||
municipio: tenant.municipio || '',
|
||||
estado: tenant.estado || '',
|
||||
codigoPostal: tenant.codigoPostal || '',
|
||||
telefono: tenant.telefono || '',
|
||||
}
|
||||
: null;
|
||||
|
||||
return { tenantRfc: tenant.rfc, regimenFiscal, codigoPostal: tenant.codigoPostal, domicilio };
|
||||
}
|
||||
|
||||
function mergeContribuyenteWithTenant(
|
||||
row: ContribuyenteRow,
|
||||
tenantData: NonNullable<Awaited<ReturnType<typeof fetchTenantFiscalData>>>
|
||||
): ContribuyenteRow {
|
||||
return {
|
||||
...row,
|
||||
regimenFiscal: row.regimenFiscal || tenantData.regimenFiscal,
|
||||
codigoPostal: row.codigoPostal || tenantData.codigoPostal,
|
||||
domicilio: row.domicilio || tenantData.domicilio,
|
||||
};
|
||||
}
|
||||
|
||||
export async function listContribuyentes(pool: Pool, entidadIds?: string[], tenantId?: string): Promise<ContribuyenteRow[]> {
|
||||
let query = `
|
||||
SELECT
|
||||
e.id, e.tipo, e.nombre, e.identificador,
|
||||
@@ -45,10 +100,20 @@ export async function listContribuyentes(pool: Pool, entidadIds?: string[]): Pro
|
||||
|
||||
query += ' ORDER BY e.created_at DESC';
|
||||
const { rows } = await pool.query(query, params);
|
||||
return rows;
|
||||
|
||||
if (!tenantId) return rows;
|
||||
|
||||
const tenantData = await fetchTenantFiscalData(tenantId);
|
||||
if (!tenantData) return rows;
|
||||
|
||||
return rows.map((r: ContribuyenteRow) => {
|
||||
if (r.rfc !== tenantData.tenantRfc) return r;
|
||||
if (r.regimenFiscal && r.codigoPostal && r.domicilio) return r;
|
||||
return mergeContribuyenteWithTenant(r, tenantData);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getContribuyenteById(pool: Pool, id: string): Promise<ContribuyenteRow | null> {
|
||||
export async function getContribuyenteById(pool: Pool, id: string, tenantId?: string): Promise<ContribuyenteRow | null> {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT
|
||||
e.id, e.tipo, e.nombre, e.identificador,
|
||||
@@ -60,7 +125,14 @@ export async function getContribuyenteById(pool: Pool, id: string): Promise<Cont
|
||||
JOIN contribuyentes c ON c.entidad_id = e.id
|
||||
WHERE e.id = $1
|
||||
`, [id]);
|
||||
return rows[0] ?? null;
|
||||
const row = rows[0] ?? null;
|
||||
if (!row || !tenantId) return row;
|
||||
|
||||
const tenantData = await fetchTenantFiscalData(tenantId);
|
||||
if (!tenantData || row.rfc !== tenantData.tenantRfc) return row;
|
||||
if (row.regimenFiscal && row.codigoPostal && row.domicilio) return row;
|
||||
|
||||
return mergeContribuyenteWithTenant(row, tenantData);
|
||||
}
|
||||
|
||||
export async function createContribuyente(pool: Pool, data: CreateContribuyenteData): Promise<ContribuyenteRow> {
|
||||
|
||||
Reference in New Issue
Block a user