127 lines
4.6 KiB
TypeScript
127 lines
4.6 KiB
TypeScript
import { prisma } from '../config/database.js';
|
|
import type { KpiData, IngresosEgresosData, ResumenFiscal, Alerta } from '@horux/shared';
|
|
|
|
export async function getKpis(schema: string, año: number, mes: number): Promise<KpiData> {
|
|
const [ingresos] = await prisma.$queryRawUnsafe<[{ total: number }]>(`
|
|
SELECT COALESCE(SUM(total), 0) as total
|
|
FROM "${schema}".cfdis
|
|
WHERE tipo = 'ingreso'
|
|
AND estado = 'vigente'
|
|
AND EXTRACT(YEAR FROM fecha_emision) = $1
|
|
AND EXTRACT(MONTH FROM fecha_emision) = $2
|
|
`, año, mes);
|
|
|
|
const [egresos] = await prisma.$queryRawUnsafe<[{ total: number }]>(`
|
|
SELECT COALESCE(SUM(total), 0) as total
|
|
FROM "${schema}".cfdis
|
|
WHERE tipo = 'egreso'
|
|
AND estado = 'vigente'
|
|
AND EXTRACT(YEAR FROM fecha_emision) = $1
|
|
AND EXTRACT(MONTH FROM fecha_emision) = $2
|
|
`, año, mes);
|
|
|
|
const [ivaData] = await prisma.$queryRawUnsafe<[{ trasladado: number; acreditable: number }]>(`
|
|
SELECT
|
|
COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN iva ELSE 0 END), 0) as trasladado,
|
|
COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN iva ELSE 0 END), 0) as acreditable
|
|
FROM "${schema}".cfdis
|
|
WHERE estado = 'vigente'
|
|
AND EXTRACT(YEAR FROM fecha_emision) = $1
|
|
AND EXTRACT(MONTH FROM fecha_emision) = $2
|
|
`, año, mes);
|
|
|
|
const [counts] = await prisma.$queryRawUnsafe<[{ emitidos: number; recibidos: number }]>(`
|
|
SELECT
|
|
COUNT(CASE WHEN tipo = 'ingreso' THEN 1 END) as emitidos,
|
|
COUNT(CASE WHEN tipo = 'egreso' THEN 1 END) as recibidos
|
|
FROM "${schema}".cfdis
|
|
WHERE estado = 'vigente'
|
|
AND EXTRACT(YEAR FROM fecha_emision) = $1
|
|
AND EXTRACT(MONTH FROM fecha_emision) = $2
|
|
`, año, mes);
|
|
|
|
const ingresosVal = Number(ingresos?.total || 0);
|
|
const egresosVal = Number(egresos?.total || 0);
|
|
const utilidad = ingresosVal - egresosVal;
|
|
const margen = ingresosVal > 0 ? (utilidad / ingresosVal) * 100 : 0;
|
|
const ivaBalance = Number(ivaData?.trasladado || 0) - Number(ivaData?.acreditable || 0);
|
|
|
|
return {
|
|
ingresos: ingresosVal,
|
|
egresos: egresosVal,
|
|
utilidad,
|
|
margen: Math.round(margen * 100) / 100,
|
|
ivaBalance,
|
|
cfdisEmitidos: Number(counts?.emitidos || 0),
|
|
cfdisRecibidos: Number(counts?.recibidos || 0),
|
|
};
|
|
}
|
|
|
|
export async function getIngresosEgresos(schema: string, año: number): Promise<IngresosEgresosData[]> {
|
|
const data = await prisma.$queryRawUnsafe<{ mes: number; ingresos: number; egresos: number }[]>(`
|
|
SELECT
|
|
EXTRACT(MONTH FROM fecha_emision)::int as mes,
|
|
COALESCE(SUM(CASE WHEN tipo = 'ingreso' THEN total ELSE 0 END), 0) as ingresos,
|
|
COALESCE(SUM(CASE WHEN tipo = 'egreso' THEN total ELSE 0 END), 0) as egresos
|
|
FROM "${schema}".cfdis
|
|
WHERE estado = 'vigente'
|
|
AND EXTRACT(YEAR FROM fecha_emision) = $1
|
|
GROUP BY EXTRACT(MONTH FROM fecha_emision)
|
|
ORDER BY mes
|
|
`, año);
|
|
|
|
const meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'];
|
|
|
|
return meses.map((mes, index) => {
|
|
const found = data.find(d => d.mes === index + 1);
|
|
return {
|
|
mes,
|
|
ingresos: Number(found?.ingresos || 0),
|
|
egresos: Number(found?.egresos || 0),
|
|
};
|
|
});
|
|
}
|
|
|
|
export async function getResumenFiscal(schema: string, año: number, mes: number): Promise<ResumenFiscal> {
|
|
const [ivaResult] = await prisma.$queryRawUnsafe<[{ resultado: number; acumulado: number }]>(`
|
|
SELECT resultado, acumulado FROM "${schema}".iva_mensual
|
|
WHERE año = $1 AND mes = $2
|
|
`, año, mes) || [{ resultado: 0, acumulado: 0 }];
|
|
|
|
const [pendientes] = await prisma.$queryRawUnsafe<[{ count: number }]>(`
|
|
SELECT COUNT(*) as count FROM "${schema}".iva_mensual
|
|
WHERE año = $1 AND estado = 'pendiente'
|
|
`, año);
|
|
|
|
const resultado = Number(ivaResult?.resultado || 0);
|
|
const acumulado = Number(ivaResult?.acumulado || 0);
|
|
|
|
return {
|
|
ivaPorPagar: resultado > 0 ? resultado : 0,
|
|
ivaAFavor: acumulado < 0 ? Math.abs(acumulado) : 0,
|
|
isrPorPagar: 0,
|
|
declaracionesPendientes: Number(pendientes?.count || 0),
|
|
proximaObligacion: {
|
|
titulo: 'Declaracion mensual IVA/ISR',
|
|
fecha: new Date(año, mes, 17).toISOString(),
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function getAlertas(schema: string, limit = 5): Promise<Alerta[]> {
|
|
const alertas = await prisma.$queryRawUnsafe<Alerta[]>(`
|
|
SELECT id, tipo, titulo, mensaje, prioridad,
|
|
fecha_vencimiento as "fechaVencimiento",
|
|
leida, resuelta,
|
|
created_at as "createdAt"
|
|
FROM "${schema}".alertas
|
|
WHERE resuelta = false
|
|
ORDER BY
|
|
CASE prioridad WHEN 'alta' THEN 1 WHEN 'media' THEN 2 ELSE 3 END,
|
|
created_at DESC
|
|
LIMIT $1
|
|
`, limit);
|
|
|
|
return alertas;
|
|
}
|