1. Funcionalidad Principal: Auditoría General
Se creó una nueva sección de "Auditoría" en la aplicación, diseñada para ser accedida por SuperAdmins. Se implementó una página AuditoriaGeneralPage.tsx que actúa como un visor centralizado para el historial de cambios de múltiples entidades del sistema. 2. Backend: Nuevo Controlador (AuditoriaController.cs): Centraliza los endpoints para obtener datos de las tablas de historial (_H). Servicios y Repositorios Extendidos: Se añadieron métodos GetHistorialAsync y ObtenerHistorialAsync a las capas de repositorio y servicio para cada una de las siguientes entidades, permitiendo consultar sus tablas _H con filtros: Usuarios (gral_Usuarios_H) Pagos de Distribuidores (cue_PagosDistribuidor_H) Notas de Crédito/Débito (cue_CreditosDebitos_H) Entradas/Salidas de Distribuidores (dist_EntradasSalidas_H) Entradas/Salidas de Canillitas (dist_EntradasSalidasCanillas_H) Novedades de Canillitas (dist_dtNovedadesCanillas_H) Tipos de Pago (cue_dtTipopago_H) Canillitas (Maestro) (dist_dtCanillas_H) Distribuidores (Maestro) (dist_dtDistribuidores_H) Empresas (Maestro) (dist_dtEmpresas_H) Zonas (Maestro) (dist_dtZonas_H) Otros Destinos (Maestro) (dist_dtOtrosDestinos_H) Publicaciones (Maestro) (dist_dtPublicaciones_H) Secciones de Publicación (dist_dtPubliSecciones_H) Precios de Publicación (dist_Precios_H) Recargos por Zona (dist_RecargoZona_H) Porcentajes Pago Distribuidores (dist_PorcPago_H) Porcentajes/Montos Canillita (dist_PorcMonPagoCanilla_H) Control de Devoluciones (dist_dtCtrlDevoluciones_H) Tipos de Bobina (bob_dtBobinas_H) Estados de Bobina (bob_dtEstadosBobinas_H) Plantas de Impresión (bob_dtPlantas_H) Stock de Bobinas (bob_StockBobinas_H) Tiradas (Registro Principal) (bob_RegTiradas_H) Secciones de Tirada (bob_RegPublicaciones_H) Cambios de Parada de Canillitas (dist_CambiosParadasCanillas_H) Ajustes Manuales de Saldo (cue_SaldoAjustesHistorial) DTOs de Historial: Se crearon DTOs específicos para cada tabla de historial (ej. UsuarioHistorialDto, PagoDistribuidorHistorialDto, etc.) para transferir los datos al frontend, incluyendo el nombre del usuario que realizó la modificación. Corrección de Lógica de Saldos: Se revisó y corrigió la lógica de afectación de saldos en los servicios PagoDistribuidorService y NotaCreditoDebitoService para asegurar que los débitos y créditos se apliquen correctamente. 3. Frontend: Nuevo Servicio (auditoriaService.ts): Contiene métodos para llamar a cada uno de los nuevos endpoints de auditoría del backend. Nueva Página (AuditoriaGeneralPage.tsx): Permite al SuperAdmin seleccionar el "Tipo de Entidad" a auditar desde un dropdown. Ofrece filtros comunes (rango de fechas, usuario modificador, tipo de acción) y filtros específicos que aparecen dinámicamente según la entidad seleccionada. Utiliza un DataGrid de Material-UI para mostrar el historial, con columnas que se adaptan dinámicamente al tipo de entidad consultada. Nuevos DTOs en TypeScript: Se crearon las interfaces correspondientes a los DTOs de historial del backend. Gestión de Permisos: La sección de Auditoría en MainLayout.tsx y su ruta en AppRoutes.tsx están protegidas para ser visibles y accesibles solo por SuperAdmins. Se añadió un permiso de ejemplo AU_GENERAL_VIEW para ser usado si se decide extender el acceso en el futuro. Corrección de Errores Menores: Se solucionó el problema del "parpadeo" del selector de fecha en GestionarNovedadesCanillaPage al adoptar un patrón de carga de datos más controlado, similar a otras páginas funcionales.
This commit is contained in:
@@ -229,7 +229,7 @@ const NotaCreditoDebitoFormModal: React.FC<NotaCreditoDebitoFormModalProps> = ({
|
||||
margin="dense" fullWidth error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
||||
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
||||
/>
|
||||
<TextField label="Referencia (Opcional)" value={referencia}
|
||||
<TextField label="Referencia Interna (Opcional)" value={referencia}
|
||||
onChange={(e) => setReferencia(e.target.value)}
|
||||
margin="dense" fullWidth disabled={loading || isEditing}
|
||||
/>
|
||||
@@ -244,10 +244,10 @@ const NotaCreditoDebitoFormModal: React.FC<NotaCreditoDebitoFormModalProps> = ({
|
||||
<TextField label="Monto" type="number" value={monto} required
|
||||
onChange={(e) => {setMonto(e.target.value); handleInputChange('monto');}}
|
||||
margin="dense" fullWidth error={!!localErrors.monto} helperText={localErrors.monto || ''}
|
||||
disabled={loading} inputProps={{step: "0.01", min:0.01, lang:"es-AR" }}
|
||||
disabled={loading} inputProps={{step: "0.01", min:0.05, lang:"es-AR" }}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
|
||||
/>
|
||||
<TextField label="Observaciones (Opcional)" value={observaciones}
|
||||
<TextField label="Referencia (Opcional)" value={observaciones}
|
||||
onChange={(e) => setObservaciones(e.target.value)}
|
||||
margin="dense" fullWidth multiline rows={2} disabled={loading}
|
||||
/>
|
||||
|
||||
@@ -210,7 +210,7 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
|
||||
<TextField label="Monto" type="number" value={monto} required
|
||||
onChange={(e) => {setMonto(e.target.value); handleInputChange('monto');}}
|
||||
margin="dense" fullWidth error={!!localErrors.monto} helperText={localErrors.monto || ''}
|
||||
disabled={loading} inputProps={{step: "0.01", min:0.01, lang:"es-AR" }}
|
||||
disabled={loading} inputProps={{step: "0.01", min:0.05, lang:"es-AR" }}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
|
||||
/>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idTipoPago} required>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||
InputAdornment
|
||||
// Quitar Grid si no se usa
|
||||
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||
InputAdornment
|
||||
// Quitar Grid si no se usa
|
||||
} from '@mui/material';
|
||||
import type { PrecioDto } from '../../../models/dtos/Distribucion/PrecioDto';
|
||||
import type { CreatePrecioDto } from '../../../models/dtos/Distribucion/CreatePrecioDto';
|
||||
import type { UpdatePrecioDto } from '../../../models/dtos/Distribucion/UpdatePrecioDto';
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: { xs: '95%', sm: '80%', md: '700px' },
|
||||
bgcolor: 'background.paper',
|
||||
border: '2px solid #000',
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
maxHeight: '90vh',
|
||||
overflowY: 'auto'
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: { xs: '95%', sm: '80%', md: '700px' },
|
||||
bgcolor: 'background.paper',
|
||||
border: '2px solid #000',
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
maxHeight: '90vh',
|
||||
overflowY: 'auto'
|
||||
};
|
||||
|
||||
const diasSemana = ["Lunes", "Martes", "Miercoles", "Jueves", "Viernes", "Sabado", "Domingo"] as const;
|
||||
@@ -55,48 +55,50 @@ const PrecioFormModal: React.FC<PrecioFormModalProps> = ({
|
||||
const isEditing = Boolean(initialData);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setVigenciaD(initialData?.vigenciaD || '');
|
||||
setVigenciaH(initialData?.vigenciaH || '');
|
||||
const initialPrecios: Record<DiaSemana, string> = { Lunes: '', Martes: '', Miercoles: '', Jueves: '', Viernes: '', Sabado: '', Domingo: '' };
|
||||
if (initialData) {
|
||||
diasSemana.forEach(dia => {
|
||||
const key = dia.toLowerCase() as keyof Omit<PrecioDto, 'idPrecio' | 'idPublicacion' | 'vigenciaD' | 'vigenciaH'>;
|
||||
initialPrecios[dia] = initialData[key]?.toString() || '';
|
||||
});
|
||||
}
|
||||
setPreciosDia(initialPrecios);
|
||||
setLocalErrors({});
|
||||
clearErrorMessage();
|
||||
// Este efecto se ejecuta cuando el modal se abre o cuando los datos iniciales cambian
|
||||
if (open) {
|
||||
setVigenciaD(initialData?.vigenciaD || '');
|
||||
setVigenciaH(initialData?.vigenciaH || '');
|
||||
const initialPrecios: Record<DiaSemana, string> = { Lunes: '', Martes: '', Miercoles: '', Jueves: '', Viernes: '', Sabado: '', Domingo: '' };
|
||||
if (initialData) {
|
||||
diasSemana.forEach(dia => {
|
||||
const key = dia.toLowerCase() as keyof Omit<PrecioDto, 'idPrecio' | 'idPublicacion' | 'vigenciaD' | 'vigenciaH'>;
|
||||
initialPrecios[dia] = initialData[key]?.toString() || '';
|
||||
});
|
||||
}
|
||||
}, [open, initialData, clearErrorMessage]);
|
||||
setPreciosDia(initialPrecios);
|
||||
setLocalErrors({});
|
||||
// NO llamar a clearErrorMessage aquí automáticamente al abrir si ya hay un error de un intento previo.
|
||||
// Solo limpiar si es una "nueva" apertura (ej, initialData cambió o se abrió desde cerrado)
|
||||
}
|
||||
}, [open, initialData]);
|
||||
|
||||
const validate = (): boolean => {
|
||||
const errors: { [key: string]: string | null } = {};
|
||||
if (!isEditing && !vigenciaD.trim()) {
|
||||
errors.vigenciaD = 'La Vigencia Desde es obligatoria.';
|
||||
errors.vigenciaD = 'La Vigencia Desde es obligatoria.';
|
||||
} else if (vigenciaD.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaD)) {
|
||||
errors.vigenciaD = 'Formato de fecha inválido (YYYY-MM-DD).';
|
||||
errors.vigenciaD = 'Formato de fecha inválido (YYYY-MM-DD).';
|
||||
}
|
||||
|
||||
if (vigenciaH.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaH)) {
|
||||
errors.vigenciaH = 'Formato de fecha inválido (YYYY-MM-DD).';
|
||||
errors.vigenciaH = 'Formato de fecha inválido (YYYY-MM-DD).';
|
||||
} else if (vigenciaH.trim() && vigenciaD.trim() && new Date(vigenciaH) < new Date(vigenciaD)) {
|
||||
errors.vigenciaH = 'Vigencia Hasta no puede ser anterior a Vigencia Desde.';
|
||||
errors.vigenciaH = 'Vigencia Hasta no puede ser anterior a Vigencia Desde.';
|
||||
}
|
||||
|
||||
let alMenosUnPrecio = false;
|
||||
diasSemana.forEach(dia => {
|
||||
const valor = preciosDia[dia];
|
||||
if (valor.trim()) {
|
||||
alMenosUnPrecio = true;
|
||||
if (isNaN(parseFloat(valor)) || parseFloat(valor) < 0) {
|
||||
errors[dia.toLowerCase()] = `Precio de ${dia} inválido.`;
|
||||
}
|
||||
const valor = preciosDia[dia];
|
||||
if (valor.trim()) {
|
||||
alMenosUnPrecio = true;
|
||||
if (isNaN(parseFloat(valor)) || parseFloat(valor) < 0) {
|
||||
errors[dia.toLowerCase()] = `Precio de ${dia} inválido.`;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!isEditing && !alMenosUnPrecio) {
|
||||
errors.dias = 'Debe ingresar al menos un precio para un día de la semana.';
|
||||
errors.dias = 'Debe ingresar al menos un precio para un día de la semana.';
|
||||
}
|
||||
|
||||
setLocalErrors(errors);
|
||||
@@ -106,53 +108,59 @@ const PrecioFormModal: React.FC<PrecioFormModalProps> = ({
|
||||
const handlePrecioDiaChange = (dia: DiaSemana, value: string) => {
|
||||
setPreciosDia(prev => ({ ...prev, [dia]: value }));
|
||||
if (localErrors[dia.toLowerCase()] || localErrors.dias) {
|
||||
setLocalErrors(prev => ({ ...prev, [dia.toLowerCase()]: null, dias: null }));
|
||||
setLocalErrors(prev => ({ ...prev, [dia.toLowerCase()]: null, dias: null }));
|
||||
}
|
||||
if (errorMessage) clearErrorMessage();
|
||||
};
|
||||
const handleDateChange = (setter: React.Dispatch<React.SetStateAction<string>>, fieldName: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setter(e.target.value);
|
||||
if (localErrors[fieldName]) {
|
||||
setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||
setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||
}
|
||||
if (errorMessage) clearErrorMessage();
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
clearErrorMessage();
|
||||
clearErrorMessage(); // Limpiar al inicio de un nuevo intento de submit
|
||||
if (!validate()) return;
|
||||
|
||||
setLoading(true);
|
||||
let success = false; // Bandera para controlar el cierre
|
||||
try {
|
||||
const preciosNumericos: Partial<Record<keyof Omit<PrecioDto, 'idPrecio'|'idPublicacion'|'vigenciaD'|'vigenciaH'>, number | null>> = {};
|
||||
diasSemana.forEach(dia => {
|
||||
const valor = preciosDia[dia].trim();
|
||||
// Convertir el nombre del día a la clave correcta del DTO (ej. "Lunes" -> "lunes")
|
||||
const key = dia.toLowerCase() as keyof typeof preciosNumericos;
|
||||
preciosNumericos[key] = valor ? parseFloat(valor) : null;
|
||||
});
|
||||
|
||||
diasSemana.forEach(dia => {
|
||||
const valor = preciosDia[dia].trim();
|
||||
const key = dia.toLowerCase() as keyof typeof preciosNumericos;
|
||||
preciosNumericos[key] = valor ? parseFloat(valor) : null;
|
||||
});
|
||||
|
||||
if (isEditing && initialData) {
|
||||
const dataToSubmit: UpdatePrecioDto = {
|
||||
vigenciaH: vigenciaH.trim() ? vigenciaH : null,
|
||||
...preciosNumericos // Spread de los precios de los días
|
||||
vigenciaH: vigenciaH.trim() ? vigenciaH.split('T')[0] : null,
|
||||
...preciosNumericos
|
||||
};
|
||||
await onSubmit(dataToSubmit, initialData.idPrecio);
|
||||
} else {
|
||||
const dataToSubmit: CreatePrecioDto = {
|
||||
idPublicacion,
|
||||
vigenciaD,
|
||||
...preciosNumericos
|
||||
idPublicacion,
|
||||
vigenciaD,
|
||||
...preciosNumericos
|
||||
};
|
||||
await onSubmit(dataToSubmit);
|
||||
}
|
||||
onClose();
|
||||
success = true; // Marcar como exitoso si no hubo excepciones
|
||||
} catch (error: any) {
|
||||
console.error("Error en submit de PrecioFormModal:", error);
|
||||
// El error ya fue seteado en el padre (GestionarPreciosPublicacionPage)
|
||||
// y se pasa como prop 'errorMessage' a este modal.
|
||||
// No necesitamos setearlo aquí de nuevo.
|
||||
console.error("Error propagado al submit de PrecioFormModal:", error);
|
||||
success = false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setLoading(false);
|
||||
if (success) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -163,51 +171,52 @@ const PrecioFormModal: React.FC<PrecioFormModalProps> = ({
|
||||
{isEditing ? 'Editar Período de Precio' : 'Agregar Nuevo Período de Precio'}
|
||||
</Typography>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||
{/* Sección de Vigencias */}
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap' }}>
|
||||
<TextField label="Vigencia Desde" type="date" value={vigenciaD} required={!isEditing}
|
||||
onChange={handleDateChange(setVigenciaD, 'vigenciaD')} margin="dense"
|
||||
error={!!localErrors.vigenciaD} helperText={localErrors.vigenciaD || ''}
|
||||
disabled={loading || isEditing}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
sx={{ flex: 1, minWidth: isEditing ? 'calc(50% - 8px)' : '100%' }}
|
||||
autoFocus={!isEditing}
|
||||
/>
|
||||
{isEditing && (
|
||||
<TextField label="Vigencia Hasta (Opcional)" type="date" value={vigenciaH}
|
||||
onChange={handleDateChange(setVigenciaH, 'vigenciaH')} margin="dense"
|
||||
error={!!localErrors.vigenciaH} helperText={localErrors.vigenciaH || ''}
|
||||
disabled={loading}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
sx={{ flex: 1, minWidth: 'calc(50% - 8px)' }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{/* Sección de Vigencias */}
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap' }}>
|
||||
<TextField label="Vigencia Desde" type="date" value={vigenciaD} required={!isEditing}
|
||||
onChange={handleDateChange(setVigenciaD, 'vigenciaD')} margin="dense"
|
||||
error={!!localErrors.vigenciaD} helperText={localErrors.vigenciaD || ''}
|
||||
disabled={loading || isEditing}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
sx={{ flex: 1, minWidth: isEditing ? 'calc(50% - 8px)' : '100%' }}
|
||||
autoFocus={!isEditing}
|
||||
/>
|
||||
{isEditing && (
|
||||
<TextField label="Vigencia Hasta (Opcional)" type="date" value={vigenciaH}
|
||||
onChange={handleDateChange(setVigenciaH, 'vigenciaH')} margin="dense"
|
||||
error={!!localErrors.vigenciaH} helperText={localErrors.vigenciaH || ''}
|
||||
disabled={loading}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
sx={{ flex: 1, minWidth: 'calc(50% - 8px)' }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Sección de Precios por Día con Flexbox */}
|
||||
<Typography variant="subtitle1" sx={{mt: 2, mb: 1}}>Precios por Día:</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
|
||||
{diasSemana.map(dia => (
|
||||
<Box key={dia} sx={{ flexBasis: { xs: 'calc(50% - 8px)', sm: 'calc(33.33% - 11px)', md: 'calc(25% - 12px)' }, minWidth: '120px' }}>
|
||||
{/* El ajuste de -Xpx en flexBasis es aproximado para compensar el gap.
|
||||
{/* Sección de Precios por Día con Flexbox */}
|
||||
<Typography variant="subtitle1" sx={{ mt: 2, mb: 1 }}>Precios por Día:</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
|
||||
{diasSemana.map(dia => (
|
||||
<Box key={dia} sx={{ flexBasis: { xs: 'calc(50% - 8px)', sm: 'calc(33.33% - 11px)', md: 'calc(25% - 12px)' }, minWidth: '120px' }}>
|
||||
{/* El ajuste de -Xpx en flexBasis es aproximado para compensar el gap.
|
||||
Para 3 columnas (33.33%): gap es 16px, se distribuye entre 2 espacios -> 16/2 * 2/3 = ~11px
|
||||
Para 4 columnas (25%): gap es 16px, se distribuye entre 3 espacios -> 16/3 * 3/4 = 12px
|
||||
*/}
|
||||
<TextField label={dia} type="number"
|
||||
value={preciosDia[dia]}
|
||||
onChange={(e) => handlePrecioDiaChange(dia, e.target.value)}
|
||||
margin="dense" fullWidth
|
||||
error={!!localErrors[dia.toLowerCase()]} helperText={localErrors[dia.toLowerCase()] || ''}
|
||||
disabled={loading}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
|
||||
inputProps={{ step: "0.01", lang:"es-AR" }}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
{localErrors.dias && <Alert severity="error" sx={{width: '100%', mt:1}}>{localErrors.dias}</Alert>}
|
||||
<TextField label={dia} type="number"
|
||||
value={preciosDia[dia]}
|
||||
onChange={(e) => handlePrecioDiaChange(dia, e.target.value)}
|
||||
margin="dense" fullWidth
|
||||
error={!!localErrors[dia.toLowerCase()]} helperText={localErrors[dia.toLowerCase()] || ''}
|
||||
disabled={loading}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
|
||||
inputProps={{ step: "0.01", lang: "es-AR" }}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
{localErrors.dias && <Alert severity="error" sx={{ width: '100%', mt: 1 }}>{localErrors.dias}</Alert>}
|
||||
|
||||
|
||||
{/* Mostrar error de la API si existe (pasado desde el padre) */}
|
||||
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||
|
||||
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// src/components/Modals/Distribucion/RecargoZonaFormModal.tsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||
@@ -6,10 +7,10 @@ import {
|
||||
import type { RecargoZonaDto } from '../../../models/dtos/Distribucion/RecargoZonaDto';
|
||||
import type { CreateRecargoZonaDto } from '../../../models/dtos/Distribucion/CreateRecargoZonaDto';
|
||||
import type { UpdateRecargoZonaDto } from '../../../models/dtos/Distribucion/UpdateRecargoZonaDto';
|
||||
import type { ZonaDto } from '../../../models/dtos/Zonas/ZonaDto'; // Para el dropdown de zonas
|
||||
import zonaService from '../../../services/Distribucion/zonaService'; // Para cargar zonas
|
||||
import type { ZonaDto } from '../../../models/dtos/Zonas/ZonaDto';
|
||||
import zonaService from '../../../services/Distribucion/zonaService';
|
||||
|
||||
const modalStyle = { /* ... (mismo estilo) ... */
|
||||
const modalStyle = {
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
@@ -28,7 +29,7 @@ interface RecargoZonaFormModalProps {
|
||||
onClose: () => void;
|
||||
onSubmit: (data: CreateRecargoZonaDto | UpdateRecargoZonaDto, idRecargo?: number) => Promise<void>;
|
||||
idPublicacion: number;
|
||||
initialData?: RecargoZonaDto | null; // Para editar
|
||||
initialData?: RecargoZonaDto | null;
|
||||
errorMessage?: string | null;
|
||||
clearErrorMessage: () => void;
|
||||
}
|
||||
@@ -39,12 +40,12 @@ const RecargoZonaFormModal: React.FC<RecargoZonaFormModalProps> = ({
|
||||
onSubmit,
|
||||
idPublicacion,
|
||||
initialData,
|
||||
errorMessage,
|
||||
errorMessage, // Esta prop viene del padre (GestionarRecargosPublicacionPage)
|
||||
clearErrorMessage
|
||||
}) => {
|
||||
const [idZona, setIdZona] = useState<number | string>('');
|
||||
const [vigenciaD, setVigenciaD] = useState(''); // "yyyy-MM-dd"
|
||||
const [vigenciaH, setVigenciaH] = useState(''); // "yyyy-MM-dd"
|
||||
const [vigenciaD, setVigenciaD] = useState('');
|
||||
const [vigenciaH, setVigenciaH] = useState('');
|
||||
const [valor, setValor] = useState<string>('');
|
||||
|
||||
const [zonas, setZonas] = useState<ZonaDto[]>([]);
|
||||
@@ -58,7 +59,7 @@ const RecargoZonaFormModal: React.FC<RecargoZonaFormModalProps> = ({
|
||||
const fetchZonas = async () => {
|
||||
setLoadingZonas(true);
|
||||
try {
|
||||
const data = await zonaService.getAllZonas(); // Asume que devuelve zonas activas
|
||||
const data = await zonaService.getAllZonas();
|
||||
setZonas(data);
|
||||
} catch (error) {
|
||||
console.error("Error al cargar zonas", error);
|
||||
@@ -75,11 +76,13 @@ const RecargoZonaFormModal: React.FC<RecargoZonaFormModalProps> = ({
|
||||
setVigenciaH(initialData?.vigenciaH || '');
|
||||
setValor(initialData?.valor?.toString() || '');
|
||||
setLocalErrors({});
|
||||
clearErrorMessage();
|
||||
// No limpiar errorMessage aquí si queremos que persista un error de submit anterior
|
||||
// clearErrorMessage(); // Se limpia al abrir desde la página o al inicio de un nuevo submit
|
||||
}
|
||||
}, [open, initialData, clearErrorMessage]);
|
||||
}, [open, initialData]); // Quitar clearErrorMessage de aquí para que no limpie el error del padre en cada re-render por 'open'
|
||||
|
||||
const validate = (): boolean => {
|
||||
// ... (lógica de validación sin cambios)
|
||||
const errors: { [key: string]: string | null } = {};
|
||||
if (!idZona) errors.idZona = 'Debe seleccionar una zona.';
|
||||
if (!isEditing && !vigenciaD.trim()) {
|
||||
@@ -102,38 +105,46 @@ const RecargoZonaFormModal: React.FC<RecargoZonaFormModalProps> = ({
|
||||
|
||||
const handleInputChange = (fieldName: string) => {
|
||||
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||
if (errorMessage) clearErrorMessage();
|
||||
if (errorMessage) clearErrorMessage(); // Limpiar error del padre si el usuario modifica un campo
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
clearErrorMessage();
|
||||
clearErrorMessage(); // Limpiar errores del padre antes de un nuevo intento
|
||||
if (!validate()) return;
|
||||
|
||||
setLoading(true);
|
||||
let success = false; // Bandera para controlar el cierre del modal
|
||||
try {
|
||||
const valorNum = parseFloat(valor);
|
||||
|
||||
if (isEditing && initialData) {
|
||||
const dataToSubmit: UpdateRecargoZonaDto = {
|
||||
valor: valorNum,
|
||||
vigenciaH: vigenciaH.trim() ? vigenciaH : null,
|
||||
vigenciaH: vigenciaH.trim() ? vigenciaH.split('T')[0] : null, // Enviar solo fecha o null
|
||||
};
|
||||
await onSubmit(dataToSubmit, initialData.idRecargo);
|
||||
} else {
|
||||
const dataToSubmit: CreateRecargoZonaDto = {
|
||||
idPublicacion,
|
||||
idZona: Number(idZona),
|
||||
vigenciaD,
|
||||
vigenciaD: vigenciaD.split('T')[0], // Enviar solo fecha
|
||||
valor: valorNum,
|
||||
};
|
||||
await onSubmit(dataToSubmit);
|
||||
}
|
||||
onClose();
|
||||
success = true; // Marcar como exitoso si no hubo excepciones
|
||||
} catch (error: any) {
|
||||
console.error("Error en submit de RecargoZonaFormModal:", error);
|
||||
// El error de la API ya fue seteado en la página padre (GestionarRecargosPublicacionPage)
|
||||
// y se pasa a este modal a través de la prop 'errorMessage'.
|
||||
// No necesitamos hacer setApiErrorMessage aquí.
|
||||
console.error("Error propagado al submit de RecargoZonaFormModal:", error);
|
||||
success = false; // Marcar como no exitoso
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (success) { // Solo cerrar si la operación fue exitosa
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,7 +159,7 @@ const RecargoZonaFormModal: React.FC<RecargoZonaFormModalProps> = ({
|
||||
<InputLabel id="zona-recargo-select-label" required>Zona</InputLabel>
|
||||
<Select labelId="zona-recargo-select-label" label="Zona" value={idZona}
|
||||
onChange={(e) => {setIdZona(e.target.value as number); handleInputChange('idZona');}}
|
||||
disabled={loading || loadingZonas || isEditing} // Zona no se edita
|
||||
disabled={loading || loadingZonas || isEditing}
|
||||
>
|
||||
<MenuItem value="" disabled><em>Seleccione una zona</em></MenuItem>
|
||||
{zonas.map((z) => (<MenuItem key={z.idZona} value={z.idZona}>{z.nombre}</MenuItem>))}
|
||||
@@ -175,6 +186,7 @@ const RecargoZonaFormModal: React.FC<RecargoZonaFormModalProps> = ({
|
||||
inputProps={{ step: "0.01", lang:"es-AR" }}
|
||||
/>
|
||||
|
||||
{/* Este Alert mostrará el error de la API que viene de la página padre */}
|
||||
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||
{localErrors.zonas && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.zonas}</Alert>}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// src/components/Modals/Impresion/StockBobinaCambioEstadoModal.tsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||
@@ -6,13 +7,14 @@ import {
|
||||
import type { StockBobinaDto } from '../../../models/dtos/Impresion/StockBobinaDto';
|
||||
import type { CambiarEstadoBobinaDto } from '../../../models/dtos/Impresion/CambiarEstadoBobinaDto';
|
||||
import type { EstadoBobinaDto } from '../../../models/dtos/Impresion/EstadoBobinaDto';
|
||||
import type { PublicacionDto } from '../../../models/dtos/Distribucion/PublicacionDto';
|
||||
import type { PubliSeccionDto } from '../../../models/dtos/Distribucion/PubliSeccionDto'; // Asumiendo que tienes este DTO
|
||||
import estadoBobinaService from '../../../services/Impresion/estadoBobinaService'; // Para cargar estados
|
||||
import publicacionService from '../../../services/Distribucion/publicacionService'; // Para cargar publicaciones
|
||||
import publiSeccionService from '../../../services/Distribucion/publiSeccionService'; // Para cargar secciones
|
||||
// --- CAMBIO: Importar PublicacionDropdownDto ---
|
||||
import type { PublicacionDropdownDto } from '../../../models/dtos/Distribucion/PublicacionDropdownDto';
|
||||
import type { PubliSeccionDto } from '../../../models/dtos/Distribucion/PubliSeccionDto';
|
||||
import estadoBobinaService from '../../../services/Impresion/estadoBobinaService';
|
||||
import publicacionService from '../../../services/Distribucion/publicacionService';
|
||||
import publiSeccionService from '../../../services/Distribucion/publiSeccionService';
|
||||
|
||||
const modalStyle = { /* ... (mismo estilo) ... */
|
||||
const modalStyle = {
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
@@ -26,16 +28,15 @@ const modalStyle = { /* ... (mismo estilo) ... */
|
||||
overflowY: 'auto'
|
||||
};
|
||||
|
||||
// IDs de estados conocidos (ajusta según tu BD)
|
||||
const ID_ESTADO_EN_USO = 2;
|
||||
const ID_ESTADO_DISPONIBLE = 1;
|
||||
const ID_ESTADO_EN_USO = 2; // Usaremos este consistentemente
|
||||
const ID_ESTADO_DANADA = 3;
|
||||
// const ID_ESTADO_DISPONIBLE = 1; // No se cambia a Disponible desde este modal
|
||||
|
||||
interface StockBobinaCambioEstadoModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (idBobina: number, data: CambiarEstadoBobinaDto) => Promise<void>;
|
||||
bobinaActual: StockBobinaDto | null; // La bobina cuyo estado se va a cambiar
|
||||
bobinaActual: StockBobinaDto | null;
|
||||
errorMessage?: string | null;
|
||||
clearErrorMessage: () => void;
|
||||
}
|
||||
@@ -55,36 +56,47 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
|
||||
const [fechaCambioEstado, setFechaCambioEstado] = useState('');
|
||||
|
||||
const [estadosDisponibles, setEstadosDisponibles] = useState<EstadoBobinaDto[]>([]);
|
||||
const [publicacionesDisponibles, setPublicacionesDisponibles] = useState<PublicacionDto[]>([]);
|
||||
// --- CAMBIO: Usar PublicacionDropdownDto para el estado ---
|
||||
const [publicacionesDisponibles, setPublicacionesDisponibles] = useState<PublicacionDropdownDto[]>([]);
|
||||
const [seccionesDisponibles, setSeccionesDisponibles] = useState<PubliSeccionDto[]>([]);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
const fetchDropdownData = async () => {
|
||||
if (!bobinaActual) return;
|
||||
setLoadingDropdowns(true);
|
||||
try {
|
||||
const estadosData = await estadoBobinaService.getAllEstadosBobina();
|
||||
// Filtrar estados: no se puede volver a "Disponible" o al mismo estado actual desde aquí.
|
||||
// Y si está "Dañada", no se puede cambiar.
|
||||
let estadosFiltrados = estadosData.filter(e => e.idEstadoBobina !== bobinaActual.idEstadoBobina && e.idEstadoBobina !== 1);
|
||||
if (bobinaActual.idEstadoBobina === ID_ESTADO_DANADA) { // Si ya está dañada, no hay más cambios
|
||||
estadosFiltrados = [];
|
||||
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) { // Si está en uso, solo puede pasar a Dañada
|
||||
estadosFiltrados = estadosData.filter(e => e.idEstadoBobina === ID_ESTADO_DANADA);
|
||||
}
|
||||
const todosLosEstados = await estadoBobinaService.getAllEstadosBobina();
|
||||
let estadosFiltrados: EstadoBobinaDto[];
|
||||
|
||||
if (bobinaActual.idEstadoBobina === ID_ESTADO_DANADA) {
|
||||
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina === ID_ESTADO_DISPONIBLE);
|
||||
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) {
|
||||
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina === ID_ESTADO_DANADA);
|
||||
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_DISPONIBLE) {
|
||||
// --- CAMBIO: Usar ID_ESTADO_EN_USO ---
|
||||
estadosFiltrados = todosLosEstados.filter(
|
||||
e => e.idEstadoBobina === ID_ESTADO_EN_USO || e.idEstadoBobina === ID_ESTADO_DANADA
|
||||
);
|
||||
} else {
|
||||
estadosFiltrados = todosLosEstados.filter(e => e.idEstadoBobina !== bobinaActual.idEstadoBobina);
|
||||
}
|
||||
|
||||
setEstadosDisponibles(estadosFiltrados);
|
||||
|
||||
if (estadosFiltrados.some(e => e.idEstadoBobina === ID_ESTADO_EN_USO)) { // Solo cargar publicaciones si "En Uso" es una opción
|
||||
const publicacionesData = await publicacionService.getAllPublicaciones(undefined, undefined, true); // Solo habilitadas
|
||||
const sePuedePonerEnUso = estadosFiltrados.some(e => e.idEstadoBobina === ID_ESTADO_EN_USO);
|
||||
|
||||
if (sePuedePonerEnUso) {
|
||||
// --- CAMBIO: La data es PublicacionDropdownDto[] ---
|
||||
const publicacionesData: PublicacionDropdownDto[] = await publicacionService.getPublicacionesForDropdown(true);
|
||||
setPublicacionesDisponibles(publicacionesData);
|
||||
} else {
|
||||
setPublicacionesDisponibles([]);
|
||||
setIdPublicacion('');
|
||||
setIdSeccion('');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -98,42 +110,67 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
|
||||
if (open && bobinaActual) {
|
||||
fetchDropdownData();
|
||||
setNuevoEstadoId('');
|
||||
setIdPublicacion('');
|
||||
setIdSeccion('');
|
||||
setObs(bobinaActual.obs || ''); // Pre-cargar obs existente
|
||||
setFechaCambioEstado(new Date().toISOString().split('T')[0]); // Default a hoy
|
||||
// Pre-cargar basado en si la bobina actual está "En Uso"
|
||||
if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) {
|
||||
setIdPublicacion(bobinaActual.idPublicacion?.toString() || '');
|
||||
// Solo pre-cargar sección si la publicación también estaba pre-cargada
|
||||
if (bobinaActual.idPublicacion) {
|
||||
setIdSeccion(bobinaActual.idSeccion?.toString() || '');
|
||||
} else {
|
||||
setIdSeccion('');
|
||||
}
|
||||
} else {
|
||||
setIdPublicacion('');
|
||||
setIdSeccion('');
|
||||
}
|
||||
setObs(bobinaActual.obs || '');
|
||||
setFechaCambioEstado(new Date().toISOString().split('T')[0]);
|
||||
setLocalErrors({});
|
||||
clearErrorMessage();
|
||||
}
|
||||
}, [open, bobinaActual, clearErrorMessage]);
|
||||
|
||||
|
||||
// Cargar secciones cuando cambia la publicación seleccionada y el estado es "En Uso"
|
||||
useEffect(() => {
|
||||
const fetchSecciones = async () => {
|
||||
if (nuevoEstadoId === ID_ESTADO_EN_USO && idPublicacion) {
|
||||
setLoadingDropdowns(true); // Podrías tener un loader específico para secciones
|
||||
if (Number(nuevoEstadoId) === ID_ESTADO_EN_USO && idPublicacion) {
|
||||
setLoadingDropdowns(true);
|
||||
try {
|
||||
const data = await publiSeccionService.getSeccionesPorPublicacion(Number(idPublicacion), true); // Solo activas
|
||||
const data = await publiSeccionService.getSeccionesPorPublicacion(Number(idPublicacion), true);
|
||||
setSeccionesDisponibles(data);
|
||||
} catch (error) {
|
||||
console.error("Error al cargar secciones:", error);
|
||||
setLocalErrors(prev => ({ ...prev, secciones: 'Error al cargar secciones.'}));
|
||||
setSeccionesDisponibles([]);
|
||||
} finally {
|
||||
setLoadingDropdowns(false);
|
||||
}
|
||||
} else {
|
||||
setSeccionesDisponibles([]); // Limpiar secciones si no aplica
|
||||
setSeccionesDisponibles([]);
|
||||
// No es necesario setIdSeccion('') aquí si el useEffect de nuevoEstadoId ya lo hace.
|
||||
}
|
||||
};
|
||||
fetchSecciones();
|
||||
if (idPublicacion && Number(nuevoEstadoId) === ID_ESTADO_EN_USO) { // Solo fetchear si hay idPublicacion
|
||||
fetchSecciones();
|
||||
} else {
|
||||
setSeccionesDisponibles([]); // Limpiar si no se cumplen condiciones
|
||||
}
|
||||
}, [nuevoEstadoId, idPublicacion]);
|
||||
|
||||
|
||||
// Efecto para limpiar publicacion/seccion si el nuevo estado no es "En Uso"
|
||||
useEffect(() => {
|
||||
if (Number(nuevoEstadoId) !== ID_ESTADO_EN_USO) {
|
||||
setIdPublicacion('');
|
||||
setIdSeccion('');
|
||||
}
|
||||
}, [nuevoEstadoId]);
|
||||
|
||||
|
||||
const validate = (): boolean => {
|
||||
const errors: { [key: string]: string | null } = {};
|
||||
if (!nuevoEstadoId) errors.nuevoEstadoId = 'Seleccione un nuevo estado.';
|
||||
if (!fechaCambioEstado.trim()) errors.fechaCambioEstado = 'La fecha de cambio es obligatoria.';
|
||||
if (!fechaCambioEstado.trim()) errors.fechaCambioEstado = 'La fecha es obligatoria.';
|
||||
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fechaCambioEstado)) errors.fechaCambioEstado = 'Formato de fecha inválido.';
|
||||
|
||||
if (Number(nuevoEstadoId) === ID_ESTADO_EN_USO) {
|
||||
@@ -147,11 +184,9 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
|
||||
const handleInputChange = (fieldName: string) => {
|
||||
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||
if (errorMessage) clearErrorMessage();
|
||||
if (fieldName === 'nuevoEstadoId') { // Si cambia el estado, resetear pub/secc
|
||||
setIdPublicacion('');
|
||||
setIdSeccion('');
|
||||
}
|
||||
if (fieldName === 'idPublicacion') { // Si cambia la publicación, resetear seccion
|
||||
// La lógica de limpieza de pub/secc se movió a un useEffect dedicado a nuevoEstadoId
|
||||
// y el de sección a un useEffect de idPublicacion
|
||||
if (fieldName === 'idPublicacion') { // Si cambia la publicación, resetear seccion
|
||||
setIdSeccion('');
|
||||
}
|
||||
};
|
||||
@@ -163,11 +198,12 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const esEnUso = Number(nuevoEstadoId) === ID_ESTADO_EN_USO;
|
||||
const dataToSubmit: CambiarEstadoBobinaDto = {
|
||||
nuevoEstadoId: Number(nuevoEstadoId),
|
||||
idPublicacion: Number(nuevoEstadoId) === ID_ESTADO_EN_USO ? Number(idPublicacion) : null,
|
||||
idSeccion: Number(nuevoEstadoId) === ID_ESTADO_EN_USO ? Number(idSeccion) : null,
|
||||
obs: obs || undefined,
|
||||
idPublicacion: esEnUso && idPublicacion ? Number(idPublicacion) : null,
|
||||
idSeccion: esEnUso && idPublicacion && idSeccion ? Number(idSeccion) : null,
|
||||
obs: obs.trim() || null,
|
||||
fechaCambioEstado,
|
||||
};
|
||||
await onSubmit(bobinaActual.idBobina, dataToSubmit);
|
||||
@@ -192,10 +228,16 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
|
||||
</Typography>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.nuevoEstadoId}>
|
||||
<InputLabel id="nuevo-estado-select-label" required>Nuevo Estado</InputLabel>
|
||||
<Select labelId="nuevo-estado-select-label" label="Nuevo Estado" value={nuevoEstadoId}
|
||||
onChange={(e) => {setNuevoEstadoId(e.target.value as number); handleInputChange('nuevoEstadoId');}}
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.nuevoEstadoId} required>
|
||||
<InputLabel id="nuevo-estado-select-label">Nuevo Estado</InputLabel>
|
||||
<Select
|
||||
labelId="nuevo-estado-select-label"
|
||||
label="Nuevo Estado"
|
||||
value={nuevoEstadoId}
|
||||
onChange={(e) => {
|
||||
setNuevoEstadoId(e.target.value as number | string);
|
||||
handleInputChange('nuevoEstadoId');
|
||||
}}
|
||||
disabled={loading || loadingDropdowns || estadosDisponibles.length === 0}
|
||||
>
|
||||
<MenuItem value="" disabled><em>Seleccione un estado</em></MenuItem>
|
||||
@@ -206,24 +248,24 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
|
||||
|
||||
{Number(nuevoEstadoId) === ID_ESTADO_EN_USO && (
|
||||
<>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion}>
|
||||
<InputLabel id="publicacion-estado-select-label" required>Publicación</InputLabel>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion} required>
|
||||
<InputLabel id="publicacion-estado-select-label">Publicación</InputLabel>
|
||||
<Select labelId="publicacion-estado-select-label" label="Publicación" value={idPublicacion}
|
||||
onChange={(e) => {setIdPublicacion(e.target.value as number); handleInputChange('idPublicacion');}}
|
||||
disabled={loading || loadingDropdowns}
|
||||
disabled={loading || loadingDropdowns || publicacionesDisponibles.length === 0}
|
||||
>
|
||||
<MenuItem value="" disabled><em>Seleccione publicación</em></MenuItem>
|
||||
{publicacionesDisponibles.map((p) => (<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>))}
|
||||
</Select>
|
||||
{localErrors.idPublicacion && <Typography color="error" variant="caption">{localErrors.idPublicacion}</Typography>}
|
||||
</FormControl>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idSeccion}>
|
||||
<InputLabel id="seccion-estado-select-label" required>Sección</InputLabel>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idSeccion} required>
|
||||
<InputLabel id="seccion-estado-select-label">Sección</InputLabel>
|
||||
<Select labelId="seccion-estado-select-label" label="Sección" value={idSeccion}
|
||||
onChange={(e) => {setIdSeccion(e.target.value as number); handleInputChange('idSeccion');}}
|
||||
disabled={loading || loadingDropdowns || !idPublicacion || seccionesDisponibles.length === 0}
|
||||
>
|
||||
<MenuItem value="" disabled><em>{idPublicacion ? 'Seleccione sección' : 'Seleccione publicación primero'}</em></MenuItem>
|
||||
<MenuItem value="" disabled><em>{idPublicacion ? (seccionesDisponibles.length > 0 ? 'Seleccione sección' : 'No hay secciones para esta pub.') : 'Seleccione publicación primero'}</em></MenuItem>
|
||||
{seccionesDisponibles.map((s) => (<MenuItem key={s.idSeccion} value={s.idSeccion}>{s.nombre}</MenuItem>))}
|
||||
</Select>
|
||||
{localErrors.idSeccion && <Typography color="error" variant="caption">{localErrors.idSeccion}</Typography>}
|
||||
@@ -248,7 +290,8 @@ const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps>
|
||||
|
||||
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns || estadosDisponibles.length === 0}>
|
||||
<Button type="submit" variant="contained"
|
||||
disabled={loading || loadingDropdowns || (estadosDisponibles.length === 0 && bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) }>
|
||||
{loading ? <CircularProgress size={24} /> : 'Guardar Cambio de Estado'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||
FormControl, InputLabel, Select, MenuItem
|
||||
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||
FormControl, InputLabel, Select, MenuItem
|
||||
} from '@mui/material';
|
||||
import type { StockBobinaDto } from '../../../models/dtos/Impresion/StockBobinaDto';
|
||||
import type { UpdateStockBobinaDto } from '../../../models/dtos/Impresion/UpdateStockBobinaDto';
|
||||
@@ -11,17 +11,17 @@ import tipoBobinaService from '../../../services/Impresion/tipoBobinaService';
|
||||
import plantaService from '../../../services/Impresion/plantaService';
|
||||
|
||||
const modalStyle = { /* ... (mismo estilo que StockBobinaIngresoFormModal) ... */
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: { xs: '90%', sm: 550 },
|
||||
bgcolor: 'background.paper',
|
||||
border: '2px solid #000',
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
maxHeight: '90vh',
|
||||
overflowY: 'auto'
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: { xs: '90%', sm: 550 },
|
||||
bgcolor: 'background.paper',
|
||||
border: '2px solid #000',
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
maxHeight: '90vh',
|
||||
overflowY: 'auto'
|
||||
};
|
||||
|
||||
interface StockBobinaEditFormModalProps {
|
||||
@@ -56,34 +56,43 @@ const StockBobinaEditFormModal: React.FC<StockBobinaEditFormModalProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDropdownData = async () => {
|
||||
setLoadingDropdowns(true);
|
||||
try {
|
||||
const [tiposData, plantasData] = await Promise.all([
|
||||
tipoBobinaService.getAllTiposBobina(),
|
||||
plantaService.getAllPlantas()
|
||||
]);
|
||||
setTiposBobina(tiposData);
|
||||
setPlantas(plantasData);
|
||||
} catch (error) {
|
||||
console.error("Error al cargar datos para dropdowns (StockBobina Edit)", error);
|
||||
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar tipos/plantas.'}));
|
||||
} finally {
|
||||
setLoadingDropdowns(false);
|
||||
}
|
||||
setLoadingDropdowns(true); // Ya está arriba, pero por claridad
|
||||
try {
|
||||
// --- CAMBIO: Usar los servicios que devuelven DTOs de dropdown si los tienes ---
|
||||
const [tiposData, plantasData] = await Promise.all([
|
||||
tipoBobinaService.getAllTiposBobina(), // Asume que existe y devuelve TipoBobinaDropdownDto[]
|
||||
plantaService.getAllPlantas() // Asume que existe y devuelve PlantaDropdownDto[]
|
||||
]);
|
||||
setTiposBobina(tiposData);
|
||||
setPlantas(plantasData);
|
||||
} catch (error) {
|
||||
console.error("Error al cargar datos para dropdowns (StockBobina Edit)", error);
|
||||
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar tipos/plantas.' }));
|
||||
} finally {
|
||||
setLoadingDropdowns(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (open && initialData) {
|
||||
fetchDropdownData();
|
||||
setIdTipoBobina(initialData.idTipoBobina || '');
|
||||
setNroBobina(initialData.nroBobina || '');
|
||||
setPeso(initialData.peso?.toString() || '');
|
||||
setIdPlanta(initialData.idPlanta || '');
|
||||
setRemito(initialData.remito || '');
|
||||
setFechaRemito(initialData.fechaRemito || ''); // Asume yyyy-MM-dd del DTO
|
||||
setLocalErrors({});
|
||||
clearErrorMessage();
|
||||
if (open) { // Solo fetchear si el modal está abierto
|
||||
fetchDropdownData();
|
||||
}
|
||||
}, [open, initialData, clearErrorMessage]);
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && initialData && !loadingDropdowns) { // <<--- ESPERAR A QUE loadingDropdowns SEA FALSE
|
||||
setIdTipoBobina(initialData.idTipoBobina || '');
|
||||
setNroBobina(initialData.nroBobina || '');
|
||||
setPeso(initialData.peso?.toString() || '');
|
||||
setIdPlanta(initialData.idPlanta || '');
|
||||
setRemito(initialData.remito || '');
|
||||
setFechaRemito(initialData.fechaRemito ? initialData.fechaRemito.split('T')[0] : ''); // Formatear si es necesario
|
||||
setLocalErrors({});
|
||||
clearErrorMessage();
|
||||
} else if (open && !initialData) { // Si se abre para crear (aunque este modal es de edición)
|
||||
// Resetear o manejar como prefieras
|
||||
setIdTipoBobina(''); setNroBobina(''); setPeso(''); setIdPlanta('');
|
||||
setRemito(''); setFechaRemito(new Date().toISOString().split('T')[0]);
|
||||
}
|
||||
}, [open, initialData, loadingDropdowns, clearErrorMessage]); // Añadir loadingDropdowns
|
||||
|
||||
const validate = (): boolean => {
|
||||
const errors: { [key: string]: string | null } = {};
|
||||
@@ -111,12 +120,12 @@ const StockBobinaEditFormModal: React.FC<StockBobinaEditFormModalProps> = ({
|
||||
setLoading(true);
|
||||
try {
|
||||
const dataToSubmit: UpdateStockBobinaDto = {
|
||||
idTipoBobina: Number(idTipoBobina),
|
||||
nroBobina,
|
||||
peso: parseInt(peso, 10),
|
||||
idPlanta: Number(idPlanta),
|
||||
remito,
|
||||
fechaRemito,
|
||||
idTipoBobina: Number(idTipoBobina),
|
||||
nroBobina,
|
||||
peso: parseInt(peso, 10),
|
||||
idPlanta: Number(idPlanta),
|
||||
remito,
|
||||
fechaRemito,
|
||||
};
|
||||
await onSubmit(initialData.idBobina, dataToSubmit);
|
||||
onClose();
|
||||
@@ -124,80 +133,91 @@ const StockBobinaEditFormModal: React.FC<StockBobinaEditFormModalProps> = ({
|
||||
console.error("Error en submit de StockBobinaEditFormModal:", error);
|
||||
// El error de API lo maneja la página que llama a este modal
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!initialData) return null; // No renderizar si no hay datos iniciales (aunque open lo controla)
|
||||
if (!initialData && open) { // Si se abre sin initialData (no debería pasar para un modal de EDICIÓN)
|
||||
return <Modal open={open} onClose={onClose}><Box sx={modalStyle}><Alert severity="error">Error: No se proporcionaron datos iniciales para editar.</Alert></Box></Modal>;
|
||||
}
|
||||
if (!open) return null; // No renderizar nada si no está abierto
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<Box sx={modalStyle}>
|
||||
<Typography variant="h6" component="h2" gutterBottom>
|
||||
Editar Datos de Bobina (ID: {initialData.idBobina})
|
||||
Editar Datos de Bobina (ID: {initialData?.idBobina || 'N/A'}) {/* Usar initialData?.idBobina */}
|
||||
</Typography>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idTipoBobina} sx={{flex:1, minWidth: '200px'}}>
|
||||
<InputLabel id="edit-tipo-bobina-select-label" required>Tipo Bobina</InputLabel>
|
||||
<Select labelId="edit-tipo-bobina-select-label" label="Tipo Bobina" value={idTipoBobina}
|
||||
onChange={(e) => {setIdTipoBobina(e.target.value as number); handleInputChange('idTipoBobina');}}
|
||||
disabled={loading || loadingDropdowns}
|
||||
>
|
||||
<MenuItem value="" disabled><em>Seleccione un tipo</em></MenuItem>
|
||||
{tiposBobina.map((t) => (<MenuItem key={t.idTipoBobina} value={t.idTipoBobina}>{t.denominacion}</MenuItem>))}
|
||||
</Select>
|
||||
{localErrors.idTipoBobina && <Typography color="error" variant="caption">{localErrors.idTipoBobina}</Typography>}
|
||||
</FormControl>
|
||||
<TextField label="Nro. Bobina" value={nroBobina} required
|
||||
onChange={(e) => {setNroBobina(e.target.value); handleInputChange('nroBobina');}}
|
||||
margin="dense" fullWidth error={!!localErrors.nroBobina} helperText={localErrors.nroBobina || ''}
|
||||
disabled={loading} sx={{flex:1, minWidth: '200px'}} autoFocus
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||
<TextField label="Peso (Kg)" type="number" value={peso} required
|
||||
onChange={(e) => {setPeso(e.target.value); handleInputChange('peso');}}
|
||||
margin="dense" fullWidth error={!!localErrors.peso} helperText={localErrors.peso || ''}
|
||||
disabled={loading} sx={{flex:1, minWidth: '150px'}}
|
||||
/>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idPlanta} sx={{flex:1, minWidth: '200px'}}>
|
||||
<InputLabel id="edit-planta-select-label" required>Planta Destino</InputLabel>
|
||||
<Select labelId="edit-planta-select-label" label="Planta Destino" value={idPlanta}
|
||||
onChange={(e) => {setIdPlanta(e.target.value as number); handleInputChange('idPlanta');}}
|
||||
disabled={loading || loadingDropdowns}
|
||||
>
|
||||
<MenuItem value="" disabled><em>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">{localErrors.idPlanta}</Typography>}
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||
<TextField label="Nro. Remito" value={remito} required
|
||||
onChange={(e) => {setRemito(e.target.value); handleInputChange('remito');}}
|
||||
margin="dense" fullWidth error={!!localErrors.remito} helperText={localErrors.remito || ''}
|
||||
disabled={loading} sx={{flex:1, minWidth: '200px'}}
|
||||
/>
|
||||
<TextField label="Fecha Remito" type="date" value={fechaRemito} required
|
||||
onChange={(e) => {setFechaRemito(e.target.value); handleInputChange('fechaRemito');}}
|
||||
margin="dense" fullWidth error={!!localErrors.fechaRemito} helperText={localErrors.fechaRemito || ''}
|
||||
disabled={loading} InputLabelProps={{ shrink: true }} sx={{flex:1, minWidth: '200px'}}
|
||||
/>
|
||||
</Box>
|
||||
{loadingDropdowns ? <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box> : (
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idTipoBobina} sx={{ flex: 1, minWidth: '200px' }}>
|
||||
<InputLabel id="edit-tipo-bobina-select-label" required>Tipo Bobina</InputLabel>
|
||||
<Select
|
||||
labelId="edit-tipo-bobina-select-label"
|
||||
label="Tipo Bobina"
|
||||
value={idTipoBobina} // MUI Select maneja bien number/string si los values de MenuItem son consistentes
|
||||
onChange={(e) => { setIdTipoBobina(e.target.value as number | string); handleInputChange('idTipoBobina'); }}
|
||||
disabled={loading || loadingDropdowns}
|
||||
>
|
||||
<MenuItem value="" disabled><em>Seleccione un tipo</em></MenuItem>
|
||||
{tiposBobina.map((t) => (<MenuItem key={t.idTipoBobina} value={t.idTipoBobina}>{t.denominacion}</MenuItem>))}
|
||||
</Select>
|
||||
{localErrors.idTipoBobina && <Typography color="error" variant="caption">{localErrors.idTipoBobina}</Typography>}
|
||||
</FormControl>
|
||||
<TextField label="Nro. Bobina" value={nroBobina} required
|
||||
onChange={(e) => { setNroBobina(e.target.value); handleInputChange('nroBobina'); }}
|
||||
margin="dense" fullWidth error={!!localErrors.nroBobina} helperText={localErrors.nroBobina || ''}
|
||||
disabled={loading} sx={{ flex: 1, minWidth: '200px' }} autoFocus
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||
<TextField label="Peso (Kg)" type="number" value={peso} required
|
||||
onChange={(e) => { setPeso(e.target.value); handleInputChange('peso'); }}
|
||||
margin="dense" fullWidth error={!!localErrors.peso} helperText={localErrors.peso || ''}
|
||||
disabled={loading} sx={{ flex: 1, minWidth: '150px' }}
|
||||
/>
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idPlanta} sx={{ flex: 1, minWidth: '200px' }}>
|
||||
<InputLabel id="edit-planta-select-label" required>Planta Destino</InputLabel>
|
||||
<Select
|
||||
labelId="edit-planta-select-label"
|
||||
label="Planta Destino"
|
||||
value={idPlanta}
|
||||
onChange={(e) => { setIdPlanta(e.target.value as number | string); handleInputChange('idPlanta'); }}
|
||||
disabled={loading || loadingDropdowns}
|
||||
>
|
||||
<MenuItem value="" disabled><em>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">{localErrors.idPlanta}</Typography>}
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||
<TextField label="Nro. Remito" value={remito} required
|
||||
onChange={(e) => { setRemito(e.target.value); handleInputChange('remito'); }}
|
||||
margin="dense" fullWidth error={!!localErrors.remito} helperText={localErrors.remito || ''}
|
||||
disabled={loading} sx={{ flex: 1, minWidth: '200px' }}
|
||||
/>
|
||||
<TextField label="Fecha Remito" type="date" value={fechaRemito} required
|
||||
onChange={(e) => { setFechaRemito(e.target.value); handleInputChange('fechaRemito'); }}
|
||||
margin="dense" fullWidth error={!!localErrors.fechaRemito} helperText={localErrors.fechaRemito || ''}
|
||||
disabled={loading} InputLabelProps={{ shrink: true }} sx={{ flex: 1, minWidth: '200px' }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</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={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns}>
|
||||
{loading ? <CircularProgress size={24} /> : 'Guardar Cambios'}
|
||||
</Button>
|
||||
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns}>
|
||||
{loading ? <CircularProgress size={24} /> : 'Guardar Cambios'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface CambioParadaHistorialDto {
|
||||
id_Registro: number;
|
||||
id_Canilla: number;
|
||||
nombreCanilla: string;
|
||||
parada: string;
|
||||
vigenciaD: string;
|
||||
vigenciaH: string;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface ControlDevolucionesHistorialDto {
|
||||
id_Control: number;
|
||||
id_Empresa: number;
|
||||
// nombreEmpresa?: string;
|
||||
fecha: string; // Fecha original del control
|
||||
entrada: number;
|
||||
sobrantes: number;
|
||||
detalle?: string | null;
|
||||
sinCargo: number;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface EstadoBobinaHistorialDto {
|
||||
id_EstadoBobina: number;
|
||||
denominacion: string;
|
||||
obs?: string | null;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface OtroDestinoHistorialDto {
|
||||
id_Destino: number;
|
||||
nombre: string;
|
||||
obs?: string | null;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
12
Frontend/src/models/dtos/Auditoria/PerfilHistorialDto.ts
Normal file
12
Frontend/src/models/dtos/Auditoria/PerfilHistorialDto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface PerfilHistorialDto {
|
||||
idPerfil: number;
|
||||
perfil: string; // Nombre del perfil
|
||||
descPerfil?: string | null;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid (podría ser el IdHist de gral_Perfiles_H si se incluye)
|
||||
}
|
||||
14
Frontend/src/models/dtos/Auditoria/PermisoHistorialDto.ts
Normal file
14
Frontend/src/models/dtos/Auditoria/PermisoHistorialDto.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface PermisoHistorialDto {
|
||||
idHistorial: number; // PK de la tabla _H
|
||||
idPermiso: number;
|
||||
modulo: string;
|
||||
descPermiso: string;
|
||||
codAcc: string;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface PermisosPerfilesHistorialDto {
|
||||
idHistorial: number;
|
||||
idPerfil: number;
|
||||
nombrePerfil: string;
|
||||
idPermiso: number;
|
||||
descPermiso: string;
|
||||
codAccPermiso: string;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
12
Frontend/src/models/dtos/Auditoria/PlantaHistorialDto.ts
Normal file
12
Frontend/src/models/dtos/Auditoria/PlantaHistorialDto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface PlantaHistorialDto {
|
||||
id_Planta: number;
|
||||
nombre: string;
|
||||
detalle: string;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export interface PorcMonCanillaHistorialDto {
|
||||
id_PorcMon: number;
|
||||
id_Publicacion: number;
|
||||
// nombrePublicacion?: string;
|
||||
id_Canilla: number;
|
||||
// nombreCanilla?: string;
|
||||
vigenciaD: string;
|
||||
vigenciaH?: string | null;
|
||||
porcMon: number;
|
||||
esPorcentaje: boolean;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
17
Frontend/src/models/dtos/Auditoria/PorcPagoHistorialDto.ts
Normal file
17
Frontend/src/models/dtos/Auditoria/PorcPagoHistorialDto.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export interface PorcPagoHistorialDto {
|
||||
id_Porcentaje: number;
|
||||
id_Publicacion: number;
|
||||
// nombrePublicacion?: string;
|
||||
id_Distribuidor: number;
|
||||
// nombreDistribuidor?: string;
|
||||
vigenciaD: string;
|
||||
vigenciaH?: string | null;
|
||||
porcentaje: number;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
21
Frontend/src/models/dtos/Auditoria/PrecioHistorialDto.ts
Normal file
21
Frontend/src/models/dtos/Auditoria/PrecioHistorialDto.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export interface PrecioHistorialDto {
|
||||
id_Precio: number;
|
||||
id_Publicacion: number;
|
||||
// nombrePublicacion?: string;
|
||||
vigenciaD: string; // Fecha en formato string (YYYY-MM-DDTHH:mm:ss)
|
||||
vigenciaH?: string | null;
|
||||
lunes?: number | null;
|
||||
martes?: number | null;
|
||||
miercoles?: number | null;
|
||||
jueves?: number | null;
|
||||
viernes?: number | null;
|
||||
sabado?: number | null;
|
||||
domingo?: number | null;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export interface PubliSeccionHistorialDto {
|
||||
id_Seccion: number;
|
||||
id_Publicacion: number;
|
||||
// nombrePublicacion?: string;
|
||||
nombre: string; // Nombre de la sección
|
||||
estado: boolean;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export interface PublicacionHistorialDto {
|
||||
id_Publicacion: number;
|
||||
nombre: string;
|
||||
observacion?: string | null;
|
||||
id_Empresa: number;
|
||||
// nombreEmpresa?: string;
|
||||
// ctrlDevoluciones: boolean; // Si la añades
|
||||
habilitada: boolean | null; // Es nullable en la tabla _H
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface RecargoZonaHistorialDto {
|
||||
id_Recargo: number;
|
||||
id_Publicacion: number;
|
||||
// nombrePublicacion?: string;
|
||||
id_Zona: number;
|
||||
// nombreZona?: string;
|
||||
vigenciaD: string; // Fecha en formato string
|
||||
vigenciaH?: string | null;
|
||||
valor: number;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export interface RegSeccionTiradaHistorialDto {
|
||||
id_Tirada: number; // ID del registro original en bob_RegPublicaciones
|
||||
id_Publicacion: number;
|
||||
nombrePublicacion: string;
|
||||
id_Seccion: number;
|
||||
nombreSeccion: string;
|
||||
cantPag: number;
|
||||
fecha: string; // Fecha original de la tirada
|
||||
id_Planta: number;
|
||||
nombrePlanta: string;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
16
Frontend/src/models/dtos/Auditoria/RegTiradaHistorialDto.ts
Normal file
16
Frontend/src/models/dtos/Auditoria/RegTiradaHistorialDto.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface RegTiradaHistorialDto {
|
||||
id_Registro: number;
|
||||
ejemplares: number;
|
||||
id_Publicacion: number;
|
||||
nombrePublicacion: string;
|
||||
fecha: string; // Fecha original de la tirada
|
||||
id_Planta: number;
|
||||
nombrePlanta: string;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export interface StockBobinaHistorialDto {
|
||||
id_Bobina: number;
|
||||
id_TipoBobina: number;
|
||||
nombreTipoBobina: string;
|
||||
nroBobina: string;
|
||||
peso: number;
|
||||
id_Planta: number;
|
||||
nombrePlanta: string;
|
||||
id_EstadoBobina: number;
|
||||
nombreEstadoBobina: string;
|
||||
remito: string;
|
||||
fechaRemito: string; // "YYYY-MM-DDTHH:mm:ss"
|
||||
fechaEstado?: string | null;
|
||||
id_Publicacion?: number | null;
|
||||
nombrePublicacion?: string | null;
|
||||
id_Seccion?: number | null;
|
||||
nombreSeccion?: string | null;
|
||||
obs?: string | null;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
11
Frontend/src/models/dtos/Auditoria/TipoBobinaHistorialDto.ts
Normal file
11
Frontend/src/models/dtos/Auditoria/TipoBobinaHistorialDto.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface TipoBobinaHistorialDto {
|
||||
id_TipoBobina: number;
|
||||
denominacion: string;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
13
Frontend/src/models/dtos/Auditoria/ZonaHistorialDto.ts
Normal file
13
Frontend/src/models/dtos/Auditoria/ZonaHistorialDto.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface ZonaHistorialDto {
|
||||
id_Zona: number;
|
||||
nombre: string;
|
||||
descripcion?: string | null;
|
||||
estado: boolean;
|
||||
|
||||
id_Usuario: number;
|
||||
nombreUsuarioModifico: string;
|
||||
fechaMod: string;
|
||||
tipoMod: string;
|
||||
|
||||
id?: string; // Para DataGrid
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -208,7 +208,7 @@ const GestionarNotasCDPage: React.FC = () => {
|
||||
<TableHead><TableRow>
|
||||
<TableCell>Fecha</TableCell><TableCell>Empresa</TableCell><TableCell>Destino</TableCell>
|
||||
<TableCell>Destinatario</TableCell><TableCell>Tipo</TableCell>
|
||||
<TableCell align="right">Monto</TableCell><TableCell>Referencia</TableCell><TableCell>Obs.</TableCell>
|
||||
<TableCell align="right">Monto</TableCell><TableCell>Referencia Interna</TableCell><TableCell>Referencia</TableCell>
|
||||
{(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
|
||||
@@ -4,7 +4,9 @@ import { useParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box, Typography, Button, Paper, IconButton, Menu, MenuItem,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
|
||||
CircularProgress, Alert, Chip
|
||||
CircularProgress, Alert, Chip, // Alert se sigue usando para el error de carga general
|
||||
ListItemIcon,
|
||||
ListItemText
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
@@ -30,11 +32,11 @@ const GestionarPreciosPublicacionPage: React.FC = () => {
|
||||
const [publicacion, setPublicacion] = useState<PublicacionDto | null>(null);
|
||||
const [precios, setPrecios] = useState<PrecioDto[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null); // Para errores de carga de datos
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingPrecio, setEditingPrecio] = useState<PrecioDto | null>(null); // Este estado determina si el modal edita
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
const [editingPrecio, setEditingPrecio] = useState<PrecioDto | null>(null);
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null); // Este se pasa al modal
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [selectedPrecioRow, setSelectedPrecioRow] = useState<PrecioDto | null>(null);
|
||||
@@ -54,7 +56,9 @@ const GestionarPreciosPublicacionPage: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
||||
setLoading(true);
|
||||
setError(null); // Limpiar error de carga al reintentar
|
||||
setApiErrorMessage(null); // Limpiar error de API de acciones previas
|
||||
try {
|
||||
const [pubData, preciosData] = await Promise.all([
|
||||
publicacionService.getPublicacionById(idPublicacion),
|
||||
@@ -79,41 +83,48 @@ const GestionarPreciosPublicacionPage: React.FC = () => {
|
||||
}, [cargarDatos]);
|
||||
|
||||
const handleOpenModal = (precio?: PrecioDto) => {
|
||||
setEditingPrecio(precio || null); // Si hay 'precio', el modal estará en modo edición
|
||||
setApiErrorMessage(null);
|
||||
setEditingPrecio(precio || null);
|
||||
setApiErrorMessage(null); // Limpiar error de API al abrir el modal
|
||||
setModalOpen(true);
|
||||
};
|
||||
const handleCloseModal = () => {
|
||||
setModalOpen(false);
|
||||
setEditingPrecio(null);
|
||||
// No limpiar apiErrorMessage aquí, el modal lo hace o se limpia al abrir de nuevo
|
||||
};
|
||||
|
||||
// CORREGIDO: El segundo parámetro 'idPrecio' determina si es edición
|
||||
const handleSubmitModal = async (data: CreatePrecioDto | UpdatePrecioDto, idPrecio?: number) => {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
// Si idPrecio tiene valor, Y editingPrecio (initialData del modal) también lo tenía, es una actualización
|
||||
if (idPrecio && editingPrecio) {
|
||||
await precioService.updatePrecio(idPublicacion, idPrecio, data as UpdatePrecioDto);
|
||||
} else {
|
||||
await precioService.createPrecio(idPublicacion, data as CreatePrecioDto);
|
||||
}
|
||||
cargarDatos(); // Recargar lista
|
||||
cargarDatos();
|
||||
// El modal se cerrará solo si el submit es exitoso (controlado en el modal)
|
||||
} catch (err: any) {
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar el período de precio.';
|
||||
setApiErrorMessage(message); throw err; // Re-lanzar para que el modal maneje el estado de error
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||
? err.response.data.message
|
||||
: 'Error al guardar el período de precio.';
|
||||
setApiErrorMessage(message); // Pasar el error al estado que se pasa al modal
|
||||
throw err; // Re-lanzar para que el modal NO se cierre
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (idPrecio: number) => {
|
||||
if (window.confirm(`¿Está seguro de eliminar este período de precio (ID: ${idPrecio})? Esta acción puede afectar la vigencia de períodos anteriores.`)) {
|
||||
setApiErrorMessage(null);
|
||||
setApiErrorMessage(null); // Limpiar antes de la acción
|
||||
try {
|
||||
await precioService.deletePrecio(idPublicacion, idPrecio);
|
||||
cargarDatos();
|
||||
} catch (err: any) {
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar el período de precio.';
|
||||
setApiErrorMessage(message);
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||
? err.response.data.message
|
||||
: 'Error al eliminar el período de precio.';
|
||||
setApiErrorMessage(message); // Establecer error para mostrar EN EL MODAL si fuera necesario, o en un Alert genérico de la PÁGINA SI EL MODAL NO ESTÁ ABIERTO
|
||||
// Si el modal de confirmación no es parte de un modal de formulario, este apiErrorMessage se mostraría en la página.
|
||||
// Como es un window.confirm, el error se mostrará en el Alert de la página principal.
|
||||
}
|
||||
}
|
||||
handleMenuClose();
|
||||
@@ -128,25 +139,38 @@ const GestionarPreciosPublicacionPage: React.FC = () => {
|
||||
|
||||
const formatDate = (dateString?: string | null) => {
|
||||
if (!dateString) return '-';
|
||||
const date = new Date(dateString); // Asegurar que se parsee correctamente si viene con hora
|
||||
const date = new Date(dateString);
|
||||
const day = String(date.getUTCDate()).padStart(2, '0');
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0'); // Meses son 0-indexados
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
||||
const year = date.getUTCFullYear();
|
||||
return `${day}/${month}/${year}`;
|
||||
};
|
||||
|
||||
|
||||
if (loading) {
|
||||
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}><CircularProgress /></Box>;
|
||||
}
|
||||
if (error) {
|
||||
return <Alert severity="error" sx={{ m: 2 }}>{error}</Alert>;
|
||||
// Mostrar error de carga de datos de la página
|
||||
if (error) {
|
||||
return (
|
||||
<Box sx={{p:2}}>
|
||||
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate('/distribucion/publicaciones')} sx={{ mb: 2 }}>
|
||||
Volver a Publicaciones
|
||||
</Button>
|
||||
<Alert severity="error">{error}</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
if (!puedeGestionarPrecios) {
|
||||
return <Alert severity="error" sx={{ m: 2 }}>Acceso denegado.</Alert>;
|
||||
return (
|
||||
<Box sx={{p:2}}>
|
||||
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate('/distribucion/publicaciones')} sx={{ mb: 2 }}>
|
||||
Volver a Publicaciones
|
||||
</Button>
|
||||
<Alert severity="error">No tiene permiso para gestionar precios.</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate('/distribucion/publicaciones')} sx={{ mb: 2 }}>
|
||||
@@ -167,7 +191,12 @@ const GestionarPreciosPublicacionPage: React.FC = () => {
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>}
|
||||
{/* --- Alert para apiErrorMessage eliminado de acá --- */}
|
||||
{/* {apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>} */}
|
||||
|
||||
{/* Mostrar error general si es de carga Y no hay error de API de acción (para no duplicar mensajes) */}
|
||||
{error && !loading && !apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
@@ -212,25 +241,33 @@ const GestionarPreciosPublicacionPage: React.FC = () => {
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
{puedeGestionarPrecios && selectedPrecioRow && (
|
||||
<MenuItem onClick={() => { handleOpenModal(selectedPrecioRow); handleMenuClose(); }}>
|
||||
<EditIcon fontSize="small" sx={{mr:1}}/> Editar Precios/Cerrar Período
|
||||
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Editar Precios/Cerrar Período</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
{puedeGestionarPrecios && selectedPrecioRow && (
|
||||
<MenuItem onClick={() => handleDelete(selectedPrecioRow.idPrecio)}>
|
||||
<DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar Período
|
||||
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Eliminar Período</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
|
||||
{/* El Alert para errores de API de acciones como handleDelete se mostrará aquí si ocurre. */}
|
||||
{/* Se podría refinar para que apiErrorMessage solo se muestre si el modal NO está abierto,
|
||||
pero por ahora, si hay un error en handleDelete (modal no abierto), se mostrará aquí. */}
|
||||
{apiErrorMessage && !modalOpen && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>}
|
||||
|
||||
|
||||
{idPublicacion &&
|
||||
<PrecioFormModal
|
||||
open={modalOpen}
|
||||
onClose={handleCloseModal}
|
||||
onSubmit={handleSubmitModal}
|
||||
idPublicacion={idPublicacion}
|
||||
initialData={editingPrecio} // Esto le dice al modal si está editando
|
||||
errorMessage={apiErrorMessage}
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
initialData={editingPrecio}
|
||||
errorMessage={apiErrorMessage} // El modal recibe el apiErrorMessage
|
||||
clearErrorMessage={() => setApiErrorMessage(null)} // El modal puede limpiar este error
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box, Typography, Button, Paper, IconButton, Menu, MenuItem,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
|
||||
CircularProgress, Alert, Chip
|
||||
Box, Typography, Button, Paper, IconButton, Menu, MenuItem,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
|
||||
CircularProgress, Alert, Chip
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
@@ -47,7 +47,7 @@ const GestionarRecargosPublicacionPage: React.FC = () => {
|
||||
setError("ID de Publicación inválido."); setLoading(false); return;
|
||||
}
|
||||
if (!puedeGestionarRecargos) {
|
||||
setError("No tiene permiso para gestionar recargos."); setLoading(false); return;
|
||||
setError("No tiene permiso para gestionar recargos."); setLoading(false); return;
|
||||
}
|
||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
||||
try {
|
||||
@@ -70,8 +70,11 @@ const GestionarRecargosPublicacionPage: React.FC = () => {
|
||||
useEffect(() => { cargarDatos(); }, [cargarDatos]);
|
||||
|
||||
const handleOpenModal = (recargo?: RecargoZonaDto) => {
|
||||
setEditingRecargo(recargo || null); setApiErrorMessage(null); setModalOpen(true);
|
||||
setEditingRecargo(recargo || null);
|
||||
setApiErrorMessage(null); // Limpiar error al abrir el modal
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setModalOpen(false); setEditingRecargo(null);
|
||||
};
|
||||
@@ -93,13 +96,13 @@ const GestionarRecargosPublicacionPage: React.FC = () => {
|
||||
|
||||
const handleDelete = async (idRecargoDelRow: number) => {
|
||||
if (window.confirm(`¿Seguro de eliminar este recargo (ID: ${idRecargoDelRow})? Puede afectar vigencias.`)) {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
await recargoZonaService.deleteRecargoZona(idPublicacion, idRecargoDelRow);
|
||||
cargarDatos();
|
||||
} catch (err: any) {
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar el recargo.';
|
||||
setApiErrorMessage(message);
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar el recargo.';
|
||||
setApiErrorMessage(message);
|
||||
}
|
||||
}
|
||||
handleMenuClose();
|
||||
@@ -118,7 +121,7 @@ const GestionarRecargosPublicacionPage: React.FC = () => {
|
||||
// Si viniera como DateTime completo, necesitarías parsearlo y formatearlo.
|
||||
const parts = dateString.split('-');
|
||||
if (parts.length === 3) {
|
||||
return `${parts[2]}/${parts[1]}/${parts[0]}`; // dd/MM/yyyy
|
||||
return `${parts[2]}/${parts[1]}/${parts[0]}`; // dd/MM/yyyy
|
||||
}
|
||||
return dateString; // Devolver como está si no es el formato esperado
|
||||
};
|
||||
@@ -130,64 +133,69 @@ const GestionarRecargosPublicacionPage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate(`/distribucion/publicaciones`)} sx={{ mb: 2 }}>
|
||||
Volver a Publicaciones
|
||||
</Button>
|
||||
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate(`/distribucion/publicaciones`)} sx={{ mb: 2 }}>
|
||||
Volver a Publicaciones
|
||||
</Button>
|
||||
<Typography variant="h4" gutterBottom>Recargos por Zona para: {publicacion?.nombre || 'Cargando...'}</Typography>
|
||||
<Typography variant="subtitle1" color="text.secondary" gutterBottom>Empresa: {publicacion?.nombreEmpresa || '-'}</Typography>
|
||||
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
{puedeGestionarRecargos && (
|
||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>
|
||||
Agregar Nuevo Recargo
|
||||
</Button>
|
||||
{puedeGestionarRecargos && (
|
||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>
|
||||
Agregar Nuevo Recargo
|
||||
</Button>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>}
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead><TableRow>
|
||||
<TableCell sx={{fontWeight: 'bold'}}>Zona</TableCell>
|
||||
<TableCell sx={{fontWeight: 'bold'}}>Vigencia Desde</TableCell>
|
||||
<TableCell sx={{fontWeight: 'bold'}}>Vigencia Hasta</TableCell>
|
||||
<TableCell align="right" sx={{fontWeight: 'bold'}}>Valor</TableCell>
|
||||
<TableCell align="center" sx={{fontWeight: 'bold'}}>Estado</TableCell>
|
||||
<TableCell align="right" sx={{fontWeight: 'bold'}}>Acciones</TableCell>
|
||||
<TableCell sx={{ fontWeight: 'bold' }}>Zona</TableCell>
|
||||
<TableCell sx={{ fontWeight: 'bold' }}>Vigencia Desde</TableCell>
|
||||
<TableCell sx={{ fontWeight: 'bold' }}>Vigencia Hasta</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Valor</TableCell>
|
||||
<TableCell align="center" sx={{ fontWeight: 'bold' }}>Estado</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>Acciones</TableCell>
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{recargos.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={6} align="center">No hay recargos definidos para esta publicación.</TableCell></TableRow>
|
||||
) : (
|
||||
recargos.sort((a, b) => new Date(b.vigenciaD).getTime() - new Date(a.vigenciaD).getTime() || a.nombreZona.localeCompare(b.nombreZona)) // Ordenar por fecha desc, luego zona asc
|
||||
.map((r) => (
|
||||
.map((r) => (
|
||||
<TableRow key={r.idRecargo} hover>
|
||||
<TableCell>{r.nombreZona}</TableCell><TableCell>{formatDate(r.vigenciaD)}</TableCell>
|
||||
<TableCell>{formatDate(r.vigenciaH)}</TableCell>
|
||||
<TableCell align="right">${r.valor.toFixed(2)}</TableCell>
|
||||
<TableCell align="center">{!r.vigenciaH ? <Chip label="Activo" color="success" size="small" /> : <Chip label="Cerrado" size="small" />}</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, r)} disabled={!puedeGestionarRecargos}><MoreVertIcon /></IconButton>
|
||||
</TableCell>
|
||||
<TableCell>{r.nombreZona}</TableCell><TableCell>{formatDate(r.vigenciaD)}</TableCell>
|
||||
<TableCell>{formatDate(r.vigenciaH)}</TableCell>
|
||||
<TableCell align="right">${r.valor.toFixed(2)}</TableCell>
|
||||
<TableCell align="center">{!r.vigenciaH ? <Chip label="Activo" color="success" size="small" /> : <Chip label="Cerrado" size="small" />}</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, r)} disabled={!puedeGestionarRecargos}><MoreVertIcon /></IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)))}
|
||||
)))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
{puedeGestionarRecargos && selectedRecargoRow && (
|
||||
<MenuItem onClick={() => { handleOpenModal(selectedRecargoRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{mr:1}}/> Editar/Cerrar</MenuItem>)}
|
||||
<MenuItem onClick={() => { handleOpenModal(selectedRecargoRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{ mr: 1 }} /> Editar/Cerrar</MenuItem>)}
|
||||
{puedeGestionarRecargos && selectedRecargoRow && (
|
||||
<MenuItem onClick={() => handleDelete(selectedRecargoRow.idRecargo)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar</MenuItem>)}
|
||||
<MenuItem onClick={() => handleDelete(selectedRecargoRow.idRecargo)}><DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar</MenuItem>)}
|
||||
</Menu>
|
||||
|
||||
{/* Alert para errores de acciones DELETE (cuando el modal no está abierto) */ }
|
||||
{apiErrorMessage && !modalOpen && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
||||
|
||||
{idPublicacion &&
|
||||
<RecargoZonaFormModal
|
||||
open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal}
|
||||
idPublicacion={idPublicacion} initialData={editingRecargo}
|
||||
errorMessage={apiErrorMessage} clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
open={modalOpen}
|
||||
onClose={handleCloseModal}
|
||||
onSubmit={handleSubmitModal}
|
||||
idPublicacion={idPublicacion}
|
||||
initialData={editingRecargo}
|
||||
errorMessage={apiErrorMessage} // Se pasa el error al modal
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// src/pages/Impresion/GestionarStockBobinasPage.tsx
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||
CircularProgress, Alert, FormControl, InputLabel, Select
|
||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||
CircularProgress, Alert, FormControl, InputLabel, Select
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import SwapHorizIcon from '@mui/icons-material/SwapHoriz'; // Para cambiar estado
|
||||
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
|
||||
|
||||
import stockBobinaService from '../../services/Impresion/stockBobinaService';
|
||||
import tipoBobinaService from '../../services/Impresion/tipoBobinaService';
|
||||
@@ -30,13 +31,16 @@ import StockBobinaCambioEstadoModal from '../../components/Modals/Impresion/Stoc
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
import axios from 'axios';
|
||||
|
||||
const ID_ESTADO_DISPONIBLE = 1;
|
||||
const ID_ESTADO_UTILIZADA = 2;
|
||||
const ID_ESTADO_DANADA = 3;
|
||||
|
||||
const GestionarStockBobinasPage: React.FC = () => {
|
||||
const [stock, setStock] = useState<StockBobinaDto[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Estados para filtros
|
||||
const [filtroTipoBobina, setFiltroTipoBobina] = useState<number | string>('');
|
||||
const [filtroNroBobina, setFiltroNroBobina] = useState('');
|
||||
const [filtroPlanta, setFiltroPlanta] = useState<number | string>('');
|
||||
@@ -45,22 +49,21 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
|
||||
// Datos para dropdowns de filtros
|
||||
const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]);
|
||||
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||
const [estadosBobina, setEstadosBobina] = useState<EstadoBobinaDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
|
||||
|
||||
const [ingresoModalOpen, setIngresoModalOpen] = useState(false);
|
||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||
const [cambioEstadoModalOpen, setCambioEstadoModalOpen] = useState(false);
|
||||
|
||||
const [selectedBobina, setSelectedBobina] = useState<StockBobinaDto | null>(null);
|
||||
const [selectedBobina, setSelectedBobina] = useState<StockBobinaDto | null>(null); // Para los modales
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(25);
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [selectedBobinaForRowMenu, setSelectedBobinaForRowMenu] = useState<StockBobinaDto | null>(null); // Para el menú contextual
|
||||
|
||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||
const puedeVer = isSuperAdmin || tienePermiso("IB001");
|
||||
@@ -69,23 +72,22 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
const puedeModificarDatos = isSuperAdmin || tienePermiso("IB004");
|
||||
const puedeEliminar = isSuperAdmin || tienePermiso("IB005");
|
||||
|
||||
|
||||
const fetchFiltersDropdownData = useCallback(async () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const [tiposData, plantasData, estadosData] = await Promise.all([
|
||||
tipoBobinaService.getAllTiposBobina(),
|
||||
plantaService.getAllPlantas(),
|
||||
estadoBobinaService.getAllEstadosBobina()
|
||||
]);
|
||||
setTiposBobina(tiposData);
|
||||
setPlantas(plantasData);
|
||||
setEstadosBobina(estadosData);
|
||||
const [tiposData, plantasData, estadosData] = await Promise.all([
|
||||
tipoBobinaService.getAllTiposBobina(),
|
||||
plantaService.getAllPlantas(),
|
||||
estadoBobinaService.getAllEstadosBobina()
|
||||
]);
|
||||
setTiposBobina(tiposData);
|
||||
setPlantas(plantasData);
|
||||
setEstadosBobina(estadosData);
|
||||
} catch (err) {
|
||||
console.error("Error cargando datos para filtros:", err);
|
||||
setError("Error al cargar opciones de filtro.");
|
||||
console.error("Error cargando datos para filtros:", err);
|
||||
setError("Error al cargar opciones de filtro.");
|
||||
} finally {
|
||||
setLoadingFiltersDropdown(false);
|
||||
setLoadingFiltersDropdown(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -120,7 +122,6 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
cargarStock();
|
||||
}, [cargarStock]);
|
||||
|
||||
// Handlers para modales
|
||||
const handleOpenIngresoModal = () => { setApiErrorMessage(null); setIngresoModalOpen(true); };
|
||||
const handleCloseIngresoModal = () => setIngresoModalOpen(false);
|
||||
const handleSubmitIngresoModal = async (data: CreateStockBobinaDto) => {
|
||||
@@ -129,41 +130,78 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al ingresar bobina.'; setApiErrorMessage(msg); throw err; }
|
||||
};
|
||||
|
||||
const handleOpenEditModal = (bobina: StockBobinaDto) => {
|
||||
setSelectedBobina(bobina); setApiErrorMessage(null); setEditModalOpen(true); handleMenuClose();
|
||||
const handleOpenEditModal = (bobina: StockBobinaDto | null) => {
|
||||
if (!bobina) return;
|
||||
setSelectedBobina(bobina);
|
||||
setApiErrorMessage(null);
|
||||
setEditModalOpen(true);
|
||||
};
|
||||
const handleCloseEditModal = () => { setEditModalOpen(false); setSelectedBobina(null); };
|
||||
const handleCloseEditModal = () => {
|
||||
setEditModalOpen(false);
|
||||
setSelectedBobina(null);
|
||||
// Devolver el foco al botón que abrió el menú (si el modal se abrió desde el menú)
|
||||
if (lastOpenedMenuButtonRef.current) {
|
||||
setTimeout(() => { // setTimeout puede ayudar
|
||||
lastOpenedMenuButtonRef.current?.focus();
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
const handleSubmitEditModal = async (idBobina: number, data: UpdateStockBobinaDto) => {
|
||||
setApiErrorMessage(null);
|
||||
try { await stockBobinaService.updateDatosBobinaDisponible(idBobina, data); cargarStock(); }
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al actualizar bobina.'; setApiErrorMessage(msg); throw err; }
|
||||
};
|
||||
|
||||
const handleOpenCambioEstadoModal = (bobina: StockBobinaDto) => {
|
||||
setSelectedBobina(bobina); setApiErrorMessage(null); setCambioEstadoModalOpen(true); handleMenuClose();
|
||||
const handleOpenCambioEstadoModal = (bobina: StockBobinaDto | null) => {
|
||||
if (!bobina) return;
|
||||
setSelectedBobina(bobina);
|
||||
setApiErrorMessage(null);
|
||||
setCambioEstadoModalOpen(true);
|
||||
};
|
||||
const handleCloseCambioEstadoModal = () => { setCambioEstadoModalOpen(false); setSelectedBobina(null); };
|
||||
const handleSubmitCambioEstadoModal = async (idBobina: number, data: CambiarEstadoBobinaDto) => {
|
||||
setApiErrorMessage(null);
|
||||
setApiErrorMessage(null);
|
||||
try { await stockBobinaService.cambiarEstadoBobina(idBobina, data); cargarStock(); }
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al cambiar estado.'; setApiErrorMessage(msg); throw err; }
|
||||
};
|
||||
|
||||
const handleDeleteBobina = async (idBobina: number) => {
|
||||
if (window.confirm(`¿Seguro de eliminar este ingreso de bobina (ID: ${idBobina})? Solo se permite si está 'Disponible'.`)) {
|
||||
setApiErrorMessage(null);
|
||||
try { await stockBobinaService.deleteIngresoBobina(idBobina); cargarStock(); }
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.'; setApiErrorMessage(msg); }
|
||||
const handleDeleteBobina = async (bobina: StockBobinaDto | null) => {
|
||||
if (!bobina) return;
|
||||
// Permitir eliminar si está Disponible (1) o Dañada (3)
|
||||
if (bobina.idEstadoBobina !== ID_ESTADO_DISPONIBLE && bobina.idEstadoBobina !== ID_ESTADO_DANADA) {
|
||||
alert("Solo se pueden eliminar bobinas en estado 'Disponible' o 'Dañada'.");
|
||||
handleMenuClose();
|
||||
return;
|
||||
}
|
||||
if (window.confirm(`¿Seguro de eliminar este ingreso de bobina (ID: ${bobina.idBobina})?`)) {
|
||||
setApiErrorMessage(null);
|
||||
try { await stockBobinaService.deleteIngresoBobina(bobina.idBobina); cargarStock(); }
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.'; setApiErrorMessage(msg); }
|
||||
}
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const lastOpenedMenuButtonRef = React.useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, bobina: StockBobinaDto) => {
|
||||
setAnchorEl(event.currentTarget); setSelectedBobina(bobina);
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLButtonElement>, bobina: StockBobinaDto) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
setSelectedBobinaForRowMenu(bobina);
|
||||
lastOpenedMenuButtonRef.current = event.currentTarget; // Guardar el botón que abrió el menú
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null); setSelectedBobina(null);
|
||||
setAnchorEl(null);
|
||||
// No es estrictamente necesario limpiar selectedBobinaForRowMenu aquí,
|
||||
// ya que se actualiza en el siguiente handleMenuOpen.
|
||||
// Pero se puede ser explícito:
|
||||
setSelectedBobinaForRowMenu(null);
|
||||
|
||||
// Devolver el foco al botón que abrió el menú si existe
|
||||
if (lastOpenedMenuButtonRef.current) {
|
||||
setTimeout(() => { // Pequeño retraso para asegurar que el menú se haya cerrado visualmente
|
||||
lastOpenedMenuButtonRef.current?.focus();
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||
@@ -171,128 +209,146 @@ const GestionarStockBobinasPage: React.FC = () => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||
};
|
||||
const displayData = stock.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||
|
||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
||||
|
||||
|
||||
if (!loading && !puedeVer) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Typography variant="h5" gutterBottom>Stock de Bobinas</Typography>
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
{/* ... (Filtros sin cambios) ... */}
|
||||
<Typography variant="h6" gutterBottom>Filtros</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2}}>
|
||||
<FormControl size="small" sx={{minWidth: 180, flexGrow: 1}}>
|
||||
<InputLabel>Tipo Bobina</InputLabel>
|
||||
<Select value={filtroTipoBobina} label="Tipo Bobina" onChange={(e) => setFiltroTipoBobina(e.target.value)} disabled={loadingFiltersDropdown}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
{tiposBobina.map(t => <MenuItem key={t.idTipoBobina} value={t.idTipoBobina}>{t.denominacion}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField label="Nro. Bobina" size="small" value={filtroNroBobina} onChange={(e) => setFiltroNroBobina(e.target.value)} sx={{minWidth: 150, flexGrow: 1}}/>
|
||||
<FormControl size="small" sx={{minWidth: 180, flexGrow: 1}}>
|
||||
<InputLabel>Planta</InputLabel>
|
||||
<Select value={filtroPlanta} label="Planta" onChange={(e) => setFiltroPlanta(e.target.value)} disabled={loadingFiltersDropdown}>
|
||||
<MenuItem value=""><em>Todas</em></MenuItem>
|
||||
{plantas.map(p => <MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{minWidth: 180, flexGrow: 1}}>
|
||||
<InputLabel>Estado</InputLabel>
|
||||
<Select value={filtroEstadoBobina} label="Estado" onChange={(e) => setFiltroEstadoBobina(e.target.value)} disabled={loadingFiltersDropdown}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
{estadosBobina.map(e => <MenuItem key={e.idEstadoBobina} value={e.idEstadoBobina}>{e.denominacion}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField label="Remito" size="small" value={filtroRemito} onChange={(e) => setFiltroRemito(e.target.value)} sx={{minWidth: 150, flexGrow: 1}}/>
|
||||
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170, flexGrow: 1}}/>
|
||||
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170, flexGrow: 1}}/>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }}>
|
||||
<InputLabel>Tipo Bobina</InputLabel>
|
||||
<Select value={filtroTipoBobina} label="Tipo Bobina" onChange={(e) => setFiltroTipoBobina(e.target.value)} disabled={loadingFiltersDropdown}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
{tiposBobina.map(t => <MenuItem key={t.idTipoBobina} value={t.idTipoBobina}>{t.denominacion}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField label="Nro. Bobina" size="small" value={filtroNroBobina} onChange={(e) => setFiltroNroBobina(e.target.value)} sx={{ minWidth: 150, flexGrow: 1 }} />
|
||||
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }}>
|
||||
<InputLabel>Planta</InputLabel>
|
||||
<Select value={filtroPlanta} label="Planta" onChange={(e) => setFiltroPlanta(e.target.value)} disabled={loadingFiltersDropdown}>
|
||||
<MenuItem value=""><em>Todas</em></MenuItem>
|
||||
{plantas.map(p => <MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }}>
|
||||
<InputLabel>Estado</InputLabel>
|
||||
<Select value={filtroEstadoBobina} label="Estado" onChange={(e) => setFiltroEstadoBobina(e.target.value)} disabled={loadingFiltersDropdown}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
{estadosBobina.map(e => <MenuItem key={e.idEstadoBobina} value={e.idEstadoBobina}>{e.denominacion}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField label="Remito" size="small" value={filtroRemito} onChange={(e) => setFiltroRemito(e.target.value)} sx={{ minWidth: 150, flexGrow: 1 }} />
|
||||
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170, flexGrow: 1 }} />
|
||||
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170, flexGrow: 1 }} />
|
||||
</Box>
|
||||
{/* <Button variant="outlined" onClick={cargarStock} sx={{ mr: 1 }}>Aplicar Filtros</Button>
|
||||
<Button variant="outlined" color="secondary" onClick={() => { // Resetear filtros
|
||||
setFiltroTipoBobina(''); setFiltroNroBobina(''); setFiltroPlanta('');
|
||||
setFiltroEstadoBobina(''); setFiltroRemito(''); setFiltroFechaDesde(''); setFiltroFechaHasta('');
|
||||
// cargarStock(); // Opcional: recargar inmediatamente
|
||||
}}>Limpiar Filtros</Button> */}
|
||||
|
||||
{puedeIngresar && (<Button variant="contained" startIcon={<AddIcon />} onClick={handleOpenIngresoModal} sx={{ mt:2 }}>Ingresar Bobina</Button>)}
|
||||
{puedeIngresar && (<Button variant="contained" startIcon={<AddIcon />} onClick={handleOpenIngresoModal} sx={{ mt: 2 }}>Ingresar Bobina</Button>)}
|
||||
</Paper>
|
||||
|
||||
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||
{error && !loading && <Alert severity="error" sx={{my: 2}}>{error}</Alert>}
|
||||
{apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>}
|
||||
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
||||
|
||||
{!loading && !error && puedeVer && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead><TableRow>
|
||||
<TableCell>Nro. Bobina</TableCell><TableCell>Tipo</TableCell><TableCell>Peso (Kg)</TableCell>
|
||||
<TableCell>Planta</TableCell><TableCell>Estado</TableCell><TableCell>Remito</TableCell>
|
||||
<TableCell>F. Remito</TableCell><TableCell>F. Estado</TableCell>
|
||||
<TableCell>Publicación</TableCell><TableCell>Sección</TableCell>
|
||||
<TableCell>Obs.</TableCell><TableCell align="right">Acciones</TableCell>
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{displayData.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={12} align="center">No se encontraron bobinas con los filtros aplicados.</TableCell></TableRow>
|
||||
) : (
|
||||
displayData.map((b) => (
|
||||
<TableRow key={b.idBobina} hover>
|
||||
<TableCell>{b.nroBobina}</TableCell><TableCell>{b.nombreTipoBobina}</TableCell>
|
||||
<TableCell align="right">{b.peso}</TableCell><TableCell>{b.nombrePlanta}</TableCell>
|
||||
<TableCell><Chip label={b.nombreEstadoBobina} size="small" color={
|
||||
b.idEstadoBobina === 1 ? "success" : b.idEstadoBobina === 2 ? "primary" : b.idEstadoBobina === 3 ? "error" : "default"
|
||||
}/></TableCell>
|
||||
<TableCell>{b.remito}</TableCell><TableCell>{formatDate(b.fechaRemito)}</TableCell>
|
||||
<TableCell>{formatDate(b.fechaEstado)}</TableCell>
|
||||
<TableCell>{b.nombrePublicacion || '-'}</TableCell><TableCell>{b.nombreSeccion || '-'}</TableCell>
|
||||
<TableCell>{b.obs || '-'}</TableCell>
|
||||
<TableCell align="right">
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead><TableRow>
|
||||
<TableCell>Nro. Bobina</TableCell><TableCell>Tipo</TableCell><TableCell>Peso (Kg)</TableCell>
|
||||
<TableCell>Planta</TableCell><TableCell>Estado</TableCell><TableCell>Remito</TableCell>
|
||||
<TableCell>F. Remito</TableCell><TableCell>F. Estado</TableCell>
|
||||
<TableCell>Publicación</TableCell><TableCell>Sección</TableCell>
|
||||
<TableCell>Obs.</TableCell>
|
||||
{/* Mostrar columna de acciones si tiene algún permiso de acción */}
|
||||
{(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{displayData.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) ? 12 : 11} align="center">No se encontraron bobinas con los filtros aplicados.</TableCell></TableRow>
|
||||
) : (
|
||||
displayData.map((b) => (
|
||||
<TableRow key={b.idBobina} hover>
|
||||
<TableCell>{b.nroBobina}</TableCell><TableCell>{b.nombreTipoBobina}</TableCell>
|
||||
<TableCell align="right">{b.peso}</TableCell><TableCell>{b.nombrePlanta}</TableCell>
|
||||
<TableCell><Chip label={b.nombreEstadoBobina} size="small" color={
|
||||
b.idEstadoBobina === ID_ESTADO_DISPONIBLE ? "success" : b.idEstadoBobina === ID_ESTADO_UTILIZADA ? "primary" : b.idEstadoBobina === ID_ESTADO_DANADA ? "error" : "default"
|
||||
} /></TableCell>
|
||||
<TableCell>{b.remito}</TableCell><TableCell>{formatDate(b.fechaRemito)}</TableCell>
|
||||
<TableCell>{formatDate(b.fechaEstado)}</TableCell>
|
||||
<TableCell>{b.nombrePublicacion || '-'}</TableCell><TableCell>{b.nombreSeccion || '-'}</TableCell>
|
||||
<TableCell>{b.obs || '-'}</TableCell>
|
||||
{(puedeModificarDatos || puedeCambiarEstado || puedeEliminar) && (
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, b)}
|
||||
disabled={!puedeModificarDatos && !puedeCambiarEstado && !puedeEliminar}
|
||||
// El botón de menú se deshabilita si no hay NINGUNA acción posible para esa fila
|
||||
disabled={
|
||||
!(b.idEstadoBobina === ID_ESTADO_DISPONIBLE && puedeModificarDatos) &&
|
||||
!(puedeCambiarEstado) && // Siempre se puede intentar cambiar estado (el modal lo validará)
|
||||
!((b.idEstadoBobina === ID_ESTADO_DISPONIBLE || b.idEstadoBobina === ID_ESTADO_DANADA) && puedeEliminar)
|
||||
}
|
||||
><MoreVertIcon /></IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[25, 50, 100]} component="div" count={stock.length}
|
||||
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
|
||||
/>
|
||||
</TableContainer>
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
)))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[25, 50, 100]} component="div" count={stock.length}
|
||||
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
|
||||
/>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
{selectedBobina?.idEstadoBobina === 1 && puedeModificarDatos && (
|
||||
<MenuItem onClick={() => handleOpenEditModal(selectedBobina!)}><EditIcon fontSize="small" sx={{mr:1}}/> Editar Datos</MenuItem>)}
|
||||
{selectedBobina?.idEstadoBobina !== 3 && puedeCambiarEstado && ( // No se puede cambiar estado si está dañada
|
||||
<MenuItem onClick={() => handleOpenCambioEstadoModal(selectedBobina!)}><SwapHorizIcon fontSize="small" sx={{mr:1}}/> Cambiar Estado</MenuItem>)}
|
||||
{selectedBobina?.idEstadoBobina === 1 && puedeEliminar && (
|
||||
<MenuItem onClick={() => handleDeleteBobina(selectedBobina!.idBobina)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar Ingreso</MenuItem>)}
|
||||
{selectedBobina && selectedBobina.idEstadoBobina === 3 && (!puedeModificarDatos && !puedeCambiarEstado && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
||||
{selectedBobina && selectedBobina.idEstadoBobina !== 1 && selectedBobina.idEstadoBobina !== 3 && (!puedeCambiarEstado) && <MenuItem disabled>Sin acciones</MenuItem>}
|
||||
{selectedBobinaForRowMenu && puedeModificarDatos && selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE && (
|
||||
<MenuItem onClick={() => { handleOpenEditModal(selectedBobinaForRowMenu); handleMenuClose(); }}>
|
||||
<EditIcon fontSize="small" sx={{ mr: 1 }} /> Editar Datos
|
||||
</MenuItem>
|
||||
)}
|
||||
{/* --- CAMBIO: Permitir cambiar estado incluso si está Dañada --- */}
|
||||
{selectedBobinaForRowMenu && puedeCambiarEstado && (
|
||||
<MenuItem onClick={() => { handleOpenCambioEstadoModal(selectedBobinaForRowMenu); handleMenuClose(); }}>
|
||||
<SwapHorizIcon fontSize="small" sx={{ mr: 1 }} /> Cambiar Estado
|
||||
</MenuItem>
|
||||
)}
|
||||
{/* --- CAMBIO: Permitir eliminar si está Disponible o Dañada --- */}
|
||||
{selectedBobinaForRowMenu && puedeEliminar &&
|
||||
(selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE || selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DANADA) && (
|
||||
<MenuItem onClick={() => handleDeleteBobina(selectedBobinaForRowMenu)}>
|
||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar Ingreso
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{/* Lógica para el MenuItem "Sin acciones" */}
|
||||
{selectedBobinaForRowMenu &&
|
||||
!((selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE && puedeModificarDatos)) &&
|
||||
!(puedeCambiarEstado) &&
|
||||
!(((selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DISPONIBLE || selectedBobinaForRowMenu.idEstadoBobina === ID_ESTADO_DANADA) && puedeEliminar)) &&
|
||||
<MenuItem disabled>Sin acciones disponibles</MenuItem>
|
||||
}
|
||||
</Menu>
|
||||
|
||||
<StockBobinaIngresoFormModal
|
||||
open={ingresoModalOpen} onClose={handleCloseIngresoModal} onSubmit={handleSubmitIngresoModal}
|
||||
errorMessage={apiErrorMessage} clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
/>
|
||||
{selectedBobina && editModalOpen &&
|
||||
{editModalOpen && selectedBobina &&
|
||||
<StockBobinaEditFormModal
|
||||
open={editModalOpen} onClose={handleCloseEditModal} onSubmit={handleSubmitEditModal}
|
||||
initialData={selectedBobina} errorMessage={apiErrorMessage}
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
open={editModalOpen} onClose={handleCloseEditModal} onSubmit={handleSubmitEditModal}
|
||||
initialData={selectedBobina} errorMessage={apiErrorMessage}
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
/>
|
||||
}
|
||||
{selectedBobina && cambioEstadoModalOpen &&
|
||||
{cambioEstadoModalOpen && selectedBobina &&
|
||||
<StockBobinaCambioEstadoModal
|
||||
open={cambioEstadoModalOpen} onClose={handleCloseCambioEstadoModal} onSubmit={handleSubmitCambioEstadoModal}
|
||||
bobinaActual={selectedBobina} errorMessage={apiErrorMessage}
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
open={cambioEstadoModalOpen} onClose={handleCloseCambioEstadoModal} onSubmit={handleSubmitCambioEstadoModal}
|
||||
bobinaActual={selectedBobina} errorMessage={apiErrorMessage}
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
|
||||
@@ -10,6 +10,25 @@ import type { TipoPagoHistorialDto } from '../../models/dtos/Auditoria/TipoPagoH
|
||||
import type { CanillaHistorialDto } from '../../models/dtos/Auditoria/CanillaHistorialDto';
|
||||
import type { DistribuidorHistorialDto } from '../../models/dtos/Auditoria/DistribuidorHistorialDto';
|
||||
import type { EmpresaHistorialDto } from '../../models/dtos/Auditoria/EmpresaHistorialDto';
|
||||
import type { ZonaHistorialDto } from '../../models/dtos/Auditoria/ZonaHistorialDto';
|
||||
import type { OtroDestinoHistorialDto } from '../../models/dtos/Auditoria/OtroDestinoHistorialDto';
|
||||
import type { PublicacionHistorialDto } from '../../models/dtos/Auditoria/PublicacionHistorialDto';
|
||||
import type { PubliSeccionHistorialDto } from '../../models/dtos/Auditoria/PubliSeccionHistorialDto';
|
||||
import type { PrecioHistorialDto } from '../../models/dtos/Auditoria/PrecioHistorialDto';
|
||||
import type { RecargoZonaHistorialDto } from '../../models/dtos/Auditoria/RecargoZonaHistorialDto';
|
||||
import type { PorcPagoHistorialDto } from '../../models/dtos/Auditoria/PorcPagoHistorialDto';
|
||||
import type { PorcMonCanillaHistorialDto } from '../../models/dtos/Auditoria/PorcMonCanillaHistorialDto';
|
||||
import type { ControlDevolucionesHistorialDto } from '../../models/dtos/Auditoria/ControlDevolucionesHistorialDto';
|
||||
import type { TipoBobinaHistorialDto } from '../../models/dtos/Auditoria/TipoBobinaHistorialDto';
|
||||
import type { EstadoBobinaHistorialDto } from '../../models/dtos/Auditoria/EstadoBobinaHistorialDto';
|
||||
import type { PlantaHistorialDto } from '../../models/dtos/Auditoria/PlantaHistorialDto';
|
||||
import type { StockBobinaHistorialDto } from '../../models/dtos/Auditoria/StockBobinaHistorialDto';
|
||||
import type { RegTiradaHistorialDto } from '../../models/dtos/Auditoria/RegTiradaHistorialDto';
|
||||
import type { RegSeccionTiradaHistorialDto } from '../../models/dtos/Auditoria/RegSeccionTiradaHistorialDto';
|
||||
import type { PerfilHistorialDto } from '../../models/dtos/Auditoria/PerfilHistorialDto';
|
||||
import type { PermisoHistorialDto } from '../../models/dtos/Auditoria/PermisoHistorialDto';
|
||||
import type { PermisosPerfilesHistorialDto } from '../../models/dtos/Auditoria/PermisosPerfilesHistorialDto';
|
||||
import type { CambioParadaHistorialDto } from '../../models/dtos/Auditoria/CambioParadaHistorialDto';
|
||||
|
||||
interface HistorialParamsComunes {
|
||||
fechaDesde?: string; // "yyyy-MM-dd"
|
||||
@@ -18,6 +37,103 @@ interface HistorialParamsComunes {
|
||||
tipoModificacion?: string; // Cambiado de tipoMod
|
||||
}
|
||||
|
||||
interface HistorialCambiosParadaParams extends HistorialParamsComunes {
|
||||
idCanillaAfectado?: number;
|
||||
}
|
||||
|
||||
interface HistorialPermisosPerfilesParams extends HistorialParamsComunes {
|
||||
idPerfilAfectado?: number;
|
||||
idPermisoAfectado?: number;
|
||||
}
|
||||
|
||||
interface HistorialPermisosMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idPermisoAfectado?: number;
|
||||
}
|
||||
|
||||
interface HistorialPerfilesMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idPerfilAfectado?: number;
|
||||
}
|
||||
|
||||
interface HistorialRegSeccionesTiradaParams extends HistorialParamsComunes {
|
||||
idTiradaAfectada?: number; // ID de bob_RegPublicaciones
|
||||
idPublicacionFiltro?: number;
|
||||
idSeccionFiltro?: number;
|
||||
idPlantaFiltro?: number;
|
||||
fechaTiradaFiltro?: string;
|
||||
}
|
||||
|
||||
interface HistorialRegTiradasParams extends HistorialParamsComunes {
|
||||
idRegistroAfectado?: number;
|
||||
idPublicacionFiltro?: number;
|
||||
idPlantaFiltro?: number;
|
||||
fechaTiradaFiltro?: string; // YYYY-MM-DD
|
||||
}
|
||||
|
||||
interface HistorialStockBobinasParams extends HistorialParamsComunes {
|
||||
idBobinaAfectada?: number;
|
||||
idTipoBobinaFiltro?: number;
|
||||
idPlantaFiltro?: number;
|
||||
idEstadoBobinaFiltro?: number;
|
||||
}
|
||||
|
||||
interface HistorialPlantasMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idPlantaAfectada?: number;
|
||||
}
|
||||
|
||||
interface HistorialEstadosBobinaMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idEstadoBobinaAfectado?: number;
|
||||
}
|
||||
|
||||
interface HistorialTiposBobinaMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idTipoBobinaAfectado?: number;
|
||||
}
|
||||
|
||||
interface HistorialControlDevolucionesParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idControlAfectado?: number;
|
||||
idEmpresaAfectada?: number;
|
||||
fechaAfectada?: string; // yyyy-MM-dd
|
||||
}
|
||||
|
||||
interface HistorialPorcMonCanillaMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idPorcMonAfectado?: number;
|
||||
idPublicacionAfectada?: number;
|
||||
idCanillaAfectada?: number;
|
||||
}
|
||||
|
||||
interface HistorialPorcPagoDistMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idPorcentajeAfectado?: number;
|
||||
idPublicacionAfectada?: number;
|
||||
idDistribuidorAfectado?: number;
|
||||
}
|
||||
|
||||
interface HistorialRecargosZonaMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idRecargoAfectado?: number;
|
||||
idPublicacionAfectada?: number;
|
||||
idZonaAfectada?: number;
|
||||
}
|
||||
|
||||
interface HistorialPreciosMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idPrecioAfectado?: number;
|
||||
idPublicacionAfectada?: number;
|
||||
}
|
||||
|
||||
interface HistorialPubliSeccionesMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idSeccionAfectada?: number;
|
||||
idPublicacionAfectada?: number; // Para filtrar por publicación si es necesario
|
||||
}
|
||||
|
||||
interface HistorialPublicacionesMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idPublicacionAfectada?: number;
|
||||
}
|
||||
|
||||
interface HistorialOtrosDestinosMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idOtroDestinoAfectado?: number;
|
||||
}
|
||||
|
||||
interface HistorialZonasMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idZonaAfectada?: number;
|
||||
}
|
||||
|
||||
interface HistorialEmpresasMaestroParams extends HistorialParamsComunes { // << NUEVA INTERFAZ
|
||||
idEmpresaAfectada?: number;
|
||||
}
|
||||
@@ -173,6 +289,184 @@ const getHistorialEmpresasMaestro = async (params: HistorialEmpresasMaestroParam
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialZonasMaestro = async (params: HistorialZonasMaestroParams): Promise<ZonaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
const response = await apiClient.get<ZonaHistorialDto[]>('/auditoria/zonas-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialOtrosDestinosMaestro = async (params: HistorialOtrosDestinosMaestroParams): Promise<OtroDestinoHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
const response = await apiClient.get<OtroDestinoHistorialDto[]>('/auditoria/otros-destinos-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialPublicacionesMaestro = async (params: HistorialPublicacionesMaestroParams): Promise<PublicacionHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
const response = await apiClient.get<PublicacionHistorialDto[]>('/auditoria/publicaciones-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialPubliSeccionesMaestro = async (params: HistorialPubliSeccionesMaestroParams): Promise<PubliSeccionHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
// Pasar los nuevos filtros si están presentes
|
||||
if (params.idPublicacionAfectada) queryParams.idPublicacionAfectada = params.idPublicacionAfectada;
|
||||
|
||||
|
||||
const response = await apiClient.get<PubliSeccionHistorialDto[]>('/auditoria/publi-secciones-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialPreciosMaestro = async (params: HistorialPreciosMaestroParams): Promise<PrecioHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
if (params.idPublicacionAfectada) queryParams.idPublicacionAfectada = params.idPublicacionAfectada;
|
||||
|
||||
|
||||
const response = await apiClient.get<PrecioHistorialDto[]>('/auditoria/precios-publicacion-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialRecargosZonaMaestro = async (params: HistorialRecargosZonaMaestroParams): Promise<RecargoZonaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
if (params.idPublicacionAfectada) queryParams.idPublicacionAfectada = params.idPublicacionAfectada;
|
||||
if (params.idZonaAfectada) queryParams.idZonaAfectada = params.idZonaAfectada;
|
||||
|
||||
|
||||
const response = await apiClient.get<RecargoZonaHistorialDto[]>('/auditoria/recargos-zona-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialPorcPagoDistMaestro = async (params: HistorialPorcPagoDistMaestroParams): Promise<PorcPagoHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
if (params.idPublicacionAfectada) queryParams.idPublicacionAfectada = params.idPublicacionAfectada;
|
||||
if (params.idDistribuidorAfectado) queryParams.idDistribuidorAfectado = params.idDistribuidorAfectado;
|
||||
|
||||
|
||||
const response = await apiClient.get<PorcPagoHistorialDto[]>('/auditoria/porc-pago-dist-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialPorcMonCanillaMaestro = async (params: HistorialPorcMonCanillaMaestroParams): Promise<PorcMonCanillaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
if (params.idPublicacionAfectada) queryParams.idPublicacionAfectada = params.idPublicacionAfectada;
|
||||
if (params.idCanillaAfectada) queryParams.idCanillaAfectada = params.idCanillaAfectada;
|
||||
|
||||
const response = await apiClient.get<PorcMonCanillaHistorialDto[]>('/auditoria/porc-mon-canilla-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialControlDevoluciones = async (params: HistorialControlDevolucionesParams): Promise<ControlDevolucionesHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
if (params.idEmpresaAfectada) queryParams.idEmpresaAfectada = params.idEmpresaAfectada;
|
||||
if (params.fechaAfectada) queryParams.fechaAfectada = params.fechaAfectada;
|
||||
|
||||
|
||||
const response = await apiClient.get<ControlDevolucionesHistorialDto[]>('/auditoria/control-devoluciones-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialTiposBobinaMaestro = async (params: HistorialTiposBobinaMaestroParams): Promise<TipoBobinaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
const response = await apiClient.get<TipoBobinaHistorialDto[]>('/auditoria/tipos-bobina-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialEstadosBobinaMaestro = async (params: HistorialEstadosBobinaMaestroParams): Promise<EstadoBobinaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
const response = await apiClient.get<EstadoBobinaHistorialDto[]>('/auditoria/estados-bobina-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialPlantasMaestro = async (params: HistorialPlantasMaestroParams): Promise<PlantaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
const response = await apiClient.get<PlantaHistorialDto[]>('/auditoria/plantas-impresion-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialStockBobinas = async (params: HistorialStockBobinasParams): Promise<StockBobinaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
const response = await apiClient.get<StockBobinaHistorialDto[]>('/auditoria/stock-bobinas-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialRegTiradas = async (params: HistorialRegTiradasParams): Promise<RegTiradaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
const response = await apiClient.get<RegTiradaHistorialDto[]>('/auditoria/reg-tiradas-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialRegSeccionesTirada = async (params: HistorialRegSeccionesTiradaParams): Promise<RegSeccionTiradaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
// ... (mapeo de idUsuarioModificador)
|
||||
const response = await apiClient.get<RegSeccionTiradaHistorialDto[]>('/auditoria/reg-secciones-tirada-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialPerfilesMaestro = async (params: HistorialPerfilesMaestroParams): Promise<PerfilHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
const response = await apiClient.get<PerfilHistorialDto[]>('/auditoria/perfiles-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialPermisosMaestro = async (params: HistorialPermisosMaestroParams): Promise<PermisoHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
const response = await apiClient.get<PermisoHistorialDto[]>('/auditoria/permisos-maestro', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialPermisosPerfiles = async (params: HistorialPermisosPerfilesParams): Promise<PermisosPerfilesHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
// ... mapeo de idUsuarioModificador ...
|
||||
const response = await apiClient.get<PermisosPerfilesHistorialDto[]>('/auditoria/permisos-perfiles-historial', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const getHistorialCambiosParada = async (params: HistorialCambiosParadaParams): Promise<CambioParadaHistorialDto[]> => {
|
||||
const queryParams: any = { ...params };
|
||||
if (params.idUsuarioModificador) queryParams.idUsuarioModifico = params.idUsuarioModificador;
|
||||
delete queryParams.idUsuarioModificador;
|
||||
|
||||
const response = await apiClient.get<CambioParadaHistorialDto[]>('/auditoria/cambios-parada-canilla', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const auditoriaService = {
|
||||
getHistorialUsuarios,
|
||||
getHistorialPagosDistribuidor,
|
||||
@@ -185,6 +479,25 @@ const auditoriaService = {
|
||||
getHistorialCanillitasMaestro,
|
||||
getHistorialDistribuidoresMaestro,
|
||||
getHistorialEmpresasMaestro,
|
||||
getHistorialZonasMaestro,
|
||||
getHistorialOtrosDestinosMaestro,
|
||||
getHistorialPublicacionesMaestro,
|
||||
getHistorialPubliSeccionesMaestro,
|
||||
getHistorialPreciosMaestro,
|
||||
getHistorialRecargosZonaMaestro,
|
||||
getHistorialPorcPagoDistMaestro,
|
||||
getHistorialPorcMonCanillaMaestro,
|
||||
getHistorialControlDevoluciones,
|
||||
getHistorialTiposBobinaMaestro,
|
||||
getHistorialEstadosBobinaMaestro,
|
||||
getHistorialPlantasMaestro,
|
||||
getHistorialStockBobinas,
|
||||
getHistorialRegTiradas,
|
||||
getHistorialRegSeccionesTirada,
|
||||
getHistorialPerfilesMaestro,
|
||||
getHistorialPermisosMaestro,
|
||||
getHistorialPermisosPerfiles,
|
||||
getHistorialCambiosParada,
|
||||
};
|
||||
|
||||
export default auditoriaService;
|
||||
Reference in New Issue
Block a user