Final de creación de Módulos de Reportes. Se procede a testeos y ordenamientos...
This commit is contained in:
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+2273ebb1e018273a6e35d3f9ab0afe55ae1814bc")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+70fc84772161b499c8283a31b7a61246a6bcc46f")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface BalanceCuentaDebCredDto {
|
||||||
|
fecha: string;
|
||||||
|
referencia?: string;
|
||||||
|
debe: number;
|
||||||
|
haber: number;
|
||||||
|
}
|
||||||
10
Frontend/src/models/dtos/Reportes/BalanceCuentaDistDto.tsx
Normal file
10
Frontend/src/models/dtos/Reportes/BalanceCuentaDistDto.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export interface BalanceCuentaDistDto {
|
||||||
|
fecha: string;
|
||||||
|
publicacion: string;
|
||||||
|
distribuidor: string;
|
||||||
|
cantidad: number;
|
||||||
|
remito: string;
|
||||||
|
observacion?: string;
|
||||||
|
debe: number;
|
||||||
|
haber: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface BalanceCuentaPagosDto {
|
||||||
|
fecha: string;
|
||||||
|
recibo: number;
|
||||||
|
tipo: string;
|
||||||
|
debe: number;
|
||||||
|
haber: number;
|
||||||
|
detalle?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface ComparativaConsumoBobinasDto {
|
||||||
|
tipoBobina: string;
|
||||||
|
bobinasUtilizadasMesA: number;
|
||||||
|
bobinasUtilizadasMesB: number;
|
||||||
|
diferenciaBobinasUtilizadas: number;
|
||||||
|
kilosUtilizadosMesA: number;
|
||||||
|
kilosUtilizadosMesB: number;
|
||||||
|
diferenciaKilosUtilizados: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface ConsumoBobinasPublicacionDto {
|
||||||
|
nombrePlanta: string;
|
||||||
|
nombrePublicacion: string;
|
||||||
|
totalKilos: number;
|
||||||
|
cantidadBobinas: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface ConsumoBobinasSeccionDto {
|
||||||
|
nombrePublicacion: string;
|
||||||
|
nombreSeccion: string;
|
||||||
|
nombreBobina: string;
|
||||||
|
cantidadBobinas: number;
|
||||||
|
totalKilos: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface ControlDevolucionesReporteDto {
|
||||||
|
ingresados: number;
|
||||||
|
sobrantes: number;
|
||||||
|
publicacion: string;
|
||||||
|
llevados: number;
|
||||||
|
devueltos: number;
|
||||||
|
tipo: string; // "Accionistas" o "Canillitas"
|
||||||
|
totalNoAccionistas: number;
|
||||||
|
sinCargo: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface DetalleDistribucionCanillaAllDto {
|
||||||
|
publicacion: string;
|
||||||
|
totalCantSalida: number;
|
||||||
|
totalCantEntrada: number;
|
||||||
|
totalRendir: number;
|
||||||
|
tipoVendedor: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface DetalleDistribucionCanillaDto {
|
||||||
|
publicacion: string;
|
||||||
|
canilla: string;
|
||||||
|
totalCantSalida: number;
|
||||||
|
totalCantEntrada: number;
|
||||||
|
totalRendir: number;
|
||||||
|
fecha?: string | null; // Opcional y nullable para adaptarse al backend
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface DevueltosOtrosDiasDto {
|
||||||
|
devueltos: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface ListadoDistribucionCanillasImporteDto {
|
||||||
|
fecha: string; // SP devuelve FORMAT(..., 'dd/MM/yyyy')
|
||||||
|
llevados: number;
|
||||||
|
devueltos: number;
|
||||||
|
vendidos: number;
|
||||||
|
totalRendirPublicacion: number; // decimal en BD
|
||||||
|
totalRendirGeneral: number; // decimal en BD
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface ObtenerCtrlDevolucionesDto {
|
||||||
|
remito: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import type { BalanceCuentaDebCredDto } from "./BalanceCuentaDebCredDto";
|
||||||
|
import type { BalanceCuentaDistDto } from "./BalanceCuentaDistDto";
|
||||||
|
import type { BalanceCuentaPagosDto } from "./BalanceCuentaPagosDto";
|
||||||
|
import type { SaldoDto } from "./SaldoDto";
|
||||||
|
|
||||||
|
export interface ReporteCuentasDistribuidorResponseDto {
|
||||||
|
entradasSalidas: BalanceCuentaDistDto[];
|
||||||
|
debitosCreditos: BalanceCuentaDebCredDto[];
|
||||||
|
pagos: BalanceCuentaPagosDto[];
|
||||||
|
saldos: SaldoDto[]; // Aunque SP_BalanceCuentSaldos devuelve una lista, para un distribuidor/empresa debería ser 1 solo.
|
||||||
|
// Se podría ajustar el DTO o el servicio para devolver solo el primer saldo.
|
||||||
|
nombreDistribuidor?: string; // Para el título del reporte
|
||||||
|
nombreEmpresa?: string; // Para el título del reporte
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { BalanceCuentaDebCredDto } from "./BalanceCuentaDebCredDto";
|
||||||
|
import type { BalanceCuentaDistDto } from "./BalanceCuentaDistDto";
|
||||||
|
import type { BalanceCuentaPagosDto } from "./BalanceCuentaPagosDto";
|
||||||
|
import type { SaldoDto } from "./SaldoDto";
|
||||||
|
|
||||||
|
export interface ReporteCuentasDistribuidorResponseDto {
|
||||||
|
entradasSalidas: BalanceCuentaDistDto[];
|
||||||
|
debitosCreditos: BalanceCuentaDebCredDto[];
|
||||||
|
pagos: BalanceCuentaPagosDto[];
|
||||||
|
saldos: SaldoDto[];
|
||||||
|
nombreDistribuidor?: string;
|
||||||
|
nombreEmpresa?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import type { DetalleDistribucionCanillaDto } from './DetalleDistribucionCanillaDto';
|
||||||
|
import type { DetalleDistribucionCanillaAllDto } from './DetalleDistribucionCanillaAllDto';
|
||||||
|
import type { ObtenerCtrlDevolucionesDto } from './ObtenerCtrlDevolucionesDto';
|
||||||
|
import type { ControlDevolucionesReporteDto } from './ControlDevolucionesReporteDto';
|
||||||
|
import type { DevueltosOtrosDiasDto } from './DevueltosOtrosDiasDto';
|
||||||
|
|
||||||
|
|
||||||
|
export interface ReporteDistribucionCanillasResponseDto {
|
||||||
|
canillas: DetalleDistribucionCanillaDto[];
|
||||||
|
canillasAccionistas: DetalleDistribucionCanillaDto[];
|
||||||
|
canillasTodos: DetalleDistribucionCanillaAllDto[];
|
||||||
|
canillasLiquidadasOtraFecha: DetalleDistribucionCanillaDto[];
|
||||||
|
canillasAccionistasLiquidadasOtraFecha: DetalleDistribucionCanillaDto[];
|
||||||
|
controlDevolucionesRemitos: ObtenerCtrlDevolucionesDto[];
|
||||||
|
controlDevolucionesDetalle: ControlDevolucionesReporteDto[];
|
||||||
|
controlDevolucionesOtrosDias: DevueltosOtrosDiasDto[];
|
||||||
|
}
|
||||||
3
Frontend/src/models/dtos/Reportes/SaldoDto.tsx
Normal file
3
Frontend/src/models/dtos/Reportes/SaldoDto.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface SaldoDto {
|
||||||
|
monto: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface TiradasPublicacionesSeccionesDto {
|
||||||
|
nombreSeccion: string;
|
||||||
|
totalPaginasImpresas: number;
|
||||||
|
cantidadTiradas: number;
|
||||||
|
totalPaginasEjemplares: number;
|
||||||
|
totalEjemplares: number;
|
||||||
|
promedioPaginasPorEjemplar: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface VentaMensualSecretariaElDiaDto {
|
||||||
|
dia: number;
|
||||||
|
cantidadCanillas: number;
|
||||||
|
tirajes: number;
|
||||||
|
ventas: number;
|
||||||
|
accionistas: number;
|
||||||
|
totalCooperativa: number;
|
||||||
|
totalGeneral: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface VentaMensualSecretariaElPlataDto {
|
||||||
|
dia: number;
|
||||||
|
tiradaCoop: number;
|
||||||
|
devolucionCoop: number;
|
||||||
|
ventaCoop: number;
|
||||||
|
tiradaCan: number;
|
||||||
|
ventaCan: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export interface VentaMensualSecretariaTirDevoDto {
|
||||||
|
dia: number;
|
||||||
|
tiradaCoop: number;
|
||||||
|
devolucionCoop: number;
|
||||||
|
ventaCoop: number;
|
||||||
|
ventaCan: number;
|
||||||
|
tiradaPopular: number;
|
||||||
|
devolucionPopular: number;
|
||||||
|
ventaPopular: number;
|
||||||
|
tiradaClarin: number;
|
||||||
|
devolucionClarin: number;
|
||||||
|
ventaClarin: number;
|
||||||
|
tiradaNacion: number;
|
||||||
|
devolucionNacion: number;
|
||||||
|
ventaNacion: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody,
|
||||||
|
TableFooter
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { ComparativaConsumoBobinasDto } from '../../models/dtos/Reportes/ComparativaConsumoBobinasDto';
|
||||||
|
import SeleccionaReporteComparativaConsumoBobinas from './SeleccionaReporteComparativaConsumoBobinas';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ReporteComparativaConsumoBobinasPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<ComparativaConsumoBobinasDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPdf, setLoadingPdf] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [apiErrorParams, setApiErrorParams] = useState<string | null>(null);
|
||||||
|
const [showParamSelector, setShowParamSelector] = useState(true);
|
||||||
|
const [currentParams, setCurrentParams] = useState<{
|
||||||
|
fechaInicioMesA: string; fechaFinMesA: string;
|
||||||
|
fechaInicioMesB: string; fechaFinMesB: string;
|
||||||
|
idPlanta?: number | null; consolidado: boolean;
|
||||||
|
nombrePlanta?: string; // Para el PDF
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
fechaInicioMesA: string; fechaFinMesA: string;
|
||||||
|
fechaInicioMesB: string; fechaFinMesB: string;
|
||||||
|
idPlanta?: number | null; consolidado: boolean;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setReportData([]);
|
||||||
|
|
||||||
|
let plantaNombre = "Consolidado";
|
||||||
|
if (!params.consolidado && params.idPlanta) {
|
||||||
|
const plantaService = (await import('../../services/Impresion/plantaService')).default;
|
||||||
|
const plantaData = await plantaService.getPlantaById(params.idPlanta);
|
||||||
|
plantaNombre = plantaData?.nombre ?? "N/A";
|
||||||
|
}
|
||||||
|
setCurrentParams({...params, nombrePlanta: plantaNombre});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getComparativaConsumoBobinas(params);
|
||||||
|
setReportData(data);
|
||||||
|
if (data.length === 0) {
|
||||||
|
setError("No se encontraron datos para los parámetros seleccionados.");
|
||||||
|
}
|
||||||
|
setShowParamSelector(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||||
|
? err.response.data.message
|
||||||
|
: 'Ocurrió un error al generar el reporte.';
|
||||||
|
setApiErrorParams(message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleVolverAParametros = useCallback(() => {
|
||||||
|
setShowParamSelector(true);
|
||||||
|
setReportData([]);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExportToExcel = useCallback(() => {
|
||||||
|
if (reportData.length === 0) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dataToExport = reportData.map(item => ({
|
||||||
|
"Tipo Bobina": item.tipoBobina,
|
||||||
|
"Cant. Mes A": item.bobinasUtilizadasMesA,
|
||||||
|
"Cant. Mes B": item.bobinasUtilizadasMesB,
|
||||||
|
"Dif. Cant.": item.diferenciaBobinasUtilizadas,
|
||||||
|
"Kg Mes A": item.kilosUtilizadosMesA,
|
||||||
|
"Kg Mes B": item.kilosUtilizadosMesB,
|
||||||
|
"Dif. Kg": item.diferenciaKilosUtilizados,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Totales
|
||||||
|
const totales = dataToExport.reduce((acc, row) => {
|
||||||
|
acc.cantA += Number(row["Cant. Mes A"]); acc.cantB += Number(row["Cant. Mes B"]);
|
||||||
|
acc.difCant += Number(row["Dif. Cant."]); acc.kgA += Number(row["Kg Mes A"]);
|
||||||
|
acc.kgB += Number(row["Kg Mes B"]); acc.difKg += Number(row["Dif. Kg"]);
|
||||||
|
return acc;
|
||||||
|
}, { cantA:0, cantB:0, difCant:0, kgA:0, kgB:0, difKg:0 });
|
||||||
|
|
||||||
|
dataToExport.push({
|
||||||
|
"Tipo Bobina": "TOTALES", "Cant. Mes A": totales.cantA, "Cant. Mes B": totales.cantB,
|
||||||
|
"Dif. Cant.": totales.difCant, "Kg Mes A": totales.kgA, "Kg Mes B": totales.kgB,
|
||||||
|
"Dif. Kg": totales.difKg,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet(dataToExport);
|
||||||
|
const headers = Object.keys(dataToExport[0] || {});
|
||||||
|
ws['!cols'] = headers.map(h => {
|
||||||
|
const maxLen = Math.max(...dataToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length);
|
||||||
|
return { wch: maxLen + 2 };
|
||||||
|
});
|
||||||
|
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
|
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, "ComparativaConsumo");
|
||||||
|
let fileName = "ReporteComparativaConsumoBobinas";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.consolidado ? 'Consolidado' : `Planta${currentParams.idPlanta}`}`;
|
||||||
|
fileName += `_${currentParams.fechaInicioMesA}_vs_${currentParams.fechaInicioMesB}`;
|
||||||
|
}
|
||||||
|
fileName += ".xlsx";
|
||||||
|
XLSX.writeFile(wb, fileName);
|
||||||
|
}, [reportData, currentParams]);
|
||||||
|
|
||||||
|
const handleGenerarYAbrirPdf = useCallback(async () => {
|
||||||
|
if (!currentParams) {
|
||||||
|
setError("Primero debe generar el reporte en pantalla o seleccionar parámetros.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingPdf(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const blob = await reportesService.getComparativaConsumoBobinasPdf(currentParams);
|
||||||
|
if (blob.type === "application/json") {
|
||||||
|
const text = await blob.text();
|
||||||
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF.";
|
||||||
|
setError(msg);
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const w = window.open(url, '_blank');
|
||||||
|
if (!w) alert("Permite popups para ver el PDF.");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError('Ocurrió un error al generar el PDF.');
|
||||||
|
} finally {
|
||||||
|
setLoadingPdf(false);
|
||||||
|
}
|
||||||
|
}, [currentParams]);
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteComparativaConsumoBobinas
|
||||||
|
onGenerarReporte={handleGenerarReporte}
|
||||||
|
onCancel={handleVolverAParametros}
|
||||||
|
isLoading={loading}
|
||||||
|
apiErrorMessage={apiErrorParams}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
<Typography variant="h5">Reporte: Comparativa Consumo de Bobinas</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
|
||||||
|
Exportar a Excel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
|
||||||
|
Nuevos Parámetros
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
|
||||||
|
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
|
{!loading && !error && reportData.length > 0 && (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Tipo Bobina</TableCell>
|
||||||
|
<TableCell align="right">Cant. Mes A</TableCell>
|
||||||
|
<TableCell align="right">Cant. Mes B</TableCell>
|
||||||
|
<TableCell align="right">Dif. Cant.</TableCell>
|
||||||
|
<TableCell align="right" sx={{ borderLeft: '2px solid grey' }}>Kg Mes A</TableCell>
|
||||||
|
<TableCell align="right">Kg Mes B</TableCell>
|
||||||
|
<TableCell align="right">Dif. Kg</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.map((row, idx) => (
|
||||||
|
<TableRow key={`${row.tipoBobina}-${idx}`}>
|
||||||
|
<TableCell>{row.tipoBobina}</TableCell>
|
||||||
|
<TableCell align="right">{row.bobinasUtilizadasMesA.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.bobinasUtilizadasMesB.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.diferenciaBobinasUtilizadas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{ borderLeft: '2px solid grey' }}>{row.kilosUtilizadosMesA.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.kilosUtilizadosMesB.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.diferenciaKilosUtilizados.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow sx={{backgroundColor: 'grey.300'}}>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>TOTALES:</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesA, 0).toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesB, 0).toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.diferenciaBobinasUtilizadas, 0).toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem', borderLeft: '2px solid grey'}}>{reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesA, 0).toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesB, 0).toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.diferenciaKilosUtilizados, 0).toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteComparativaConsumoBobinasPage;
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody, TableFooter
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { ConsumoBobinasPublicacionDto } from '../../models/dtos/Reportes/ConsumoBobinasPublicacionDto';
|
||||||
|
import SeleccionaReporteConsumoBobinasPublicacion from './SeleccionaReporteConsumoBobinasPublicacion';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// Helper para agrupar los datos por Planta
|
||||||
|
const groupDataByPlanta = (data: ConsumoBobinasPublicacionDto[]) => {
|
||||||
|
return data.reduce((acc, current) => {
|
||||||
|
const plantaKey = current.nombrePlanta;
|
||||||
|
if (!acc[plantaKey]) {
|
||||||
|
acc[plantaKey] = [];
|
||||||
|
}
|
||||||
|
acc[plantaKey].push(current);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, ConsumoBobinasPublicacionDto[]>);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReporteConsumoBobinasPublicacionPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<ConsumoBobinasPublicacionDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPdf, setLoadingPdf] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [apiErrorParams, setApiErrorParams] = useState<string | null>(null);
|
||||||
|
const [showParamSelector, setShowParamSelector] = useState(true);
|
||||||
|
const [currentParams, setCurrentParams] = useState<{
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(params);
|
||||||
|
setReportData([]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getConsumoBobinasPorPublicacion(params);
|
||||||
|
setReportData(data);
|
||||||
|
if (data.length === 0) {
|
||||||
|
setError("No se encontraron datos para los parámetros seleccionados.");
|
||||||
|
}
|
||||||
|
setShowParamSelector(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||||
|
? err.response.data.message
|
||||||
|
: 'Ocurrió un error al generar el reporte.';
|
||||||
|
setApiErrorParams(message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleVolverAParametros = useCallback(() => {
|
||||||
|
setShowParamSelector(true);
|
||||||
|
setReportData([]);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExportToExcel = useCallback(() => {
|
||||||
|
if (reportData.length === 0) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dataToExport = reportData.map(item => ({
|
||||||
|
"Planta": item.nombrePlanta,
|
||||||
|
"Publicación": item.nombrePublicacion,
|
||||||
|
"Total Kilos": item.totalKilos,
|
||||||
|
"Cantidad Bobinas": item.cantidadBobinas,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Calcular totales generales
|
||||||
|
const totalGeneralKilos = reportData.reduce((sum, item) => sum + item.totalKilos, 0);
|
||||||
|
const totalGeneralBobinas = reportData.reduce((sum, item) => sum + item.cantidadBobinas, 0);
|
||||||
|
|
||||||
|
dataToExport.push({
|
||||||
|
"Planta": "TOTAL GENERAL",
|
||||||
|
"Publicación": "",
|
||||||
|
"Total Kilos": totalGeneralKilos,
|
||||||
|
"Cantidad Bobinas": totalGeneralBobinas,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet(dataToExport);
|
||||||
|
const headers = Object.keys(dataToExport[0] || {});
|
||||||
|
ws['!cols'] = headers.map(h => {
|
||||||
|
const maxLen = Math.max(...dataToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length);
|
||||||
|
return { wch: maxLen + 2 };
|
||||||
|
});
|
||||||
|
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
|
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, "ConsumoBobinasPublicacion");
|
||||||
|
let fileName = "ReporteConsumoBobinasPublicacion";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`;
|
||||||
|
}
|
||||||
|
fileName += ".xlsx";
|
||||||
|
XLSX.writeFile(wb, fileName);
|
||||||
|
}, [reportData, currentParams]);
|
||||||
|
|
||||||
|
const handleGenerarYAbrirPdf = useCallback(async () => {
|
||||||
|
if (!currentParams) {
|
||||||
|
setError("Primero debe generar el reporte en pantalla o seleccionar parámetros.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingPdf(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const blob = await reportesService.getConsumoBobinasPorPublicacionPdf(currentParams);
|
||||||
|
if (blob.type === "application/json") {
|
||||||
|
const text = await blob.text();
|
||||||
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF.";
|
||||||
|
setError(msg);
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const w = window.open(url, '_blank');
|
||||||
|
if (!w) alert("Permite popups para ver el PDF.");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError('Ocurrió un error al generar el PDF.');
|
||||||
|
} finally {
|
||||||
|
setLoadingPdf(false);
|
||||||
|
}
|
||||||
|
}, [currentParams]);
|
||||||
|
|
||||||
|
const groupedData = reportData.length > 0 ? groupDataByPlanta(reportData) : {};
|
||||||
|
const totalGeneralKilos = reportData.reduce((sum, item) => sum + item.totalKilos, 0);
|
||||||
|
const totalGeneralBobinas = reportData.reduce((sum, item) => sum + item.cantidadBobinas, 0);
|
||||||
|
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteConsumoBobinasPublicacion
|
||||||
|
onGenerarReporte={handleGenerarReporte}
|
||||||
|
onCancel={handleVolverAParametros}
|
||||||
|
isLoading={loading}
|
||||||
|
apiErrorMessage={apiErrorParams}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
<Typography variant="h5">Reporte: Consumo de Bobinas por Publicación</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
|
||||||
|
Exportar a Excel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
|
||||||
|
Nuevos Parámetros
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
|
||||||
|
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
|
{!loading && !error && reportData.length > 0 && (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>Planta</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>Publicación</TableCell>
|
||||||
|
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Total Kilos</TableCell>
|
||||||
|
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Cant. Bobinas</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{Object.entries(groupedData).map(([plantaKey, publicaciones]) => {
|
||||||
|
const totalKilosPlanta = publicaciones.reduce((sum, item) => sum + item.totalKilos, 0);
|
||||||
|
const totalBobinasPlanta = publicaciones.reduce((sum, item) => sum + item.cantidadBobinas, 0);
|
||||||
|
return (
|
||||||
|
<React.Fragment key={plantaKey}>
|
||||||
|
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.08)' }}>
|
||||||
|
<TableCell colSpan={4} sx={{ fontWeight: 'bold' }}>{plantaKey}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{publicaciones.map((pub, pubIdx) => (
|
||||||
|
<TableRow key={`${plantaKey}-${pubIdx}`}>
|
||||||
|
<TableCell></TableCell> {/* Columna Planta vacía para esta fila */}
|
||||||
|
<TableCell>{pub.nombrePublicacion}</TableCell>
|
||||||
|
<TableCell align="right">{pub.totalKilos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{pub.cantidadBobinas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
{/* Fila de totales por planta */}
|
||||||
|
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.04)'}}>
|
||||||
|
<TableCell colSpan={2} align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>Total Planta ({plantaKey}):</TableCell>
|
||||||
|
<TableCell align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>{totalKilosPlanta.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>{totalBobinasPlanta.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</React.Fragment>
|
||||||
|
)})}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow sx={{backgroundColor: 'grey.300'}}>
|
||||||
|
<TableCell colSpan={2} align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>TOTAL GENERAL:</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{totalGeneralKilos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{totalGeneralBobinas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteConsumoBobinasPublicacionPage;
|
||||||
261
Frontend/src/pages/Reportes/ReporteConsumoBobinasSeccionPage.tsx
Normal file
261
Frontend/src/pages/Reportes/ReporteConsumoBobinasSeccionPage.tsx
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody, TableFooter
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { ConsumoBobinasSeccionDto } from '../../models/dtos/Reportes/ConsumoBobinasSeccionDto';
|
||||||
|
import SeleccionaReporteConsumoBobinasSeccion from './SeleccionaReporteConsumoBobinasSeccion';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// Helper para agrupar los datos
|
||||||
|
const groupData = (data: ConsumoBobinasSeccionDto[]) => {
|
||||||
|
return data.reduce((acc, current) => {
|
||||||
|
const pubKey = current.nombrePublicacion;
|
||||||
|
if (!acc[pubKey]) {
|
||||||
|
acc[pubKey] = {};
|
||||||
|
}
|
||||||
|
const secKey = current.nombreSeccion;
|
||||||
|
if (!acc[pubKey][secKey]) {
|
||||||
|
acc[pubKey][secKey] = [];
|
||||||
|
}
|
||||||
|
acc[pubKey][secKey].push(current);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, Record<string, ConsumoBobinasSeccionDto[]>>);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const ReporteConsumoBobinasSeccionPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<ConsumoBobinasSeccionDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPdf, setLoadingPdf] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [apiErrorParams, setApiErrorParams] = useState<string | null>(null);
|
||||||
|
const [showParamSelector, setShowParamSelector] = useState(true);
|
||||||
|
const [currentParams, setCurrentParams] = useState<{
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
nombrePlanta?: string; // Para el PDF
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setReportData([]);
|
||||||
|
|
||||||
|
let plantaNombre = "Consolidado";
|
||||||
|
if (!params.consolidado && params.idPlanta) {
|
||||||
|
const plantaService = (await import('../../services/Impresion/plantaService')).default;
|
||||||
|
const plantaData = await plantaService.getPlantaById(params.idPlanta);
|
||||||
|
plantaNombre = plantaData?.nombre ?? "N/A";
|
||||||
|
}
|
||||||
|
setCurrentParams({...params, nombrePlanta: plantaNombre});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getConsumoBobinasSeccion(params);
|
||||||
|
setReportData(data);
|
||||||
|
if (data.length === 0) {
|
||||||
|
setError("No se encontraron datos para los parámetros seleccionados.");
|
||||||
|
}
|
||||||
|
setShowParamSelector(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||||
|
? err.response.data.message
|
||||||
|
: 'Ocurrió un error al generar el reporte.';
|
||||||
|
setApiErrorParams(message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleVolverAParametros = useCallback(() => {
|
||||||
|
setShowParamSelector(true);
|
||||||
|
setReportData([]);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExportToExcel = useCallback(() => {
|
||||||
|
if (reportData.length === 0) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Mapeo plano para Excel, ya que el agrupamiento es más para visualización.
|
||||||
|
const dataToExport = reportData.map(item => ({
|
||||||
|
"Publicación": item.nombrePublicacion,
|
||||||
|
"Sección": item.nombreSeccion,
|
||||||
|
"Tipo Bobina": item.nombreBobina,
|
||||||
|
"Cantidad Bobinas": item.cantidadBobinas,
|
||||||
|
"Total Kilos": item.totalKilos,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Calcular totales generales
|
||||||
|
const totalGeneralBobinas = reportData.reduce((sum, item) => sum + item.cantidadBobinas, 0);
|
||||||
|
const totalGeneralKilos = reportData.reduce((sum, item) => sum + item.totalKilos, 0);
|
||||||
|
|
||||||
|
dataToExport.push({ // Fila de totales
|
||||||
|
"Publicación": "TOTAL GENERAL",
|
||||||
|
"Sección": "",
|
||||||
|
"Tipo Bobina": "",
|
||||||
|
"Cantidad Bobinas": totalGeneralBobinas,
|
||||||
|
"Total Kilos": totalGeneralKilos,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet(dataToExport);
|
||||||
|
const headers = Object.keys(dataToExport[0] || {});
|
||||||
|
ws['!cols'] = headers.map(h => {
|
||||||
|
const maxLen = Math.max(...dataToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length);
|
||||||
|
return { wch: maxLen + 2 };
|
||||||
|
});
|
||||||
|
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
|
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, "ConsumoBobinasSeccion");
|
||||||
|
let fileName = "ReporteConsumoBobinasSeccion";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.consolidado ? 'Consolidado' : `Planta${currentParams.idPlanta}`}`;
|
||||||
|
fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`;
|
||||||
|
}
|
||||||
|
fileName += ".xlsx";
|
||||||
|
XLSX.writeFile(wb, fileName);
|
||||||
|
}, [reportData, currentParams]);
|
||||||
|
|
||||||
|
const handleGenerarYAbrirPdf = useCallback(async () => {
|
||||||
|
if (!currentParams) {
|
||||||
|
setError("Primero debe generar el reporte en pantalla o seleccionar parámetros.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingPdf(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const blob = await reportesService.getConsumoBobinasSeccionPdf(currentParams);
|
||||||
|
if (blob.type === "application/json") {
|
||||||
|
const text = await blob.text();
|
||||||
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF.";
|
||||||
|
setError(msg);
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const w = window.open(url, '_blank');
|
||||||
|
if (!w) alert("Permite popups para ver el PDF.");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError('Ocurrió un error al generar el PDF.');
|
||||||
|
} finally {
|
||||||
|
setLoadingPdf(false);
|
||||||
|
}
|
||||||
|
}, [currentParams]);
|
||||||
|
|
||||||
|
const groupedData = reportData.length > 0 ? groupData(reportData) : {};
|
||||||
|
const totalGeneralBobinas = reportData.reduce((sum, item) => sum + item.cantidadBobinas, 0);
|
||||||
|
const totalGeneralKilos = reportData.reduce((sum, item) => sum + item.totalKilos, 0);
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteConsumoBobinasSeccion
|
||||||
|
onGenerarReporte={handleGenerarReporte}
|
||||||
|
onCancel={handleVolverAParametros}
|
||||||
|
isLoading={loading}
|
||||||
|
apiErrorMessage={apiErrorParams}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
<Typography variant="h5">Reporte: Consumo de Bobinas por Sección</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
|
||||||
|
Exportar a Excel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
|
||||||
|
Nuevos Parámetros
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
|
||||||
|
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
|
{!loading && !error && reportData.length > 0 && (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>Publicación</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>Sección</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 'bold' }}>Tipo Bobina</TableCell>
|
||||||
|
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Cant. Bobinas</TableCell>
|
||||||
|
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Total Kilos</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{Object.entries(groupedData).map(([pubKey, secciones]) => (
|
||||||
|
<React.Fragment key={pubKey}>
|
||||||
|
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.08)' }}>
|
||||||
|
<TableCell colSpan={5} sx={{ fontWeight: 'bold' }}>{pubKey}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{Object.entries(secciones).map(([secKey, bobinas]) => {
|
||||||
|
const totalBobinasSeccion = bobinas.reduce((sum, item) => sum + item.cantidadBobinas, 0);
|
||||||
|
const totalKilosSeccion = bobinas.reduce((sum, item) => sum + item.totalKilos, 0);
|
||||||
|
return (
|
||||||
|
<React.Fragment key={`${pubKey}-${secKey}`}>
|
||||||
|
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.04)' }}>
|
||||||
|
<TableCell></TableCell> {/* Columna Publicación vacía para esta fila */}
|
||||||
|
<TableCell colSpan={4} sx={{ fontWeight: 'bold' }}>{secKey}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{bobinas.map((bob, bobIdx) => (
|
||||||
|
<TableRow key={`${pubKey}-${secKey}-${bobIdx}`}>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
<TableCell>{bob.nombreBobina}</TableCell>
|
||||||
|
<TableCell align="right">{bob.cantidadBobinas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{bob.totalKilos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
{/* Fila de totales por sección */}
|
||||||
|
<TableRow sx={{ backgroundColor: 'rgba(0, 0, 0, 0.02)'}}>
|
||||||
|
<TableCell colSpan={3} align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>Total Sección ({secKey}):</TableCell>
|
||||||
|
<TableCell align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>{totalBobinasSeccion.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{ fontWeight: 'bold', fontStyle:'italic' }}>{totalKilosSeccion.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow sx={{backgroundColor: 'grey.300'}}>
|
||||||
|
<TableCell colSpan={3} align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>TOTAL GENERAL:</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{totalGeneralBobinas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{totalGeneralKilos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteConsumoBobinasSeccionPage;
|
||||||
294
Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx
Normal file
294
Frontend/src/pages/Reportes/ReporteCuentasDistribuidoresPage.tsx
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { ReporteCuentasDistribuidorResponseDto } from '../../models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto';
|
||||||
|
import type { BalanceCuentaDistDto } from '../../models/dtos/Reportes/BalanceCuentaDistDto';
|
||||||
|
import type { BalanceCuentaDebCredDto } from '../../models/dtos/Reportes/BalanceCuentaDebCredDto';
|
||||||
|
import type { BalanceCuentaPagosDto } from '../../models/dtos/Reportes/BalanceCuentaPagosDto';
|
||||||
|
import SeleccionaReporteCuentasDistribuidores from './SeleccionaReporteCuentasDistribuidores';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ReporteCuentasDistribuidoresPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<ReporteCuentasDistribuidorResponseDto | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPdf, setLoadingPdf] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [apiErrorParams, setApiErrorParams] = useState<string | null>(null);
|
||||||
|
const [showParamSelector, setShowParamSelector] = useState(true);
|
||||||
|
const [currentParams, setCurrentParams] = useState<{
|
||||||
|
idDistribuidor: number;
|
||||||
|
idEmpresa: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
nombreDistribuidor?: string; // Para el PDF y nombre de archivo
|
||||||
|
nombreEmpresa?: string; // Para el PDF y nombre de archivo
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
idDistribuidor: number;
|
||||||
|
idEmpresa: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setReportData(null);
|
||||||
|
|
||||||
|
// Obtener nombres para el PDF/Excel
|
||||||
|
const distService = (await import('../../services/Distribucion/distribuidorService')).default;
|
||||||
|
const distData = await distService.getDistribuidorById(params.idDistribuidor);
|
||||||
|
const empService = (await import('../../services/Distribucion/empresaService')).default;
|
||||||
|
const empData = await empService.getEmpresaById(params.idEmpresa);
|
||||||
|
|
||||||
|
setCurrentParams({
|
||||||
|
...params,
|
||||||
|
nombreDistribuidor: distData?.nombre,
|
||||||
|
nombreEmpresa: empData?.nombre
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getReporteCuentasDistribuidor(params);
|
||||||
|
setReportData(data);
|
||||||
|
const noData = (!data.entradasSalidas?.length && !data.debitosCreditos?.length && !data.pagos?.length && !data.saldos?.length);
|
||||||
|
if (noData) {
|
||||||
|
setError("No se encontraron datos para los parámetros seleccionados.");
|
||||||
|
}
|
||||||
|
setShowParamSelector(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||||
|
? err.response.data.message
|
||||||
|
: 'Ocurrió un error al generar el reporte.';
|
||||||
|
setApiErrorParams(message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleVolverAParametros = useCallback(() => {
|
||||||
|
setShowParamSelector(true);
|
||||||
|
setReportData(null);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExportToExcel = useCallback(() => {
|
||||||
|
if (!reportData) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
if (reportData.saldos?.length) {
|
||||||
|
const saldosToExport = reportData.saldos.map(item => ({ "Saldo Actual": item.monto }));
|
||||||
|
const wsSaldos = XLSX.utils.json_to_sheet(saldosToExport);
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsSaldos, "SaldoActual");
|
||||||
|
}
|
||||||
|
if (reportData.entradasSalidas?.length) {
|
||||||
|
const esToExport = reportData.entradasSalidas.map(item => ({
|
||||||
|
"Fecha": new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }),
|
||||||
|
"Publicación": item.publicacion, "Remito": item.remito, "Cantidad": item.cantidad,
|
||||||
|
"Observación": item.observacion, "Debe": item.debe, "Haber": item.haber,
|
||||||
|
}));
|
||||||
|
const wsES = XLSX.utils.json_to_sheet(esToExport);
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsES, "EntradasSalidas");
|
||||||
|
}
|
||||||
|
if (reportData.debitosCreditos?.length) {
|
||||||
|
const dcToExport = reportData.debitosCreditos.map(item => ({
|
||||||
|
"Fecha": new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }),
|
||||||
|
"Referencia": item.referencia, "Debe": item.debe, "Haber": item.haber,
|
||||||
|
}));
|
||||||
|
const wsDC = XLSX.utils.json_to_sheet(dcToExport);
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsDC, "DebitosCreditos");
|
||||||
|
}
|
||||||
|
if (reportData.pagos?.length) {
|
||||||
|
const paToExport = reportData.pagos.map(item => ({
|
||||||
|
"Fecha": new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }),
|
||||||
|
"Recibo": item.recibo, "Tipo": item.tipo, "Debe": item.debe, "Haber": item.haber,
|
||||||
|
"Detalle": item.detalle,
|
||||||
|
}));
|
||||||
|
const wsPA = XLSX.utils.json_to_sheet(paToExport);
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsPA, "Pagos");
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = "ReporteCuentaDistribuidor";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.nombreDistribuidor?.replace(/\s+/g, '') ?? `Dist${currentParams.idDistribuidor}`}`;
|
||||||
|
fileName += `_Emp${currentParams.nombreEmpresa?.replace(/\s+/g, '') ?? currentParams.idEmpresa}`;
|
||||||
|
fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`;
|
||||||
|
}
|
||||||
|
fileName += ".xlsx";
|
||||||
|
XLSX.writeFile(wb, fileName);
|
||||||
|
}, [reportData, currentParams]);
|
||||||
|
|
||||||
|
const handleGenerarYAbrirPdf = useCallback(async () => {
|
||||||
|
if (!currentParams) {
|
||||||
|
setError("Primero debe generar el reporte en pantalla o seleccionar parámetros.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingPdf(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const blob = await reportesService.getReporteCuentasDistribuidorPdf(currentParams);
|
||||||
|
if (blob.type === "application/json") {
|
||||||
|
const text = await blob.text();
|
||||||
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF.";
|
||||||
|
setError(msg);
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const w = window.open(url, '_blank');
|
||||||
|
if (!w) alert("Permite popups para ver el PDF.");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError('Ocurrió un error al generar el PDF.');
|
||||||
|
} finally {
|
||||||
|
setLoadingPdf(false);
|
||||||
|
}
|
||||||
|
}, [currentParams]);
|
||||||
|
|
||||||
|
// --- Funciones para renderizar las tablas ---
|
||||||
|
const renderEntradasSalidasTable = (data: BalanceCuentaDistDto[]) => (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 2 }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Fecha</TableCell><TableCell>Publicación</TableCell>
|
||||||
|
<TableCell>Remito</TableCell><TableCell align="right">Cantidad</TableCell>
|
||||||
|
<TableCell>Observación</TableCell>
|
||||||
|
<TableCell align="right">Debe</TableCell><TableCell align="right">Haber</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((row, idx) => (
|
||||||
|
<TableRow key={`es-${idx}`}>
|
||||||
|
<TableCell>{new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' })}</TableCell>
|
||||||
|
<TableCell>{row.publicacion}</TableCell><TableCell>{row.remito}</TableCell>
|
||||||
|
<TableCell align="right">{row.cantidad.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell>{row.observacion}</TableCell>
|
||||||
|
<TableCell align="right">{row.debe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
|
||||||
|
<TableCell align="right">{row.haber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderDebitosCreditosTable = (data: BalanceCuentaDebCredDto[]) => (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 2 }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Fecha</TableCell><TableCell>Referencia</TableCell>
|
||||||
|
<TableCell align="right">Debe</TableCell><TableCell align="right">Haber</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((row, idx) => (
|
||||||
|
<TableRow key={`dc-${idx}`}>
|
||||||
|
<TableCell>{new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' })}</TableCell>
|
||||||
|
<TableCell>{row.referencia}</TableCell>
|
||||||
|
<TableCell align="right">{row.debe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
|
||||||
|
<TableCell align="right">{row.haber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderPagosTable = (data: BalanceCuentaPagosDto[]) => (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 2 }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Fecha</TableCell><TableCell>Recibo</TableCell><TableCell>Tipo</TableCell>
|
||||||
|
<TableCell align="right">Debe</TableCell><TableCell align="right">Haber</TableCell>
|
||||||
|
<TableCell>Detalle</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((row, idx) => (
|
||||||
|
<TableRow key={`pa-${idx}`}>
|
||||||
|
<TableCell>{new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' })}</TableCell>
|
||||||
|
<TableCell>{row.recibo}</TableCell><TableCell>{row.tipo}</TableCell>
|
||||||
|
<TableCell align="right">{row.debe.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
|
||||||
|
<TableCell align="right">{row.haber.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
|
||||||
|
<TableCell>{row.detalle}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteCuentasDistribuidores
|
||||||
|
onGenerarReporte={handleGenerarReporte}
|
||||||
|
onCancel={handleVolverAParametros}
|
||||||
|
isLoading={loading}
|
||||||
|
apiErrorMessage={apiErrorParams}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
<Typography variant="h5">Reporte: Cuenta Corriente Distribuidor</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || !reportData || !!error} size="small">
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={!reportData || !!error} size="small">
|
||||||
|
Exportar a Excel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
|
||||||
|
Nuevos Parámetros
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
|
||||||
|
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
|
{!loading && !error && reportData && (
|
||||||
|
<>
|
||||||
|
<Typography variant="h6" gutterBottom>Saldo Actual</Typography>
|
||||||
|
{reportData.saldos && reportData.saldos.length > 0 ? (
|
||||||
|
<Typography sx={{ mb: 2 }}>
|
||||||
|
{reportData.saldos[0].monto.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}
|
||||||
|
</Typography>
|
||||||
|
) : <Typography sx={{ mb: 2, fontStyle: 'italic' }}>No hay saldo actual disponible.</Typography>}
|
||||||
|
|
||||||
|
<Typography variant="h6" gutterBottom>Movimientos (Entradas/Salidas)</Typography>
|
||||||
|
{reportData.entradasSalidas && reportData.entradasSalidas.length > 0 ?
|
||||||
|
renderEntradasSalidasTable(reportData.entradasSalidas) : <Typography sx={{ fontStyle: 'italic' }}>No hay movimientos de entradas/salidas.</Typography>}
|
||||||
|
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>Notas de Débito/Crédito</Typography>
|
||||||
|
{reportData.debitosCreditos && reportData.debitosCreditos.length > 0 ?
|
||||||
|
renderDebitosCreditosTable(reportData.debitosCreditos) : <Typography sx={{ fontStyle: 'italic' }}>No hay notas de débito/crédito.</Typography>}
|
||||||
|
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>Pagos</Typography>
|
||||||
|
{reportData.pagos && reportData.pagos.length > 0 ?
|
||||||
|
renderPagosTable(reportData.pagos) : <Typography sx={{ fontStyle: 'italic' }}>No hay pagos registrados.</Typography>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!loading && !error && (!reportData || (!reportData.entradasSalidas?.length && !reportData.debitosCreditos?.length && !reportData.pagos?.length && !reportData.saldos?.length)) &&
|
||||||
|
(<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteCuentasDistribuidoresPage;
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { ReporteDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ReporteDistribucionCanillasResponseDto';
|
||||||
|
import type { DetalleDistribucionCanillaDto } from '../../models/dtos/Reportes/DetalleDistribucionCanillaDto';
|
||||||
|
import type { DetalleDistribucionCanillaAllDto } from '../../models/dtos/Reportes/DetalleDistribucionCanillaAllDto';
|
||||||
|
import SeleccionaReporteDetalleDistribucionCanillas from './SeleccionaReporteDetalleDistribucionCanillas';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ReporteDetalleDistribucionCanillasPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<ReporteDistribucionCanillasResponseDto | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPdf, setLoadingPdf] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [apiErrorParams, setApiErrorParams] = useState<string | null>(null);
|
||||||
|
const [showParamSelector, setShowParamSelector] = useState(true);
|
||||||
|
const [currentParams, setCurrentParams] = useState<{
|
||||||
|
fecha: string;
|
||||||
|
idEmpresa: number;
|
||||||
|
nombreEmpresa?: string;
|
||||||
|
} | null>(null);
|
||||||
|
const [pdfSoloTotales, setPdfSoloTotales] = useState(false); // Estado para el tipo de PDF
|
||||||
|
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
fecha: string;
|
||||||
|
idEmpresa: number;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
|
||||||
|
const empresaService = (await import('../../services/Distribucion/empresaService')).default;
|
||||||
|
const empData = await empresaService.getEmpresaById(params.idEmpresa);
|
||||||
|
|
||||||
|
setCurrentParams({...params, nombreEmpresa: empData?.nombre});
|
||||||
|
setReportData(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getReporteDistribucionCanillas(params);
|
||||||
|
setReportData(data);
|
||||||
|
const noData = (!data.canillas || data.canillas.length === 0) &&
|
||||||
|
(!data.canillasAccionistas || data.canillasAccionistas.length === 0) &&
|
||||||
|
(!data.canillasTodos || data.canillasTodos.length === 0) &&
|
||||||
|
(!data.controlDevolucionesDetalle || data.controlDevolucionesDetalle.length === 0);
|
||||||
|
if (noData) {
|
||||||
|
setError("No se encontraron datos para los parámetros seleccionados.");
|
||||||
|
}
|
||||||
|
setShowParamSelector(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||||
|
? err.response.data.message
|
||||||
|
: 'Ocurrió un error al generar el reporte.';
|
||||||
|
setApiErrorParams(message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleVolverAParametros = useCallback(() => {
|
||||||
|
setShowParamSelector(true);
|
||||||
|
setReportData(null);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExportToExcel = useCallback(() => {
|
||||||
|
if (!reportData) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
const formatAndSheet = (data: any[], sheetName: string, fields: Record<string, string>) => {
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
const exportedData = data.map(item => {
|
||||||
|
const row: Record<string, any> = {};
|
||||||
|
Object.keys(fields).forEach(key => {
|
||||||
|
row[fields[key]] = item[key];
|
||||||
|
if (key === 'fecha' && item[key]) {
|
||||||
|
row[fields[key]] = new Date(item[key]).toLocaleDateString('es-AR', { timeZone: 'UTC' });
|
||||||
|
}
|
||||||
|
if ((key === 'totalRendir') && item[key] != null) {
|
||||||
|
row[fields[key]] = parseFloat(item[key]).toFixed(2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
const ws = XLSX.utils.json_to_sheet(exportedData);
|
||||||
|
const headers = Object.values(fields);
|
||||||
|
ws['!cols'] = headers.map(h => {
|
||||||
|
const maxLen = Math.max(...exportedData.map(row => (row[h]?.toString() ?? '').length), h.length);
|
||||||
|
return { wch: maxLen + 2 };
|
||||||
|
});
|
||||||
|
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
formatAndSheet(reportData.canillas, "Canillitas_Dia", { publicacion: "Publicación", canilla: "Canilla", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" });
|
||||||
|
formatAndSheet(reportData.canillasAccionistas, "Accionistas_Dia", { publicacion: "Publicación", canilla: "Canilla", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" });
|
||||||
|
formatAndSheet(reportData.canillasTodos, "Resumen_Dia", { publicacion: "Publicación", tipoVendedor: "Tipo", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" });
|
||||||
|
formatAndSheet(reportData.canillasLiquidadasOtraFecha, "Canillitas_OtrasFechas", { publicacion: "Publicación", canilla: "Canilla", fecha:"Fecha Mov.", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" });
|
||||||
|
formatAndSheet(reportData.canillasAccionistasLiquidadasOtraFecha, "Accionistas_OtrasFechas", { publicacion: "Publicación", canilla: "Canilla", fecha:"Fecha Mov.", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", totalRendir: "A Rendir" });
|
||||||
|
formatAndSheet(reportData.controlDevolucionesDetalle, "CtrlDev_Detalle", { ingresados: "Ingresados", sobrantes: "Sobrantes", sinCargo: "Sin Cargo", publicacion: "Publicación", llevados: "Llevados", devueltos: "Devueltos", tipo: "Tipo" });
|
||||||
|
formatAndSheet(reportData.controlDevolucionesRemitos, "CtrlDev_Remitos", { remito: "Remito Ingresado" });
|
||||||
|
formatAndSheet(reportData.controlDevolucionesOtrosDias, "CtrlDev_OtrosDias", { devueltos: "Devueltos Otros Días" });
|
||||||
|
|
||||||
|
|
||||||
|
let fileName = "ReporteDistribucionCanillitas";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.nombreEmpresa?.replace(/\s+/g, '') ?? `Emp${currentParams.idEmpresa}`}`;
|
||||||
|
fileName += `_${currentParams.fecha}`;
|
||||||
|
}
|
||||||
|
fileName += ".xlsx";
|
||||||
|
XLSX.writeFile(wb, fileName);
|
||||||
|
}, [reportData, currentParams]);
|
||||||
|
|
||||||
|
const handleGenerarYAbrirPdf = useCallback(async (soloTotales: boolean) => {
|
||||||
|
if (!currentParams) {
|
||||||
|
setError("Primero debe generar el reporte en pantalla o seleccionar parámetros.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingPdf(true);
|
||||||
|
setError(null);
|
||||||
|
setPdfSoloTotales(soloTotales); // Guardar la opción para el nombre del archivo
|
||||||
|
try {
|
||||||
|
const blob = await reportesService.getReporteDistribucionCanillasPdf({
|
||||||
|
...currentParams,
|
||||||
|
soloTotales // Pasar el parámetro al servicio
|
||||||
|
});
|
||||||
|
if (blob.type === "application/json") {
|
||||||
|
const text = await blob.text();
|
||||||
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF.";
|
||||||
|
setError(msg);
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const w = window.open(url, '_blank');
|
||||||
|
if (!w) alert("Permite popups para ver el PDF.");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError('Ocurrió un error al generar el PDF.');
|
||||||
|
} finally {
|
||||||
|
setLoadingPdf(false);
|
||||||
|
}
|
||||||
|
}, [currentParams]);
|
||||||
|
|
||||||
|
const renderTableData = (data: DetalleDistribucionCanillaDto[] | DetalleDistribucionCanillaAllDto[], title: string, isAllDto: boolean = false) => {
|
||||||
|
if (!data || data.length === 0) return <Typography sx={{mt:1, fontStyle:'italic'}}>No hay datos para {title.toLowerCase()}.</Typography>;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography variant="subtitle1" gutterBottom sx={{ mt: 2, fontWeight:'bold' }}>{title}</Typography>
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 2 }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Publicación</TableCell>
|
||||||
|
<TableCell>{isAllDto ? 'Tipo Vendedor' : 'Canillita'}</TableCell>
|
||||||
|
{ (data[0] as DetalleDistribucionCanillaDto).fecha && <TableCell>Fecha Mov.</TableCell> }
|
||||||
|
<TableCell align="right">Llevados</TableCell>
|
||||||
|
<TableCell align="right">Devueltos</TableCell>
|
||||||
|
<TableCell align="right">Vendidos</TableCell>
|
||||||
|
<TableCell align="right">A Rendir</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((row, idx) => {
|
||||||
|
const item = row as any; // Para acceso dinámico
|
||||||
|
const fechaMov = item.fecha ? new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : null;
|
||||||
|
return (
|
||||||
|
<TableRow key={`${title.replace(/\s+/g, '')}-${idx}`}>
|
||||||
|
<TableCell>{item.publicacion}</TableCell>
|
||||||
|
<TableCell>{isAllDto ? item.tipoVendedor : item.canilla}</TableCell>
|
||||||
|
{ fechaMov && <TableCell>{fechaMov}</TableCell> }
|
||||||
|
<TableCell align="right">{item.totalCantSalida.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{item.totalCantEntrada.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{(item.totalCantSalida - item.totalCantEntrada).toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{item.totalRendir.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteDetalleDistribucionCanillas
|
||||||
|
onGenerarReporte={handleGenerarReporte}
|
||||||
|
onCancel={handleVolverAParametros}
|
||||||
|
isLoading={loading}
|
||||||
|
apiErrorMessage={apiErrorParams}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
<Typography variant="h5">Reporte: Detalle Distribución Canillitas</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||||
|
<Button onClick={() => handleGenerarYAbrirPdf(false)} variant="contained" disabled={loadingPdf || !reportData || !!error} size="small">
|
||||||
|
{loadingPdf && !pdfSoloTotales ? <CircularProgress size={20} color="inherit" /> : "PDF Detalle"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => handleGenerarYAbrirPdf(true)} variant="contained" color="secondary" disabled={loadingPdf || !reportData || !!error} size="small">
|
||||||
|
{loadingPdf && pdfSoloTotales ? <CircularProgress size={20} color="inherit" /> : "PDF Totales"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={!reportData || !!error} size="small">
|
||||||
|
Exportar a Excel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
|
||||||
|
Nuevos Parámetros
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
|
||||||
|
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
|
{!loading && !error && reportData && (
|
||||||
|
<>
|
||||||
|
{renderTableData(reportData.canillas, "Canillitas")}
|
||||||
|
{renderTableData(reportData.canillasAccionistas, "Accionistas")}
|
||||||
|
{renderTableData(reportData.canillasTodos, "Resumen por Tipo de Vendedor", true)}
|
||||||
|
{reportData.canillasLiquidadasOtraFecha && reportData.canillasLiquidadasOtraFecha.length > 0 &&
|
||||||
|
renderTableData(reportData.canillasLiquidadasOtraFecha, "Canillitas (Liquidados de Otras Fechas)")}
|
||||||
|
{reportData.canillasAccionistasLiquidadasOtraFecha && reportData.canillasAccionistasLiquidadasOtraFecha.length > 0 &&
|
||||||
|
renderTableData(reportData.canillasAccionistasLiquidadasOtraFecha, "Accionistas (Liquidados de Otras Fechas)")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!loading && !error && (!reportData || ((!reportData.canillas || reportData.canillas.length === 0) && (!reportData.canillasAccionistas || reportData.canillasAccionistas.length === 0))) &&
|
||||||
|
(<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteDetalleDistribucionCanillasPage;
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { ListadoDistribucionCanillasImporteDto } from '../../models/dtos/Reportes/ListadoDistribucionCanillasImporteDto';
|
||||||
|
import SeleccionaReporteListadoDistribucionCanillasImporte from './SeleccionaReporteListadoDistribucionCanillasImporte';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ReporteListadoDistribucionCanillasImportePage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<ListadoDistribucionCanillasImporteDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPdf, setLoadingPdf] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [apiErrorParams, setApiErrorParams] = useState<string | null>(null);
|
||||||
|
const [showParamSelector, setShowParamSelector] = useState(true);
|
||||||
|
const [currentParams, setCurrentParams] = useState<{
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
esAccionista: boolean;
|
||||||
|
nombrePublicacion?: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
esAccionista: boolean;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
|
||||||
|
const pubService = (await import('../../services/Distribucion/publicacionService')).default;
|
||||||
|
const pubData = await pubService.getPublicacionById(params.idPublicacion);
|
||||||
|
|
||||||
|
setCurrentParams({...params, nombrePublicacion: pubData?.nombre});
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getListadoDistribucionCanillasImporte(params);
|
||||||
|
setReportData(data);
|
||||||
|
if (data.length === 0) {
|
||||||
|
setError("No se encontraron datos para los parámetros seleccionados.");
|
||||||
|
}
|
||||||
|
setShowParamSelector(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||||
|
? err.response.data.message
|
||||||
|
: 'Ocurrió un error al generar el reporte.';
|
||||||
|
setApiErrorParams(message);
|
||||||
|
setReportData([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleVolverAParametros = useCallback(() => {
|
||||||
|
setShowParamSelector(true);
|
||||||
|
setReportData([]);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExportToExcel = useCallback(() => {
|
||||||
|
if (reportData.length === 0) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dataToExport = reportData.map(item => ({
|
||||||
|
"Fecha": item.fecha, // Ya viene como string dd/MM/yyyy del SP
|
||||||
|
"Llevados": item.llevados,
|
||||||
|
"Devueltos": item.devueltos,
|
||||||
|
"Vendidos": item.vendidos,
|
||||||
|
"Imp. Publicación": item.totalRendirPublicacion,
|
||||||
|
"A Rendir": item.totalRendirGeneral,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet(dataToExport);
|
||||||
|
const headers = Object.keys(dataToExport[0]);
|
||||||
|
ws['!cols'] = headers.map(h => {
|
||||||
|
const maxLen = dataToExport.reduce((prev, row) => {
|
||||||
|
const cell = (row as any)[h]?.toString() ?? '';
|
||||||
|
return Math.max(prev, cell.length);
|
||||||
|
}, h.length);
|
||||||
|
return { wch: maxLen + 2 };
|
||||||
|
});
|
||||||
|
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
|
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, "DistribucionCanillasImporte");
|
||||||
|
let fileName = "ListadoDistribucionCanillasImporte";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.nombrePublicacion?.replace(/\s+/g, '') ?? `Pub${currentParams.idPublicacion}`}`;
|
||||||
|
fileName += `_${currentParams.esAccionista ? 'Accionistas' : 'Canillitas'}`;
|
||||||
|
fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`;
|
||||||
|
}
|
||||||
|
fileName += ".xlsx";
|
||||||
|
XLSX.writeFile(wb, fileName);
|
||||||
|
}, [reportData, currentParams]);
|
||||||
|
|
||||||
|
const handleGenerarYAbrirPdf = useCallback(async () => {
|
||||||
|
if (!currentParams) {
|
||||||
|
setError("Primero debe generar el reporte en pantalla o seleccionar parámetros.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingPdf(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const blob = await reportesService.getListadoDistribucionCanillasImportePdf(currentParams);
|
||||||
|
if (blob.type === "application/json") {
|
||||||
|
const text = await blob.text();
|
||||||
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF.";
|
||||||
|
setError(msg);
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const w = window.open(url, '_blank');
|
||||||
|
if (!w) alert("Permite popups para ver el PDF.");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError('Ocurrió un error al generar el PDF.');
|
||||||
|
} finally {
|
||||||
|
setLoadingPdf(false);
|
||||||
|
}
|
||||||
|
}, [currentParams]);
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteListadoDistribucionCanillasImporte
|
||||||
|
onGenerarReporte={handleGenerarReporte}
|
||||||
|
onCancel={handleVolverAParametros}
|
||||||
|
isLoading={loading}
|
||||||
|
apiErrorMessage={apiErrorParams}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
<Typography variant="h5">Reporte: Distribución Canillitas con Importe</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
|
||||||
|
Exportar a Excel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
|
||||||
|
Nuevos Parámetros
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
|
||||||
|
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
|
{!loading && !error && reportData.length > 0 && (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Fecha</TableCell>
|
||||||
|
<TableCell align="right">Llevados</TableCell>
|
||||||
|
<TableCell align="right">Devueltos</TableCell>
|
||||||
|
<TableCell align="right">Vendidos</TableCell>
|
||||||
|
<TableCell align="right">Importe Publicación</TableCell>
|
||||||
|
<TableCell align="right">A Rendir</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.map((row, idx) => (
|
||||||
|
<TableRow key={`importe-${idx}`}>
|
||||||
|
<TableCell>{row.fecha}</TableCell>
|
||||||
|
<TableCell align="right">{row.llevados.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.devueltos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.vendidos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.totalRendirPublicacion.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
|
||||||
|
<TableCell align="right">{row.totalRendirGeneral.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteListadoDistribucionCanillasImportePage;
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { TiradasPublicacionesSeccionesDto } from '../../models/dtos/Reportes/TiradasPublicacionesSeccionesDto';
|
||||||
|
import SeleccionaReporteTiradasPublicacionesSecciones from './SeleccionaReporteTiradasPublicacionesSecciones';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ReporteTiradasPublicacionesSeccionesPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<TiradasPublicacionesSeccionesDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPdf, setLoadingPdf] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [apiErrorParams, setApiErrorParams] = useState<string | null>(null);
|
||||||
|
const [showParamSelector, setShowParamSelector] = useState(true);
|
||||||
|
const [currentParams, setCurrentParams] = useState<{
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
nombrePublicacion?: string;
|
||||||
|
nombrePlanta?: string;
|
||||||
|
mesAnioParaNombreArchivo?: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setReportData([]);
|
||||||
|
|
||||||
|
const pubService = (await import('../../services/Distribucion/publicacionService')).default;
|
||||||
|
const pubData = await pubService.getPublicacionById(params.idPublicacion);
|
||||||
|
let plantaNombre = "Consolidado";
|
||||||
|
if (!params.consolidado && params.idPlanta) {
|
||||||
|
const plantaService = (await import('../../services/Impresion/plantaService')).default;
|
||||||
|
const plantaData = await plantaService.getPlantaById(params.idPlanta);
|
||||||
|
plantaNombre = plantaData?.nombre ?? "N/A";
|
||||||
|
}
|
||||||
|
const mesAnioParts = params.fechaDesde.split('-');
|
||||||
|
const mesAnioNombre = `${mesAnioParts[1]}/${mesAnioParts[0]}`;
|
||||||
|
|
||||||
|
setCurrentParams({...params, nombrePublicacion: pubData?.nombre, nombrePlanta: plantaNombre, mesAnioParaNombreArchivo: mesAnioNombre});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getTiradasPublicacionesSecciones(params);
|
||||||
|
setReportData(data);
|
||||||
|
if (data.length === 0) {
|
||||||
|
setError("No se encontraron datos para los parámetros seleccionados.");
|
||||||
|
}
|
||||||
|
setShowParamSelector(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||||
|
? err.response.data.message
|
||||||
|
: 'Ocurrió un error al generar el reporte.';
|
||||||
|
setApiErrorParams(message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleVolverAParametros = useCallback(() => {
|
||||||
|
setShowParamSelector(true);
|
||||||
|
setReportData([]);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExportToExcel = useCallback(() => {
|
||||||
|
if (reportData.length === 0) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dataToExport = reportData.map(item => ({
|
||||||
|
"Nombre Sección": item.nombreSeccion,
|
||||||
|
"Total Páginas Impresas": item.totalPaginasImpresas,
|
||||||
|
"Cantidad Ediciones": item.cantidadTiradas,
|
||||||
|
"Total Páginas x Edición": item.totalPaginasEjemplares,
|
||||||
|
"Total Ejemplares": item.totalEjemplares,
|
||||||
|
"Prom. Pág./Ejemplar": item.promedioPaginasPorEjemplar,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet(dataToExport);
|
||||||
|
const headers = Object.keys(dataToExport[0] || {});
|
||||||
|
ws['!cols'] = headers.map(h => {
|
||||||
|
const maxLen = Math.max(...dataToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length);
|
||||||
|
return { wch: maxLen + 2 };
|
||||||
|
});
|
||||||
|
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
|
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, "TiradasSecciones");
|
||||||
|
|
||||||
|
let fileName = "ReporteTiradasSecciones";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.nombrePublicacion?.replace(/\s+/g, '') ?? `Pub${currentParams.idPublicacion}`}`;
|
||||||
|
if (!currentParams.consolidado) fileName += `_Planta${currentParams.idPlanta}`;
|
||||||
|
else fileName += "_Consolidado";
|
||||||
|
fileName += `_${currentParams.mesAnioParaNombreArchivo?.replace('/', '-')}`;
|
||||||
|
}
|
||||||
|
fileName += ".xlsx";
|
||||||
|
XLSX.writeFile(wb, fileName);
|
||||||
|
}, [reportData, currentParams]);
|
||||||
|
|
||||||
|
const handleGenerarYAbrirPdf = useCallback(async () => {
|
||||||
|
if (!currentParams) {
|
||||||
|
setError("Primero debe generar el reporte en pantalla o seleccionar parámetros.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingPdf(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const blob = await reportesService.getTiradasPublicacionesSeccionesPdf(currentParams);
|
||||||
|
if (blob.type === "application/json") {
|
||||||
|
const text = await blob.text();
|
||||||
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF.";
|
||||||
|
setError(msg);
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const w = window.open(url, '_blank');
|
||||||
|
if (!w) alert("Permite popups para ver el PDF.");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError('Ocurrió un error al generar el PDF.');
|
||||||
|
} finally {
|
||||||
|
setLoadingPdf(false);
|
||||||
|
}
|
||||||
|
}, [currentParams]);
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteTiradasPublicacionesSecciones
|
||||||
|
onGenerarReporte={handleGenerarReporte}
|
||||||
|
onCancel={handleVolverAParametros}
|
||||||
|
isLoading={loading}
|
||||||
|
apiErrorMessage={apiErrorParams}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
<Typography variant="h5">Reporte: Tiradas por Publicación y Secciones</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
|
||||||
|
Exportar a Excel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
|
||||||
|
Nuevos Parámetros
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
|
||||||
|
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
|
{!loading && !error && reportData.length > 0 && (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Nombre Sección</TableCell>
|
||||||
|
<TableCell align="right">Total Páginas Imp.</TableCell>
|
||||||
|
<TableCell align="right">Cant. Ediciones</TableCell>
|
||||||
|
<TableCell align="right">Total Pág. x Edición</TableCell>
|
||||||
|
<TableCell align="right">Total Ejemplares</TableCell>
|
||||||
|
<TableCell align="right">Prom. Pág./Ejemplar</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.map((row, idx) => (
|
||||||
|
<TableRow key={`${row.nombreSeccion}-${idx}`}>
|
||||||
|
<TableCell>{row.nombreSeccion}</TableCell>
|
||||||
|
<TableCell align="right">{row.totalPaginasImpresas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.cantidadTiradas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.totalPaginasEjemplares.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.totalEjemplares.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedioPaginasPorEjemplar.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteTiradasPublicacionesSeccionesPage;
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { VentaMensualSecretariaElDiaDto } from '../../models/dtos/Reportes/VentaMensualSecretariaElDiaDto';
|
||||||
|
import type { VentaMensualSecretariaElPlataDto } from '../../models/dtos/Reportes/VentaMensualSecretariaElPlataDto';
|
||||||
|
import type { VentaMensualSecretariaTirDevoDto } from '../../models/dtos/Reportes/VentaMensualSecretariaTirDevoDto';
|
||||||
|
import SeleccionaReporteVentaMensual, { type TipoReporteVentaMensual } from './SeleccionaReporteVentaMensual';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
type ReportData = VentaMensualSecretariaElDiaDto[] | VentaMensualSecretariaElPlataDto[] | VentaMensualSecretariaTirDevoDto[];
|
||||||
|
|
||||||
|
const ReporteVentaMensualSecretariaPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<ReportData>([]);
|
||||||
|
const [currentReportType, setCurrentReportType] = useState<TipoReporteVentaMensual | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPdf, setLoadingPdf] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [apiErrorParams, setApiErrorParams] = useState<string | null>(null);
|
||||||
|
const [showParamSelector, setShowParamSelector] = useState(true);
|
||||||
|
const [currentParams, setCurrentParams] = useState<{
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
tipoReporte: TipoReporteVentaMensual;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
tipoReporte: TipoReporteVentaMensual;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(params);
|
||||||
|
setCurrentReportType(params.tipoReporte);
|
||||||
|
setReportData([]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data: ReportData = [];
|
||||||
|
if (params.tipoReporte === 'ElDia') {
|
||||||
|
data = await reportesService.getVentaMensualSecretariaElDia(params);
|
||||||
|
} else if (params.tipoReporte === 'ElPlata') {
|
||||||
|
data = await reportesService.getVentaMensualSecretariaElPlata(params);
|
||||||
|
} else if (params.tipoReporte === 'TirDevo') {
|
||||||
|
data = await reportesService.getVentaMensualSecretariaTirDevo(params);
|
||||||
|
}
|
||||||
|
setReportData(data);
|
||||||
|
if (data.length === 0) {
|
||||||
|
setError("No se encontraron datos para los parámetros seleccionados.");
|
||||||
|
}
|
||||||
|
setShowParamSelector(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||||
|
? err.response.data.message
|
||||||
|
: 'Ocurrió un error al generar el reporte.';
|
||||||
|
setApiErrorParams(message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleVolverAParametros = useCallback(() => {
|
||||||
|
setShowParamSelector(true);
|
||||||
|
setReportData([]);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(null);
|
||||||
|
setCurrentReportType(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getReportTitle = () => {
|
||||||
|
if (!currentReportType) return "Venta Mensual Secretaría";
|
||||||
|
if (currentReportType === 'ElDia') return "Venta Mensual: El Día";
|
||||||
|
if (currentReportType === 'ElPlata') return "Venta Mensual: El Plata";
|
||||||
|
if (currentReportType === 'TirDevo') return "Venta Mensual: Tirada/Devolución";
|
||||||
|
return "Venta Mensual Secretaría";
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExportToExcel = useCallback(() => {
|
||||||
|
if (reportData.length === 0 || !currentReportType) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataToExport: Record<string, any>[] = [];
|
||||||
|
let sheetName = "Reporte";
|
||||||
|
let fileNamePrefix = "ReporteVentaMensual";
|
||||||
|
|
||||||
|
if (currentReportType === 'ElDia') {
|
||||||
|
dataToExport = (reportData as VentaMensualSecretariaElDiaDto[]).map(item => ({
|
||||||
|
"Día": item.dia, "Canillitas": item.cantidadCanillas, "Tirajes": item.tirajes,
|
||||||
|
"Ventas": item.ventas, "Accionistas": item.accionistas, "Total Coop.": item.totalCooperativa,
|
||||||
|
"Total General": item.totalGeneral
|
||||||
|
}));
|
||||||
|
sheetName = "ElDia";
|
||||||
|
fileNamePrefix += "_ElDia";
|
||||||
|
} else if (currentReportType === 'ElPlata') {
|
||||||
|
dataToExport = (reportData as VentaMensualSecretariaElPlataDto[]).map(item => ({
|
||||||
|
"Día": item.dia, "Tirada Coop.": item.tiradaCoop, "Devol. Coop.": item.devolucionCoop,
|
||||||
|
"Venta Coop.": item.ventaCoop, "Tirada Can.": item.tiradaCan, "Venta Can.": item.ventaCan,
|
||||||
|
"Total": item.total
|
||||||
|
}));
|
||||||
|
sheetName = "ElPlata";
|
||||||
|
fileNamePrefix += "_ElPlata";
|
||||||
|
} else if (currentReportType === 'TirDevo') {
|
||||||
|
dataToExport = (reportData as VentaMensualSecretariaTirDevoDto[]).map(item => ({
|
||||||
|
"Día": item.dia, "Tir. Coop. (ED)": item.tiradaCoop, "Dev. Coop. (ED)": item.devolucionCoop,
|
||||||
|
"Vta. Coop. (ED)": item.ventaCoop, "Vta. Can. (ED)": item.ventaCan,
|
||||||
|
"Tir. Popular": item.tiradaPopular, "Dev. Popular": item.devolucionPopular, "Vta. Popular": item.ventaPopular,
|
||||||
|
"Tir. Clarín": item.tiradaClarin, "Dev. Clarín": item.devolucionClarin, "Vta. Clarín": item.ventaClarin,
|
||||||
|
"Tir. Nación": item.tiradaNacion, "Dev. Nación": item.devolucionNacion, "Vta. Nación": item.ventaNacion,
|
||||||
|
}));
|
||||||
|
sheetName = "TiradaDevolucion";
|
||||||
|
fileNamePrefix += "_TirDevo";
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws = XLSX.utils.json_to_sheet(dataToExport);
|
||||||
|
const headers = Object.keys(dataToExport[0] || {});
|
||||||
|
ws['!cols'] = headers.map(h => {
|
||||||
|
const maxLen = Math.max(...dataToExport.map(row => (row[h]?.toString() ?? '').length), h.length);
|
||||||
|
return { wch: maxLen + 2 };
|
||||||
|
});
|
||||||
|
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
||||||
|
let fileName = fileNamePrefix;
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`;
|
||||||
|
}
|
||||||
|
fileName += ".xlsx";
|
||||||
|
XLSX.writeFile(wb, fileName);
|
||||||
|
}, [reportData, currentReportType, currentParams]);
|
||||||
|
|
||||||
|
const handleGenerarYAbrirPdf = useCallback(async () => {
|
||||||
|
if (!currentParams) {
|
||||||
|
setError("Primero debe generar el reporte en pantalla o seleccionar parámetros.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingPdf(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
let blob: Blob;
|
||||||
|
if (currentParams.tipoReporte === 'ElDia') {
|
||||||
|
blob = await reportesService.getVentaMensualSecretariaElDiaPdf(currentParams);
|
||||||
|
} else if (currentParams.tipoReporte === 'ElPlata') {
|
||||||
|
blob = await reportesService.getVentaMensualSecretariaElPlataPdf(currentParams);
|
||||||
|
} else if (currentParams.tipoReporte === 'TirDevo') {
|
||||||
|
blob = await reportesService.getVentaMensualSecretariaTirDevoPdf(currentParams);
|
||||||
|
} else {
|
||||||
|
setError("Tipo de reporte no válido para PDF.");
|
||||||
|
setLoadingPdf(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blob.type === "application/json") {
|
||||||
|
const text = await blob.text();
|
||||||
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF.";
|
||||||
|
setError(msg);
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const w = window.open(url, '_blank');
|
||||||
|
if (!w) alert("Permite popups para ver el PDF.");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError('Ocurrió un error al generar el PDF.');
|
||||||
|
} finally {
|
||||||
|
setLoadingPdf(false);
|
||||||
|
}
|
||||||
|
}, [currentParams]);
|
||||||
|
|
||||||
|
const renderTable = () => {
|
||||||
|
if (!reportData || reportData.length === 0 || !currentReportType) return null;
|
||||||
|
|
||||||
|
if (currentReportType === 'ElDia') {
|
||||||
|
const data = reportData as VentaMensualSecretariaElDiaDto[];
|
||||||
|
return (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 280px)' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Día</TableCell><TableCell align="right">Canillitas</TableCell>
|
||||||
|
<TableCell align="right">Tirajes</TableCell><TableCell align="right">Ventas</TableCell>
|
||||||
|
<TableCell align="right">Accionistas</TableCell><TableCell align="right">Total Coop.</TableCell>
|
||||||
|
<TableCell align="right">Total General</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((row, idx) => (
|
||||||
|
<TableRow key={`eldia-${idx}`}>
|
||||||
|
<TableCell>{row.dia}</TableCell>
|
||||||
|
<TableCell align="right">{row.cantidadCanillas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.tirajes.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.ventas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.accionistas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.totalCooperativa.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.totalGeneral.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
} else if (currentReportType === 'ElPlata') {
|
||||||
|
const data = reportData as VentaMensualSecretariaElPlataDto[];
|
||||||
|
return (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 280px)' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Día</TableCell><TableCell align="right">Tirada Coop.</TableCell>
|
||||||
|
<TableCell align="right">Devol. Coop.</TableCell><TableCell align="right">Venta Coop.</TableCell>
|
||||||
|
<TableCell align="right">Tirada Can.</TableCell><TableCell align="right">Venta Can.</TableCell>
|
||||||
|
<TableCell align="right">Total</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((row, idx) => (
|
||||||
|
<TableRow key={`elplata-${idx}`}>
|
||||||
|
<TableCell>{row.dia}</TableCell>
|
||||||
|
<TableCell align="right">{row.tiradaCoop.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.devolucionCoop.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.ventaCoop.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.tiradaCan.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.ventaCan.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.total.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
} else if (currentReportType === 'TirDevo') {
|
||||||
|
const data = reportData as VentaMensualSecretariaTirDevoDto[];
|
||||||
|
return (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 280px)' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell rowSpan={2} sx={{verticalAlign: 'bottom'}}>Día</TableCell>
|
||||||
|
<TableCell colSpan={4} align="center" sx={{borderBottom: '1px solid rgba(224, 224, 224, 1)'}}>El Día</TableCell>
|
||||||
|
<TableCell colSpan={3} align="center" sx={{borderBottom: '1px solid rgba(224, 224, 224, 1)'}}>Popular</TableCell>
|
||||||
|
<TableCell colSpan={3} align="center" sx={{borderBottom: '1px solid rgba(224, 224, 224, 1)'}}>Clarín</TableCell>
|
||||||
|
<TableCell colSpan={3} align="center" sx={{borderBottom: '1px solid rgba(224, 224, 224, 1)'}}>Nación</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell align="right">Tir. Coop.</TableCell><TableCell align="right">Dev. Coop.</TableCell>
|
||||||
|
<TableCell align="right">Vta. Coop.</TableCell><TableCell align="right">Vta. Can.</TableCell>
|
||||||
|
<TableCell align="right">Tirada</TableCell><TableCell align="right">Devol.</TableCell><TableCell align="right">Venta</TableCell>
|
||||||
|
<TableCell align="right">Tirada</TableCell><TableCell align="right">Devol.</TableCell><TableCell align="right">Venta</TableCell>
|
||||||
|
<TableCell align="right">Tirada</TableCell><TableCell align="right">Devol.</TableCell><TableCell align="right">Venta</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((row, idx) => (
|
||||||
|
<TableRow key={`tirdevo-${idx}`}>
|
||||||
|
<TableCell>{row.dia}</TableCell>
|
||||||
|
<TableCell align="right">{row.tiradaCoop.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.devolucionCoop.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.ventaCoop.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.ventaCan.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.tiradaPopular.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.devolucionPopular.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.ventaPopular.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.tiradaClarin.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.devolucionClarin.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.ventaClarin.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.tiradaNacion.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.devolucionNacion.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.ventaNacion.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteVentaMensual
|
||||||
|
onGenerarReporte={handleGenerarReporte}
|
||||||
|
onCancel={handleVolverAParametros} // Este podría navegar a /reportes
|
||||||
|
isLoading={loading}
|
||||||
|
apiErrorMessage={apiErrorParams}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
<Typography variant="h5">{getReportTitle()}</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small">
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small">
|
||||||
|
Exportar a Excel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small">
|
||||||
|
Nuevos Parámetros
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>}
|
||||||
|
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
|
{!loading && !error && renderTable()}
|
||||||
|
{!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteVentaMensualSecretariaPage;
|
||||||
@@ -8,6 +8,14 @@ const reportesSubModules = [
|
|||||||
{ label: 'Mov. Bobinas por Estado', path: 'movimiento-bobinas-estado' },
|
{ label: 'Mov. Bobinas por Estado', path: 'movimiento-bobinas-estado' },
|
||||||
{ label: 'Distribución General', path: 'listado-distribucion-general' },
|
{ label: 'Distribución General', path: 'listado-distribucion-general' },
|
||||||
{ label: 'Distribución Canillas', path: 'listado-distribucion-canillas' },
|
{ label: 'Distribución Canillas', path: 'listado-distribucion-canillas' },
|
||||||
|
{ label: 'Distrib. Canillas (Importe)', path: 'listado-distribucion-canillas-importe' },
|
||||||
|
{ label: 'Venta Mensual Secretaría', path: 'venta-mensual-secretaria' },
|
||||||
|
{ label: 'Det. Distribución Canillas', path: 'detalle-distribucion-canillas' },
|
||||||
|
{ label: 'Tiradas Pub./Sección', path: 'tiradas-publicaciones-secciones' },
|
||||||
|
{ label: 'Consumo Bobinas/Sección', path: 'consumo-bobinas-seccion' },
|
||||||
|
{ label: 'Consumo Bobinas/Pub.', path: 'consumo-bobinas-publicacion' },
|
||||||
|
{ label: 'Comparativa Cons. Bobinas', path: 'comparativa-consumo-bobinas' },
|
||||||
|
{ label: 'Cuentas Distribuidores', path: 'cuentas-distribuidores' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const ReportesIndexPage: React.FC = () => {
|
const ReportesIndexPage: React.FC = () => {
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||||
|
import plantaService from '../../services/Impresion/plantaService';
|
||||||
|
|
||||||
|
interface SeleccionaReporteComparativaConsumoBobinasProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
fechaInicioMesA: string;
|
||||||
|
fechaFinMesA: string;
|
||||||
|
fechaInicioMesB: string;
|
||||||
|
fechaFinMesB: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteComparativaConsumoBobinas: React.FC<SeleccionaReporteComparativaConsumoBobinasProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const today = new Date();
|
||||||
|
const currentMonthYear = today.toISOString().substring(0, 7); // YYYY-MM para el mes actual
|
||||||
|
const lastMonthDate = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
||||||
|
const lastMonthYear = lastMonthDate.toISOString().substring(0, 7); // YYYY-MM para el mes pasado
|
||||||
|
|
||||||
|
const [mesAnioA, setMesAnioA] = useState<string>(lastMonthYear);
|
||||||
|
const [mesAnioB, setMesAnioB] = useState<string>(currentMonthYear);
|
||||||
|
const [idPlanta, setIdPlanta] = useState<number | string>('');
|
||||||
|
const [consolidado, setConsolidado] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlantas = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const plantasData = await plantaService.getAllPlantas();
|
||||||
|
setPlantas(plantasData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar plantas:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar plantas.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPlantas();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (consolidado) {
|
||||||
|
setIdPlanta('');
|
||||||
|
}
|
||||||
|
}, [consolidado]);
|
||||||
|
|
||||||
|
const getFirstAndLastDayOfMonth = (yearMonth: string): { firstDay: string, lastDay: string } => {
|
||||||
|
const [year, month] = yearMonth.split('-').map(Number);
|
||||||
|
const firstDay = new Date(year, month - 1, 1);
|
||||||
|
const lastDay = new Date(year, month, 0); // El día 0 del siguiente mes es el último del mes actual
|
||||||
|
return {
|
||||||
|
firstDay: firstDay.toISOString().split('T')[0],
|
||||||
|
lastDay: lastDay.toISOString().split('T')[0],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!mesAnioA) errors.mesAnioA = 'Mes/Año A es obligatorio.';
|
||||||
|
if (!mesAnioB) errors.mesAnioB = 'Mes/Año B es obligatorio.';
|
||||||
|
if (mesAnioA && mesAnioB && mesAnioA === mesAnioB) {
|
||||||
|
errors.mesAnioB = 'Mes A y Mes B no pueden ser iguales para una comparativa.';
|
||||||
|
}
|
||||||
|
if (!consolidado && !idPlanta) {
|
||||||
|
errors.idPlanta = 'Seleccione una planta si no es consolidado.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
const periodoA = getFirstAndLastDayOfMonth(mesAnioA);
|
||||||
|
const periodoB = getFirstAndLastDayOfMonth(mesAnioB);
|
||||||
|
|
||||||
|
onGenerarReporte({
|
||||||
|
fechaInicioMesA: periodoA.firstDay,
|
||||||
|
fechaFinMesA: periodoA.lastDay,
|
||||||
|
fechaInicioMesB: periodoB.firstDay,
|
||||||
|
fechaFinMesB: periodoB.lastDay,
|
||||||
|
idPlanta: consolidado ? null : Number(idPlanta),
|
||||||
|
consolidado
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Comparativa Consumo Bobinas
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, mb: 1, alignItems: 'flex-start' }}>
|
||||||
|
<TextField
|
||||||
|
label="Mes/Año A" type="month" value={mesAnioA}
|
||||||
|
onChange={(e) => { setMesAnioA(e.target.value); setLocalErrors(p => ({ ...p, mesAnioA: null, mesAnioB: null })); }}
|
||||||
|
margin="normal" fullWidth required error={!!localErrors.mesAnioA} helperText={localErrors.mesAnioA}
|
||||||
|
disabled={isLoading} InputLabelProps={{ shrink: true }} sx={{flex: 1}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Mes/Año B" type="month" value={mesAnioB}
|
||||||
|
onChange={(e) => { setMesAnioB(e.target.value); setLocalErrors(p => ({ ...p, mesAnioB: null })); }}
|
||||||
|
margin="normal" fullWidth required error={!!localErrors.mesAnioB} helperText={localErrors.mesAnioB}
|
||||||
|
disabled={isLoading} InputLabelProps={{ shrink: true }} sx={{flex: 1}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={consolidado}
|
||||||
|
onChange={(e) => setConsolidado(e.target.checked)}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Consolidado (Todas las Plantas)"
|
||||||
|
sx={{ mt: 1, mb: 1, display: 'block' }}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idPlanta} disabled={isLoading || loadingDropdowns || consolidado}>
|
||||||
|
<InputLabel id="planta-select-label-comp" required={!consolidado}>Planta</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="planta-select-label-comp"
|
||||||
|
label="Planta"
|
||||||
|
value={consolidado ? '' : idPlanta}
|
||||||
|
onChange={(e) => { setIdPlanta(e.target.value as number); setLocalErrors(p => ({ ...p, idPlanta: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>{consolidado ? 'N/A (Consolidado)' : 'Seleccione una planta'}</em></MenuItem>
|
||||||
|
{plantas.map((p) => (
|
||||||
|
<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPlanta && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPlanta}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteComparativaConsumoBobinas;
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
interface SeleccionaReporteConsumoBobinasPublicacionProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteConsumoBobinasPublicacion: React.FC<SeleccionaReporteConsumoBobinasPublicacionProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!fechaDesde) errors.fechaDesde = 'Fecha Desde es obligatoria.';
|
||||||
|
if (!fechaHasta) errors.fechaHasta = 'Fecha Hasta es obligatoria.';
|
||||||
|
if (fechaDesde && fechaHasta && new Date(fechaDesde) > new Date(fechaHasta)) {
|
||||||
|
errors.fechaHasta = 'Fecha Hasta no puede ser anterior a Fecha Desde.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
onGenerarReporte({
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Consumo de Bobinas por Publicación
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Desde"
|
||||||
|
type="date"
|
||||||
|
value={fechaDesde}
|
||||||
|
onChange={(e) => { setFechaDesde(e.target.value); setLocalErrors(p => ({ ...p, fechaDesde: null, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaDesde}
|
||||||
|
helperText={localErrors.fechaDesde}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Hasta"
|
||||||
|
type="date"
|
||||||
|
value={fechaHasta}
|
||||||
|
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaHasta}
|
||||||
|
helperText={localErrors.fechaHasta}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteConsumoBobinasPublicacion;
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||||
|
import plantaService from '../../services/Impresion/plantaService';
|
||||||
|
|
||||||
|
interface SeleccionaReporteConsumoBobinasSeccionProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteConsumoBobinasSeccion: React.FC<SeleccionaReporteConsumoBobinasSeccionProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
onCancel,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [idPlanta, setIdPlanta] = useState<number | string>('');
|
||||||
|
const [consolidado, setConsolidado] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlantas = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const plantasData = await plantaService.getAllPlantas();
|
||||||
|
setPlantas(plantasData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar plantas:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar plantas.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPlantas();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (consolidado) {
|
||||||
|
setIdPlanta('');
|
||||||
|
}
|
||||||
|
}, [consolidado]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!fechaDesde) errors.fechaDesde = 'Fecha Desde es obligatoria.';
|
||||||
|
if (!fechaHasta) errors.fechaHasta = 'Fecha Hasta es obligatoria.';
|
||||||
|
if (fechaDesde && fechaHasta && new Date(fechaDesde) > new Date(fechaHasta)) {
|
||||||
|
errors.fechaHasta = 'Fecha Hasta no puede ser anterior a Fecha Desde.';
|
||||||
|
}
|
||||||
|
if (!consolidado && !idPlanta) {
|
||||||
|
errors.idPlanta = 'Seleccione una planta si no es consolidado.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
onGenerarReporte({
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta,
|
||||||
|
idPlanta: consolidado ? null : Number(idPlanta),
|
||||||
|
consolidado
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Consumo de Bobinas por Sección
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Desde"
|
||||||
|
type="date"
|
||||||
|
value={fechaDesde}
|
||||||
|
onChange={(e) => { setFechaDesde(e.target.value); setLocalErrors(p => ({ ...p, fechaDesde: null, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaDesde}
|
||||||
|
helperText={localErrors.fechaDesde}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Hasta"
|
||||||
|
type="date"
|
||||||
|
value={fechaHasta}
|
||||||
|
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaHasta}
|
||||||
|
helperText={localErrors.fechaHasta}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={consolidado}
|
||||||
|
onChange={(e) => setConsolidado(e.target.checked)}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Consolidado (Todas las Plantas)"
|
||||||
|
sx={{ mt: 1, mb: 1 }}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idPlanta} disabled={isLoading || loadingDropdowns || consolidado}>
|
||||||
|
<InputLabel id="planta-select-label-consumo" required={!consolidado}>Planta</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="planta-select-label-consumo"
|
||||||
|
label="Planta"
|
||||||
|
value={consolidado ? '' : idPlanta}
|
||||||
|
onChange={(e) => { setIdPlanta(e.target.value as number); setLocalErrors(p => ({ ...p, idPlanta: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>{consolidado ? 'N/A (Consolidado)' : 'Seleccione una planta'}</em></MenuItem>
|
||||||
|
{plantas.map((p) => (
|
||||||
|
<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPlanta && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPlanta}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onCancel} color="secondary" disabled={isLoading}>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteConsumoBobinasSeccion;
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { DistribuidorDto } from '../../models/dtos/Distribucion/DistribuidorDto';
|
||||||
|
import distribuidorService from '../../services/Distribucion/distribuidorService';
|
||||||
|
import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto';
|
||||||
|
import empresaService from '../../services/Distribucion/empresaService';
|
||||||
|
|
||||||
|
interface SeleccionaReporteCuentasDistribuidoresProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
idDistribuidor: number;
|
||||||
|
idEmpresa: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteCuentasDistribuidores: React.FC<SeleccionaReporteCuentasDistribuidoresProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idDistribuidor, setIdDistribuidor] = useState<number | string>('');
|
||||||
|
const [idEmpresa, setIdEmpresa] = useState<number | string>('');
|
||||||
|
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
|
||||||
|
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
|
||||||
|
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const [distData, empData] = await Promise.all([
|
||||||
|
distribuidorService.getAllDistribuidores(), // Asume que este servicio existe
|
||||||
|
empresaService.getAllEmpresas() // Asume que este servicio existe
|
||||||
|
]);
|
||||||
|
setDistribuidores(distData.map(d => d)); // El servicio devuelve tupla
|
||||||
|
setEmpresas(empData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar datos:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar distribuidores o empresas.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idDistribuidor) errors.idDistribuidor = 'Debe seleccionar un distribuidor.';
|
||||||
|
if (!idEmpresa) errors.idEmpresa = 'Debe seleccionar una empresa.';
|
||||||
|
if (!fechaDesde) errors.fechaDesde = 'Fecha Desde es obligatoria.';
|
||||||
|
if (!fechaHasta) errors.fechaHasta = 'Fecha Hasta es obligatoria.';
|
||||||
|
if (fechaDesde && fechaHasta && new Date(fechaDesde) > new Date(fechaHasta)) {
|
||||||
|
errors.fechaHasta = 'Fecha Hasta no puede ser anterior a Fecha Desde.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
onGenerarReporte({
|
||||||
|
idDistribuidor: Number(idDistribuidor),
|
||||||
|
idEmpresa: Number(idEmpresa),
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Cuenta Corriente Distribuidor
|
||||||
|
</Typography>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idDistribuidor} disabled={isLoading || loadingDropdowns}>
|
||||||
|
<InputLabel id="distribuidor-select-label" required>Distribuidor</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="distribuidor-select-label"
|
||||||
|
label="Distribuidor"
|
||||||
|
value={idDistribuidor}
|
||||||
|
onChange={(e) => { setIdDistribuidor(e.target.value as number); setLocalErrors(p => ({ ...p, idDistribuidor: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione un distribuidor</em></MenuItem>
|
||||||
|
{distribuidores.map((d) => (
|
||||||
|
<MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idDistribuidor && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idDistribuidor}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idEmpresa} disabled={isLoading || loadingDropdowns}>
|
||||||
|
<InputLabel id="empresa-select-label-cta" required>Empresa</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="empresa-select-label-cta"
|
||||||
|
label="Empresa"
|
||||||
|
value={idEmpresa}
|
||||||
|
onChange={(e) => { setIdEmpresa(e.target.value as number); setLocalErrors(p => ({ ...p, idEmpresa: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una empresa</em></MenuItem>
|
||||||
|
{empresas.map((e) => (
|
||||||
|
<MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idEmpresa && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idEmpresa}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Fecha Desde"
|
||||||
|
type="date"
|
||||||
|
value={fechaDesde}
|
||||||
|
onChange={(e) => { setFechaDesde(e.target.value); setLocalErrors(p => ({ ...p, fechaDesde: null, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaDesde}
|
||||||
|
helperText={localErrors.fechaDesde}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Hasta"
|
||||||
|
type="date"
|
||||||
|
value={fechaHasta}
|
||||||
|
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaHasta}
|
||||||
|
helperText={localErrors.fechaHasta}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteCuentasDistribuidores;
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto';
|
||||||
|
import empresaService from '../../services/Distribucion/empresaService';
|
||||||
|
|
||||||
|
interface SeleccionaReporteDetalleDistribucionCanillasProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
fecha: string;
|
||||||
|
idEmpresa: number;
|
||||||
|
// soloTotales: boolean; // Podríamos añadirlo si el usuario elige la versión del PDF
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteDetalleDistribucionCanillas: React.FC<SeleccionaReporteDetalleDistribucionCanillasProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [idEmpresa, setIdEmpresa] = useState<number | string>('');
|
||||||
|
// const [soloTotales, setSoloTotales] = useState<boolean>(false); // Si se añade la opción
|
||||||
|
|
||||||
|
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchEmpresas = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const data = await empresaService.getAllEmpresas(); // Asume que este servicio existe
|
||||||
|
setEmpresas(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar empresas:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar empresas.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchEmpresas();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!fecha) errors.fecha = 'La fecha es obligatoria.';
|
||||||
|
if (!idEmpresa) errors.idEmpresa = 'Debe seleccionar una empresa.';
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
onGenerarReporte({
|
||||||
|
fecha,
|
||||||
|
idEmpresa: Number(idEmpresa),
|
||||||
|
// soloTotales // Si se añade la opción
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Detalle Distribución Canillitas
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
label="Fecha"
|
||||||
|
type="date"
|
||||||
|
value={fecha}
|
||||||
|
onChange={(e) => { setFecha(e.target.value); setLocalErrors(p => ({ ...p, fecha: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fecha}
|
||||||
|
helperText={localErrors.fecha}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idEmpresa} disabled={isLoading || loadingDropdowns}>
|
||||||
|
<InputLabel id="empresa-select-label-distcan" required>Empresa</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="empresa-select-label-distcan"
|
||||||
|
label="Empresa"
|
||||||
|
value={idEmpresa}
|
||||||
|
onChange={(e) => { setIdEmpresa(e.target.value as number); setLocalErrors(p => ({ ...p, idEmpresa: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una empresa</em></MenuItem>
|
||||||
|
{empresas.map((e) => (
|
||||||
|
<MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idEmpresa && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idEmpresa}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
{/*
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={soloTotales}
|
||||||
|
onChange={(e) => setSoloTotales(e.target.checked)}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Generar solo resumen de totales (PDF)"
|
||||||
|
sx={{ mt: 1, mb: 1 }}
|
||||||
|
/>
|
||||||
|
*/}
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteDetalleDistribucionCanillas;
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
import publicacionService from '../../services/Distribucion/publicacionService';
|
||||||
|
|
||||||
|
interface SeleccionaReporteListadoDistribucionCanillasImporteProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
esAccionista: boolean;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteListadoDistribucionCanillasImporte: React.FC<SeleccionaReporteListadoDistribucionCanillasImporteProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
|
||||||
|
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [esAccionista, setEsAccionista] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPublicaciones = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const data = await publicacionService.getAllPublicaciones(undefined, undefined, true);
|
||||||
|
setPublicaciones(data.map(p => p));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar publicaciones:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar publicaciones.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPublicaciones();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idPublicacion) errors.idPublicacion = 'Debe seleccionar una publicación.';
|
||||||
|
if (!fechaDesde) errors.fechaDesde = 'Fecha Desde es obligatoria.';
|
||||||
|
if (!fechaHasta) errors.fechaHasta = 'Fecha Hasta es obligatoria.';
|
||||||
|
if (fechaDesde && fechaHasta && new Date(fechaDesde) > new Date(fechaHasta)) {
|
||||||
|
errors.fechaHasta = 'Fecha Hasta no puede ser anterior a Fecha Desde.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
onGenerarReporte({
|
||||||
|
idPublicacion: Number(idPublicacion),
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta,
|
||||||
|
esAccionista
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Distribución Canillitas con Importe
|
||||||
|
</Typography>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idPublicacion} disabled={isLoading || loadingDropdowns}>
|
||||||
|
<InputLabel id="publicacion-select-label-imp" required>Publicación</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="publicacion-select-label-imp"
|
||||||
|
label="Publicación"
|
||||||
|
value={idPublicacion}
|
||||||
|
onChange={(e) => { setIdPublicacion(e.target.value as number); setLocalErrors(p => ({ ...p, idPublicacion: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una publicación</em></MenuItem>
|
||||||
|
{publicaciones.map((p) => (
|
||||||
|
<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPublicacion && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPublicacion}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Desde"
|
||||||
|
type="date"
|
||||||
|
value={fechaDesde}
|
||||||
|
onChange={(e) => { setFechaDesde(e.target.value); setLocalErrors(p => ({ ...p, fechaDesde: null, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaDesde}
|
||||||
|
helperText={localErrors.fechaDesde}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Hasta"
|
||||||
|
type="date"
|
||||||
|
value={fechaHasta}
|
||||||
|
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaHasta}
|
||||||
|
helperText={localErrors.fechaHasta}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={esAccionista}
|
||||||
|
onChange={(e) => setEsAccionista(e.target.checked)}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Ver Accionistas"
|
||||||
|
sx={{ mt: 1, mb: 1 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteListadoDistribucionCanillasImporte;
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
import publicacionService from '../../services/Distribucion/publicacionService';
|
||||||
|
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||||
|
import plantaService from '../../services/Impresion/plantaService';
|
||||||
|
|
||||||
|
interface SeleccionaReporteTiradasPublicacionesSeccionesProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string; // Primer día del mes
|
||||||
|
fechaHasta: string; // Último día del mes
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteTiradasPublicacionesSecciones: React.FC<SeleccionaReporteTiradasPublicacionesSeccionesProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
|
||||||
|
const [mesAnio, setMesAnio] = useState<string>(new Date().toISOString().substring(0, 7)); // Formato "YYYY-MM"
|
||||||
|
const [idPlanta, setIdPlanta] = useState<number | string>('');
|
||||||
|
const [consolidado, setConsolidado] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
|
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const [pubData, plantaData] = await Promise.all([
|
||||||
|
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||||
|
plantaService.getAllPlantas()
|
||||||
|
]);
|
||||||
|
setPublicaciones(pubData.map(p => p));
|
||||||
|
setPlantas(plantaData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar datos:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar publicaciones o plantas.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (consolidado) {
|
||||||
|
setIdPlanta(''); // Limpiar planta si es consolidado
|
||||||
|
}
|
||||||
|
}, [consolidado]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idPublicacion) errors.idPublicacion = 'Debe seleccionar una publicación.';
|
||||||
|
if (!mesAnio) errors.mesAnio = 'Debe seleccionar un Mes/Año.';
|
||||||
|
if (!consolidado && !idPlanta) {
|
||||||
|
errors.idPlanta = 'Seleccione una planta si no es consolidado.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
const [year, month] = mesAnio.split('-').map(Number);
|
||||||
|
const fechaDesde = new Date(year, month - 1, 1).toISOString().split('T')[0];
|
||||||
|
const fechaHasta = new Date(year, month, 0).toISOString().split('T')[0];
|
||||||
|
|
||||||
|
onGenerarReporte({
|
||||||
|
idPublicacion: Number(idPublicacion),
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta,
|
||||||
|
idPlanta: consolidado ? null : Number(idPlanta),
|
||||||
|
consolidado
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Tiradas por Publicación y Secciones
|
||||||
|
</Typography>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idPublicacion} disabled={isLoading || loadingDropdowns}>
|
||||||
|
<InputLabel id="publicacion-select-label-tir" required>Publicación</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="publicacion-select-label-tir"
|
||||||
|
label="Publicación"
|
||||||
|
value={idPublicacion}
|
||||||
|
onChange={(e) => { setIdPublicacion(e.target.value as number); setLocalErrors(p => ({ ...p, idPublicacion: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una publicación</em></MenuItem>
|
||||||
|
{publicaciones.map((p) => (
|
||||||
|
<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPublicacion && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPublicacion}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
label="Mes y Año"
|
||||||
|
type="month"
|
||||||
|
value={mesAnio}
|
||||||
|
onChange={(e) => { setMesAnio(e.target.value); setLocalErrors(p => ({ ...p, mesAnio: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.mesAnio}
|
||||||
|
helperText={localErrors.mesAnio}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={consolidado}
|
||||||
|
onChange={(e) => setConsolidado(e.target.checked)}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Consolidado (Todas las Plantas)"
|
||||||
|
sx={{ mt: 1, mb: 1 }}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idPlanta} disabled={isLoading || loadingDropdowns || consolidado}>
|
||||||
|
<InputLabel id="planta-select-label-tir" required={!consolidado}>Planta</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="planta-select-label-tir"
|
||||||
|
label="Planta"
|
||||||
|
value={consolidado ? '' : idPlanta}
|
||||||
|
onChange={(e) => { setIdPlanta(e.target.value as number); setLocalErrors(p => ({ ...p, idPlanta: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>{consolidado ? 'N/A (Consolidado)' : 'Seleccione una planta'}</em></MenuItem>
|
||||||
|
{plantas.map((p) => (
|
||||||
|
<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPlanta && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPlanta}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteTiradasPublicacionesSecciones;
|
||||||
114
Frontend/src/pages/Reportes/SeleccionaReporteVentaMensual.tsx
Normal file
114
Frontend/src/pages/Reportes/SeleccionaReporteVentaMensual.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, type SelectChangeEvent
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
export type TipoReporteVentaMensual = 'ElDia' | 'ElPlata' | 'TirDevo';
|
||||||
|
|
||||||
|
interface SeleccionaReporteVentaMensualProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
tipoReporte: TipoReporteVentaMensual;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void; // Para volver a la pantalla de selección de reportes
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteVentaMensual: React.FC<SeleccionaReporteVentaMensualProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [tipoReporte, setTipoReporte] = useState<TipoReporteVentaMensual>('ElDia');
|
||||||
|
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!fechaDesde) errors.fechaDesde = 'Fecha Desde es obligatoria.';
|
||||||
|
if (!fechaHasta) errors.fechaHasta = 'Fecha Hasta es obligatoria.';
|
||||||
|
if (fechaDesde && fechaHasta && new Date(fechaDesde) > new Date(fechaHasta)) {
|
||||||
|
errors.fechaHasta = 'Fecha Hasta no puede ser anterior a Fecha Desde.';
|
||||||
|
}
|
||||||
|
if (!tipoReporte) errors.tipoReporte = 'Debe seleccionar un tipo de reporte.';
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
onGenerarReporte({
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta,
|
||||||
|
tipoReporte
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTipoReporteChange = (event: SelectChangeEvent<string>) => {
|
||||||
|
setTipoReporte(event.target.value as TipoReporteVentaMensual);
|
||||||
|
setLocalErrors(p => ({ ...p, tipoReporte: null }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Venta Mensual Secretaría
|
||||||
|
</Typography>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.tipoReporte} disabled={isLoading}>
|
||||||
|
<InputLabel id="tipo-reporte-select-label" required>Tipo de Reporte</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="tipo-reporte-select-label"
|
||||||
|
label="Tipo de Reporte"
|
||||||
|
value={tipoReporte}
|
||||||
|
onChange={handleTipoReporteChange}
|
||||||
|
>
|
||||||
|
<MenuItem value="ElDia">El Día</MenuItem>
|
||||||
|
<MenuItem value="ElPlata">El Plata</MenuItem>
|
||||||
|
<MenuItem value="TirDevo">Tirada / Devolución</MenuItem>
|
||||||
|
</Select>
|
||||||
|
{localErrors.tipoReporte && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.tipoReporte}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Desde"
|
||||||
|
type="date"
|
||||||
|
value={fechaDesde}
|
||||||
|
onChange={(e) => { setFechaDesde(e.target.value); setLocalErrors(p => ({ ...p, fechaDesde: null, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaDesde}
|
||||||
|
helperText={localErrors.fechaDesde}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Hasta"
|
||||||
|
type="date"
|
||||||
|
value={fechaHasta}
|
||||||
|
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaHasta}
|
||||||
|
helperText={localErrors.fechaHasta}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteVentaMensual;
|
||||||
@@ -59,6 +59,14 @@ import ReporteMovimientoBobinasPage from '../pages/Reportes/ReporteMovimientoBob
|
|||||||
import ReporteMovimientoBobinasEstadoPage from '../pages/Reportes/ReporteMovimientoBobinasEstadoPage';
|
import ReporteMovimientoBobinasEstadoPage from '../pages/Reportes/ReporteMovimientoBobinasEstadoPage';
|
||||||
import ReporteListadoDistribucionGeneralPage from '../pages/Reportes/ReporteListadoDistribucionGeneralPage';
|
import ReporteListadoDistribucionGeneralPage from '../pages/Reportes/ReporteListadoDistribucionGeneralPage';
|
||||||
import ReporteListadoDistribucionCanillasPage from '../pages/Reportes/ReporteListadoDistribucionCanillasPage';
|
import ReporteListadoDistribucionCanillasPage from '../pages/Reportes/ReporteListadoDistribucionCanillasPage';
|
||||||
|
import ReporteListadoDistribucionCanillasImportePage from '../pages/Reportes/ReporteListadoDistribucionCanillasImportePage';
|
||||||
|
import ReporteVentaMensualSecretariaPage from '../pages/Reportes/ReporteVentaMensualSecretariaPage';
|
||||||
|
import ReporteDetalleDistribucionCanillasPage from '../pages/Reportes/ReporteDetalleDistribucionCanillasPage';
|
||||||
|
import ReporteTiradasPublicacionesSeccionesPage from '../pages/Reportes/ReporteTiradasPublicacionesSeccionesPage';
|
||||||
|
import ReporteConsumoBobinasSeccionPage from '../pages/Reportes/ReporteConsumoBobinasSeccionPage';
|
||||||
|
import ReporteConsumoBobinasPublicacionPage from '../pages/Reportes/ReporteConsumoBobinasPublicacionPage';
|
||||||
|
import ReporteComparativaConsumoBobinasPage from '../pages/Reportes/ReporteComparativaConsumoBobinasPage';
|
||||||
|
import ReporteCuentasDistribuidoresPage from '../pages/Reportes/ReporteCuentasDistribuidoresPage';
|
||||||
|
|
||||||
// Auditorias
|
// Auditorias
|
||||||
import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage';
|
import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage';
|
||||||
@@ -161,6 +169,14 @@ const AppRoutes = () => {
|
|||||||
<Route path="movimiento-bobinas-estado" element={<ReporteMovimientoBobinasEstadoPage />} />
|
<Route path="movimiento-bobinas-estado" element={<ReporteMovimientoBobinasEstadoPage />} />
|
||||||
<Route path="listado-distribucion-general" element={<ReporteListadoDistribucionGeneralPage />} />
|
<Route path="listado-distribucion-general" element={<ReporteListadoDistribucionGeneralPage />} />
|
||||||
<Route path="listado-distribucion-canillas" element={<ReporteListadoDistribucionCanillasPage />} />
|
<Route path="listado-distribucion-canillas" element={<ReporteListadoDistribucionCanillasPage />} />
|
||||||
|
<Route path="listado-distribucion-canillas-importe" element={<ReporteListadoDistribucionCanillasImportePage />} />
|
||||||
|
<Route path="venta-mensual-secretaria" element={<ReporteVentaMensualSecretariaPage />} />
|
||||||
|
<Route path="detalle-distribucion-canillas" element={<ReporteDetalleDistribucionCanillasPage />} />
|
||||||
|
<Route path="tiradas-publicaciones-secciones" element={<ReporteTiradasPublicacionesSeccionesPage />} />
|
||||||
|
<Route path="consumo-bobinas-seccion" element={<ReporteConsumoBobinasSeccionPage />} />
|
||||||
|
<Route path="consumo-bobinas-publicacion" element={<ReporteConsumoBobinasPublicacionPage />} />
|
||||||
|
<Route path="comparativa-consumo-bobinas" element={<ReporteComparativaConsumoBobinasPage />} />
|
||||||
|
<Route path="cuentas-distribuidores" element={<ReporteCuentasDistribuidoresPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Módulo de Radios (anidado) */}
|
{/* Módulo de Radios (anidado) */}
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ import type { MovimientoBobinasDto } from '../../models/dtos/Reportes/Movimiento
|
|||||||
import type { MovimientoBobinasPorEstadoResponseDto } from '../../models/dtos/Reportes/MovimientoBobinasPorEstadoResponseDto';
|
import type { MovimientoBobinasPorEstadoResponseDto } from '../../models/dtos/Reportes/MovimientoBobinasPorEstadoResponseDto';
|
||||||
import type { ListadoDistribucionGeneralResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionGeneralResponseDto';
|
import type { ListadoDistribucionGeneralResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionGeneralResponseDto';
|
||||||
import type { ListadoDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionCanillasResponseDto';
|
import type { ListadoDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionCanillasResponseDto';
|
||||||
|
import type { ListadoDistribucionCanillasImporteDto } from '../../models/dtos/Reportes/ListadoDistribucionCanillasImporteDto';
|
||||||
|
import type { VentaMensualSecretariaElDiaDto } from '../../models/dtos/Reportes/VentaMensualSecretariaElDiaDto';
|
||||||
|
import type { VentaMensualSecretariaElPlataDto } from '../../models/dtos/Reportes/VentaMensualSecretariaElPlataDto';
|
||||||
|
import type { VentaMensualSecretariaTirDevoDto } from '../../models/dtos/Reportes/VentaMensualSecretariaTirDevoDto';
|
||||||
|
import type { ReporteDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ReporteDistribucionCanillasResponseDto';
|
||||||
|
import type { TiradasPublicacionesSeccionesDto } from '../../models/dtos/Reportes/TiradasPublicacionesSeccionesDto';
|
||||||
|
import type { ConsumoBobinasSeccionDto } from '../../models/dtos/Reportes/ConsumoBobinasSeccionDto';
|
||||||
|
import type { ConsumoBobinasPublicacionDto } from '../../models/dtos/Reportes/ConsumoBobinasPublicacionDto';
|
||||||
|
import type { ComparativaConsumoBobinasDto } from '../../models/dtos/Reportes/ComparativaConsumoBobinasDto';
|
||||||
|
import type { ReporteCuentasDistribuidorResponseDto } from '../../models/dtos/Reportes/ReporteCuentasDistribuidorResponseDto';
|
||||||
|
|
||||||
interface GetExistenciaPapelParams {
|
interface GetExistenciaPapelParams {
|
||||||
fechaDesde: string; // yyyy-MM-dd
|
fechaDesde: string; // yyyy-MM-dd
|
||||||
@@ -129,6 +139,189 @@ const getListadoDistribucionCanillasPdf = async (params: {
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getListadoDistribucionCanillasImporte = async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
esAccionista: boolean;
|
||||||
|
}): Promise<ListadoDistribucionCanillasImporteDto[]> => {
|
||||||
|
const response = await apiClient.get<ListadoDistribucionCanillasImporteDto[]>('/reportes/listado-distribucion-canillas-importe', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getListadoDistribucionCanillasImportePdf = async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
esAccionista: boolean;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/listado-distribucion-canillas-importe/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVentaMensualSecretariaElDia = async (params: { fechaDesde: string; fechaHasta: string }): Promise<VentaMensualSecretariaElDiaDto[]> => {
|
||||||
|
const response = await apiClient.get<VentaMensualSecretariaElDiaDto[]>('/reportes/venta-mensual-secretaria/el-dia', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVentaMensualSecretariaElDiaPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-dia/pdf', { params, responseType: 'blob' });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVentaMensualSecretariaElPlata = async (params: { fechaDesde: string; fechaHasta: string }): Promise<VentaMensualSecretariaElPlataDto[]> => {
|
||||||
|
const response = await apiClient.get<VentaMensualSecretariaElPlataDto[]>('/reportes/venta-mensual-secretaria/el-plata', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVentaMensualSecretariaElPlataPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/venta-mensual-secretaria/el-plata/pdf', { params, responseType: 'blob' });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVentaMensualSecretariaTirDevo = async (params: { fechaDesde: string; fechaHasta: string }): Promise<VentaMensualSecretariaTirDevoDto[]> => {
|
||||||
|
const response = await apiClient.get<VentaMensualSecretariaTirDevoDto[]>('/reportes/venta-mensual-secretaria/tirada-devolucion', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
const getVentaMensualSecretariaTirDevoPdf = async (params: { fechaDesde: string; fechaHasta: string }): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/venta-mensual-secretaria/tirada-devolucion/pdf', { params, responseType: 'blob' });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReporteDistribucionCanillas = async (params: {
|
||||||
|
fecha: string;
|
||||||
|
idEmpresa: number;
|
||||||
|
}): Promise<ReporteDistribucionCanillasResponseDto> => {
|
||||||
|
const response = await apiClient.get<ReporteDistribucionCanillasResponseDto>('/reportes/distribucion-canillas', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReporteDistribucionCanillasPdf = async (params: {
|
||||||
|
fecha: string;
|
||||||
|
idEmpresa: number;
|
||||||
|
soloTotales: boolean; // Nuevo parámetro
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/distribucion-canillas/pdf', { // La ruta no necesita cambiar si el backend lo maneja
|
||||||
|
params, // soloTotales se enviará como query param si el backend lo espera así
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTiradasPublicacionesSecciones = async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
}): Promise<TiradasPublicacionesSeccionesDto[]> => {
|
||||||
|
const response = await apiClient.get<TiradasPublicacionesSeccionesDto[]>('/reportes/tiradas-publicaciones-secciones', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTiradasPublicacionesSeccionesPdf = async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/tiradas-publicaciones-secciones/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getConsumoBobinasSeccion = async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
}): Promise<ConsumoBobinasSeccionDto[]> => {
|
||||||
|
const response = await apiClient.get<ConsumoBobinasSeccionDto[]>('/reportes/consumo-bobinas-seccion', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getConsumoBobinasSeccionPdf = async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta?: number | null;
|
||||||
|
consolidado: boolean;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/consumo-bobinas-seccion/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getConsumoBobinasPorPublicacion = async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}): Promise<ConsumoBobinasPublicacionDto[]> => {
|
||||||
|
const response = await apiClient.get<ConsumoBobinasPublicacionDto[]>('/reportes/consumo-bobinas-publicacion', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getConsumoBobinasPorPublicacionPdf = async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/consumo-bobinas-publicacion/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getComparativaConsumoBobinas = async (params: {
|
||||||
|
fechaInicioMesA: string; fechaFinMesA: string;
|
||||||
|
fechaInicioMesB: string; fechaFinMesB: string;
|
||||||
|
idPlanta?: number | null; consolidado: boolean;
|
||||||
|
}): Promise<ComparativaConsumoBobinasDto[]> => {
|
||||||
|
const response = await apiClient.get<ComparativaConsumoBobinasDto[]>('/reportes/comparativa-consumo-bobinas', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getComparativaConsumoBobinasPdf = async (params: {
|
||||||
|
fechaInicioMesA: string; fechaFinMesA: string;
|
||||||
|
fechaInicioMesB: string; fechaFinMesB: string;
|
||||||
|
idPlanta?: number | null; consolidado: boolean;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/comparativa-consumo-bobinas/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReporteCuentasDistribuidor = async (params: {
|
||||||
|
idDistribuidor: number;
|
||||||
|
idEmpresa: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}): Promise<ReporteCuentasDistribuidorResponseDto> => {
|
||||||
|
const response = await apiClient.get<ReporteCuentasDistribuidorResponseDto>('/reportes/cuentas-distribuidores', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReporteCuentasDistribuidorPdf = async (params: {
|
||||||
|
idDistribuidor: number;
|
||||||
|
idEmpresa: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/cuentas-distribuidores/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
const reportesService = {
|
const reportesService = {
|
||||||
getExistenciaPapel,
|
getExistenciaPapel,
|
||||||
getExistenciaPapelPdf,
|
getExistenciaPapelPdf,
|
||||||
@@ -139,7 +332,27 @@ const reportesService = {
|
|||||||
getListadoDistribucionGeneral,
|
getListadoDistribucionGeneral,
|
||||||
getListadoDistribucionGeneralPdf,
|
getListadoDistribucionGeneralPdf,
|
||||||
getListadoDistribucionCanillas,
|
getListadoDistribucionCanillas,
|
||||||
getListadoDistribucionCanillasPdf
|
getListadoDistribucionCanillasPdf,
|
||||||
|
getListadoDistribucionCanillasImporte,
|
||||||
|
getListadoDistribucionCanillasImportePdf,
|
||||||
|
getVentaMensualSecretariaElDia,
|
||||||
|
getVentaMensualSecretariaElDiaPdf,
|
||||||
|
getVentaMensualSecretariaElPlata,
|
||||||
|
getVentaMensualSecretariaElPlataPdf,
|
||||||
|
getVentaMensualSecretariaTirDevo,
|
||||||
|
getVentaMensualSecretariaTirDevoPdf,
|
||||||
|
getReporteDistribucionCanillas,
|
||||||
|
getReporteDistribucionCanillasPdf,
|
||||||
|
getTiradasPublicacionesSecciones,
|
||||||
|
getTiradasPublicacionesSeccionesPdf,
|
||||||
|
getConsumoBobinasSeccion,
|
||||||
|
getConsumoBobinasSeccionPdf,
|
||||||
|
getConsumoBobinasPorPublicacion,
|
||||||
|
getConsumoBobinasPorPublicacionPdf,
|
||||||
|
getComparativaConsumoBobinas,
|
||||||
|
getComparativaConsumoBobinasPdf,
|
||||||
|
getReporteCuentasDistribuidor,
|
||||||
|
getReporteCuentasDistribuidorPdf,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default reportesService;
|
export default reportesService;
|
||||||
Reference in New Issue
Block a user