Files
GestionIntegralWeb/Frontend/src/pages/Distribucion/GestionarPorcMonCanillaPage.tsx

189 lines
9.2 KiB
TypeScript
Raw Normal View History

feat: Implementación de Secciones, Recargos, Porc. Pago Dist. y backend E/S Dist. Backend API: - Recargos por Zona (`dist_RecargoZona`): - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador). - Endpoints anidados bajo `/publicaciones/{idPublicacion}/recargos`. - Lógica de negocio para vigencias (cierre/reapertura de períodos). - Auditoría en `dist_RecargoZona_H`. - Porcentajes de Pago Distribuidores (`dist_PorcPago`): - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador). - Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajespago`. - Lógica de negocio para vigencias. - Auditoría en `dist_PorcPago_H`. - Porcentajes/Montos Pago Canillitas (`dist_PorcMonPagoCanilla`): - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador). - Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajesmoncanilla`. - Lógica de negocio para vigencias. - Auditoría en `dist_PorcMonPagoCanilla_H`. - Secciones de Publicación (`dist_dtPubliSecciones`): - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador). - Endpoints anidados bajo `/publicaciones/{idPublicacion}/secciones`. - Auditoría en `dist_dtPubliSecciones_H`. - Entradas/Salidas Distribuidores (`dist_EntradasSalidas`): - Implementado backend (Modelos, DTOs, Repositorio, Servicio, Controlador). - Lógica para determinar precios/recargos/porcentajes aplicables. - Cálculo de monto y afectación de saldos de distribuidores en `cue_Saldos`. - Auditoría en `dist_EntradasSalidas_H`. - Correcciones de Mapeo Dapper: - Aplicados alias explícitos en repositorios de RecargoZona, PorcPago, PorcMonCanilla, PubliSeccion, Canilla, Distribuidor y Precio para asegurar mapeo correcto de IDs y columnas. Frontend React: - Recargos por Zona: - `recargoZonaService.ts`. - `RecargoZonaFormModal.tsx` para crear/editar períodos de recargos. - `GestionarRecargosPublicacionPage.tsx` para listar y gestionar recargos por publicación. - Porcentajes de Pago Distribuidores: - `porcPagoService.ts`. - `PorcPagoFormModal.tsx`. - `GestionarPorcentajesPagoPage.tsx`. - Porcentajes/Montos Pago Canillitas: - `porcMonCanillaService.ts`. - `PorcMonCanillaFormModal.tsx`. - `GestionarPorcMonCanillaPage.tsx`. - Secciones de Publicación: - `publiSeccionService.ts`. - `PubliSeccionFormModal.tsx`. - `GestionarSeccionesPublicacionPage.tsx`. - Navegación: - Actualizadas rutas y menús para acceder a la gestión de recargos, porcentajes (dist. y canillita) y secciones desde la vista de una publicación. - Layout: - Uso consistente de `Box` con Flexbox en lugar de `Grid` en nuevos modales y páginas para evitar errores de tipo.
2025-05-21 14:58:52 -03:00
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
} from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import porcMonCanillaService from '../../services/Distribucion/porcMonCanillaService';
import publicacionService from '../../services/Distribucion/publicacionService';
import type { PorcMonCanillaDto } from '../../models/dtos/Distribucion/PorcMonCanillaDto';
import type { CreatePorcMonCanillaDto } from '../../models/dtos/Distribucion/CreatePorcMonCanillaDto';
import type { UpdatePorcMonCanillaDto } from '../../models/dtos/Distribucion/UpdatePorcMonCanillaDto';
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
import PorcMonCanillaFormModal from '../../components/Modals/Distribucion/PorcMonCanillaFormModal';
import { usePermissions } from '../../hooks/usePermissions';
import axios from 'axios';
const GestionarPorcMonCanillaPage: React.FC = () => {
const { idPublicacion: idPublicacionStr } = useParams<{ idPublicacion: string }>();
const navigate = useNavigate();
const idPublicacion = Number(idPublicacionStr);
const [publicacion, setPublicacion] = useState<PublicacionDto | null>(null);
const [items, setItems] = useState<PorcMonCanillaDto[]>([]); // Renombrado de 'porcentajes' a 'items'
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [modalOpen, setModalOpen] = useState(false);
const [editingItem, setEditingItem] = useState<PorcMonCanillaDto | null>(null);
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [selectedRow, setSelectedRow] = useState<PorcMonCanillaDto | null>(null);
const { tienePermiso, isSuperAdmin } = usePermissions();
// Permiso CG004 para porcentajes/montos de pago de canillitas
const puedeGestionar = isSuperAdmin || tienePermiso("CG004");
const cargarDatos = useCallback(async () => {
if (isNaN(idPublicacion)) {
setError("ID de Publicación inválido."); setLoading(false); return;
}
if (!puedeGestionar) {
setError("No tiene permiso para gestionar esta configuración."); setLoading(false); return;
}
setLoading(true); setError(null); setApiErrorMessage(null);
try {
const [pubData, data] = await Promise.all([
publicacionService.getPublicacionById(idPublicacion),
porcMonCanillaService.getPorcMonCanillaPorPublicacion(idPublicacion)
]);
setPublicacion(pubData);
setItems(data);
} catch (err: any) {
console.error(err);
if (axios.isAxiosError(err) && err.response?.status === 404) {
setError(`Publicación ID ${idPublicacion} no encontrada.`);
} else {
setError('Error al cargar los datos.');
}
} finally { setLoading(false); }
}, [idPublicacion, puedeGestionar]);
useEffect(() => { cargarDatos(); }, [cargarDatos]);
const handleOpenModal = (item?: PorcMonCanillaDto) => {
setEditingItem(item || null); setApiErrorMessage(null); setModalOpen(true);
};
const handleCloseModal = () => {
setModalOpen(false); setEditingItem(null);
};
const handleSubmitModal = async (data: CreatePorcMonCanillaDto | UpdatePorcMonCanillaDto, idPorcMon?: number) => {
setApiErrorMessage(null);
try {
if (editingItem && idPorcMon) {
await porcMonCanillaService.updatePorcMonCanilla(idPublicacion, idPorcMon, data as UpdatePorcMonCanillaDto);
} else {
await porcMonCanillaService.createPorcMonCanilla(idPublicacion, data as CreatePorcMonCanillaDto);
}
cargarDatos();
} catch (err: any) {
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar.';
setApiErrorMessage(message); throw err;
}
};
const handleDelete = async (idPorcMonDelRow: number) => {
if (window.confirm(`¿Seguro de eliminar este registro (ID: ${idPorcMonDelRow})?`)) {
setApiErrorMessage(null);
try {
await porcMonCanillaService.deletePorcMonCanilla(idPublicacion, idPorcMonDelRow);
cargarDatos();
} catch (err: any) {
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.';
setApiErrorMessage(message);
}
}
handleMenuClose();
};
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: PorcMonCanillaDto) => {
setAnchorEl(event.currentTarget); setSelectedRow(item);
};
const handleMenuClose = () => {
setAnchorEl(null); setSelectedRow(null);
};
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00').toLocaleDateString('es-AR') : '-';
if (loading) return <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}><CircularProgress /></Box>;
if (error) return <Alert severity="error" sx={{ m: 2 }}>{error}</Alert>;
if (!puedeGestionar) 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>
<Typography variant="h4" gutterBottom>Porcentajes/Montos Pago Canillita: {publicacion?.nombre || 'Cargando...'}</Typography>
<Typography variant="subtitle1" color="text.secondary" gutterBottom>Empresa: {publicacion?.nombreEmpresa || '-'}</Typography>
<Paper sx={{ p: 2, mb: 2 }}>
{puedeGestionar && (
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>
Agregar Configuración
</Button>
)}
</Paper>
{apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>}
<TableContainer component={Paper}>
<Table size="small">
<TableHead><TableRow>
<TableCell sx={{fontWeight: 'bold'}}>Canillita</TableCell>
<TableCell sx={{fontWeight: 'bold'}}>Vig. Desde</TableCell>
<TableCell sx={{fontWeight: 'bold'}}>Vig. Hasta</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold'}}>Valor</TableCell>
<TableCell align="center" sx={{fontWeight: 'bold'}}>Tipo</TableCell>
<TableCell align="center" sx={{fontWeight: 'bold'}}>Estado</TableCell>
<TableCell align="right" sx={{fontWeight: 'bold'}}>Acciones</TableCell>
</TableRow></TableHead>
<TableBody>
{items.length === 0 ? (
<TableRow><TableCell colSpan={7} align="center">No hay configuraciones definidas.</TableCell></TableRow>
) : (
items.sort((a,b) => new Date(b.vigenciaD).getTime() - new Date(a.vigenciaD).getTime() || a.nomApeCanilla.localeCompare(b.nomApeCanilla))
.map((item) => (
<TableRow key={item.idPorcMon} hover>
<TableCell>{item.nomApeCanilla}</TableCell><TableCell>{formatDate(item.vigenciaD)}</TableCell>
<TableCell>{formatDate(item.vigenciaH)}</TableCell>
<TableCell align="right">{item.esPorcentaje ? `${item.porcMon.toFixed(2)}%` : `$${item.porcMon.toFixed(2)}`}</TableCell>
<TableCell align="center">{item.esPorcentaje ? <Chip label="%" color="primary" size="small" variant="outlined"/> : <Chip label="Monto" color="secondary" size="small" variant="outlined"/>}</TableCell>
<TableCell align="center">{!item.vigenciaH ? <Chip label="Activo" color="success" size="small" /> : <Chip label="Cerrado" size="small" />}</TableCell>
<TableCell align="right">
<IconButton onClick={(e) => handleMenuOpen(e, item)} disabled={!puedeGestionar}><MoreVertIcon /></IconButton>
</TableCell>
</TableRow>
)))}
</TableBody>
</Table>
</TableContainer>
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
{puedeGestionar && selectedRow && (
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{mr:1}}/> Editar/Cerrar</MenuItem>)}
{puedeGestionar && selectedRow && (
<MenuItem onClick={() => handleDelete(selectedRow.idPorcMon)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar</MenuItem>)}
</Menu>
{idPublicacion &&
<PorcMonCanillaFormModal
open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal}
idPublicacion={idPublicacion} initialData={editingItem}
errorMessage={apiErrorMessage} clearErrorMessage={() => setApiErrorMessage(null)}
/>
}
</Box>
);
};
export default GestionarPorcMonCanillaPage;