feat(cfdi): descarga masiva de XMLs como ZIP, limite 1,000
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import * as cfdiService from '../services/cfdi.service.js';
|
||||
import { AppError } from '../middlewares/error.middleware.js';
|
||||
import AdmZip from 'adm-zip';
|
||||
import { GRUPO_PF_EMPRESARIAL, GRUPO_PM_OTROS } from '../services/dashboard.service.js';
|
||||
import { getRegimenesIgnoradosClaves } from '../services/regimen.service.js';
|
||||
import { resolveContribuyenteContext } from '../utils/contribuyente-context.js';
|
||||
@@ -75,6 +76,45 @@ export async function getXml(req: Request, res: Response, next: NextFunction) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadXmlsZip(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
if (!req.tenantPool) {
|
||||
return next(new AppError(400, 'Tenant no configurado'));
|
||||
}
|
||||
|
||||
const ids = req.body.ids as number[];
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return next(new AppError(400, 'Se requiere un array de IDs'));
|
||||
}
|
||||
if (ids.length > 1000) {
|
||||
return next(new AppError(400, 'Máximo 1,000 CFDIs por descarga'));
|
||||
}
|
||||
|
||||
const cfdis = await cfdiService.getXmlsByIds(req.tenantPool, ids);
|
||||
const zip = new AdmZip();
|
||||
let added = 0;
|
||||
|
||||
for (const cfdi of cfdis) {
|
||||
if (cfdi.xml) {
|
||||
const filename = `${cfdi.uuid || cfdi.id}.xml`;
|
||||
zip.addFile(filename, Buffer.from(cfdi.xml, 'utf8'));
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
if (added === 0) {
|
||||
return next(new AppError(404, 'No se encontraron XMLs para los CFDIs seleccionados'));
|
||||
}
|
||||
|
||||
const zipBuffer = zip.toBuffer();
|
||||
res.set('Content-Type', 'application/zip');
|
||||
res.set('Content-Disposition', `attachment; filename="cfdis-${Date.now()}.zip"`);
|
||||
res.send(zipBuffer);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function listConceptos(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
if (!req.tenantPool) return next(new AppError(400, 'Tenant no configurado'));
|
||||
|
||||
@@ -23,6 +23,7 @@ router.get('/conceptos', cfdiController.listConceptos);
|
||||
router.get('/:id', cfdiController.getCfdiById);
|
||||
router.get('/:id/conceptos', cfdiController.getConceptos);
|
||||
router.get('/:id/xml', cfdiController.getXml);
|
||||
router.post('/download-xmls', cfdiController.downloadXmlsZip);
|
||||
router.post('/', checkCfdiLimit, cfdiController.createCfdi);
|
||||
// Bulk upload: 10/hora — procesa hasta 50MB, pesado en parseo + inserts
|
||||
router.post('/bulk', strictLimit, express.json({ limit: '50mb' }), checkCfdiLimit, cfdiController.createManyCfdis);
|
||||
|
||||
@@ -357,6 +357,13 @@ export async function getXmlById(pool: Pool, id: string): Promise<string | null>
|
||||
return rows[0]?.xml_original || null;
|
||||
}
|
||||
|
||||
export async function getXmlsByIds(pool: Pool, ids: number[]): Promise<{ id: number; uuid: string; xml: string | null }[]> {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, uuid, xml_original FROM cfdis WHERE id = ANY($1)
|
||||
`, [ids]);
|
||||
return rows.map((r: any) => ({ id: r.id, uuid: r.uuid, xml: r.xml_original || null }));
|
||||
}
|
||||
|
||||
export interface CreateCfdiData {
|
||||
uuid: string;
|
||||
type: 'EMITIDO' | 'RECIBIDO';
|
||||
|
||||
Reference in New Issue
Block a user