Ya perdí el hilo de los cambios pero ahi van.
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
|
||||
const ESCanillasPage: React.FC = () => {
|
||||
return <Typography variant="h6">Página de Gestión de E/S de Canillas</Typography>;
|
||||
};
|
||||
export default ESCanillasPage;
|
||||
@@ -0,0 +1,220 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||
CircularProgress, Alert, FormControl, InputLabel, Select, Tooltip
|
||||
} 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 FilterListIcon from '@mui/icons-material/FilterList';
|
||||
|
||||
import controlDevolucionesService from '../../services/Distribucion/controlDevolucionesService';
|
||||
import empresaService from '../../services/Distribucion/empresaService'; // Para el filtro de empresa
|
||||
|
||||
import type { ControlDevolucionesDto } from '../../models/dtos/Distribucion/ControlDevolucionesDto';
|
||||
import type { CreateControlDevolucionesDto } from '../../models/dtos/Distribucion/CreateControlDevolucionesDto';
|
||||
import type { UpdateControlDevolucionesDto } from '../../models/dtos/Distribucion/UpdateControlDevolucionesDto';
|
||||
import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto';
|
||||
|
||||
import ControlDevolucionesFormModal from '../../components/Modals/Distribucion/ControlDevolucionesFormModal';
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
import axios from 'axios';
|
||||
|
||||
const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
const [controles, setControles] = useState<ControlDevolucionesDto[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
||||
|
||||
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingControl, setEditingControl] = useState<ControlDevolucionesDto | null>(null);
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [selectedRow, setSelectedRow] = useState<ControlDevolucionesDto | null>(null);
|
||||
|
||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||
// Permisos CD001 (Ver), CD002 (Crear), CD003 (Modificar)
|
||||
const puedeVer = isSuperAdmin || tienePermiso("CD001");
|
||||
const puedeCrear = isSuperAdmin || tienePermiso("CD002");
|
||||
const puedeModificar = isSuperAdmin || tienePermiso("CD003");
|
||||
const puedeEliminar = isSuperAdmin || tienePermiso("CD003"); // Asumiendo que modificar incluye eliminar
|
||||
|
||||
const fetchFiltersDropdownData = useCallback(async () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const empresasData = await empresaService.getAllEmpresas();
|
||||
setEmpresas(empresasData);
|
||||
} catch (err) {
|
||||
console.error("Error cargando empresas para filtro:", err);
|
||||
setError("Error al cargar opciones de filtro.");
|
||||
} finally {
|
||||
setLoadingFiltersDropdown(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => { fetchFiltersDropdownData(); }, [fetchFiltersDropdownData]);
|
||||
|
||||
const cargarControles = useCallback(async () => {
|
||||
if (!puedeVer) {
|
||||
setError("No tiene permiso para ver esta sección."); setLoading(false); return;
|
||||
}
|
||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
||||
try {
|
||||
const params = {
|
||||
fechaDesde: filtroFechaDesde || null,
|
||||
fechaHasta: filtroFechaHasta || null,
|
||||
idEmpresa: filtroIdEmpresa ? Number(filtroIdEmpresa) : null,
|
||||
};
|
||||
const data = await controlDevolucionesService.getAllControlesDevoluciones(params);
|
||||
setControles(data);
|
||||
} catch (err) {
|
||||
console.error(err); setError('Error al cargar los controles de devoluciones.');
|
||||
} finally { setLoading(false); }
|
||||
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdEmpresa]);
|
||||
|
||||
useEffect(() => { cargarControles(); }, [cargarControles]);
|
||||
|
||||
const handleOpenModal = (item?: ControlDevolucionesDto) => {
|
||||
setEditingControl(item || null); setApiErrorMessage(null); setModalOpen(true);
|
||||
};
|
||||
const handleCloseModal = () => {
|
||||
setModalOpen(false); setEditingControl(null);
|
||||
};
|
||||
|
||||
const handleSubmitModal = async (data: CreateControlDevolucionesDto | UpdateControlDevolucionesDto, idControl?: number) => {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
if (idControl && editingControl) {
|
||||
await controlDevolucionesService.updateControlDevoluciones(idControl, data as UpdateControlDevolucionesDto);
|
||||
} else {
|
||||
await controlDevolucionesService.createControlDevoluciones(data as CreateControlDevolucionesDto);
|
||||
}
|
||||
cargarControles();
|
||||
} catch (err: any) {
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar el control.';
|
||||
setApiErrorMessage(message); throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (idControl: number) => {
|
||||
if (window.confirm(`¿Seguro de eliminar este control de devoluciones (ID: ${idControl})?`)) {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
await controlDevolucionesService.deleteControlDevoluciones(idControl);
|
||||
cargarControles();
|
||||
} 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: ControlDevolucionesDto) => {
|
||||
setAnchorEl(event.currentTarget); setSelectedRow(item);
|
||||
};
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null); setSelectedRow(null);
|
||||
};
|
||||
|
||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||
};
|
||||
const displayData = controles.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
||||
|
||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h4" gutterBottom>Control de Devoluciones a Empresa</Typography>
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
||||
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170}}/>
|
||||
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170}}/>
|
||||
<FormControl size="small" sx={{minWidth: 200, flexGrow: 1}} disabled={loadingFiltersDropdown}>
|
||||
<InputLabel>Empresa</InputLabel>
|
||||
<Select value={filtroIdEmpresa} label="Empresa" onChange={(e) => setFiltroIdEmpresa(e.target.value as number | string)}>
|
||||
<MenuItem value=""><em>Todas</em></MenuItem>
|
||||
{empresas.map(e => <MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{puedeCrear && (<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()}>Registrar Control</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>}
|
||||
|
||||
{!loading && !error && puedeVer && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead><TableRow>
|
||||
<TableCell>Fecha</TableCell><TableCell>Empresa</TableCell>
|
||||
<TableCell align="right">Entrada (Total Dev.)</TableCell>
|
||||
<TableCell align="right">Sobrantes</TableCell>
|
||||
<TableCell align="right">Sin Cargo</TableCell>
|
||||
<TableCell>Detalle</TableCell>
|
||||
{(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{displayData.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={puedeModificar || puedeEliminar ? 7 : 6} align="center">No se encontraron controles.</TableCell></TableRow>
|
||||
) : (
|
||||
displayData.map((c) => (
|
||||
<TableRow key={c.idControl} hover>
|
||||
<TableCell>{formatDate(c.fecha)}</TableCell>
|
||||
<TableCell>{c.nombreEmpresa}</TableCell>
|
||||
<TableCell align="right">{c.entrada}</TableCell>
|
||||
<TableCell align="right">{c.sobrantes}</TableCell>
|
||||
<TableCell align="right">{c.sinCargo}</TableCell>
|
||||
<TableCell><Tooltip title={c.detalle || ''}><Box sx={{maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>{c.detalle || '-'}</Box></Tooltip></TableCell>
|
||||
{(puedeModificar || puedeEliminar) && (
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, c)} disabled={!puedeModificar && !puedeEliminar}><MoreVertIcon /></IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
)))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[10, 25, 50]} component="div" count={controles.length}
|
||||
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
|
||||
/>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
{puedeModificar && selectedRow && (
|
||||
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{mr:1}}/> Modificar</MenuItem>)}
|
||||
{puedeEliminar && selectedRow && (
|
||||
<MenuItem onClick={() => handleDelete(selectedRow.idControl)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar</MenuItem>)}
|
||||
</Menu>
|
||||
|
||||
<ControlDevolucionesFormModal
|
||||
open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal}
|
||||
initialData={editingControl} errorMessage={apiErrorMessage}
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default GestionarControlDevolucionesPage;
|
||||
@@ -0,0 +1,369 @@
|
||||
// src/pages/Distribucion/GestionarEntradasSalidasCanillaPage.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, Checkbox, Tooltip,
|
||||
Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle
|
||||
} 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 FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import PlaylistAddCheckIcon from '@mui/icons-material/PlaylistAddCheck'; // Para Liquidar
|
||||
|
||||
import entradaSalidaCanillaService from '../../services/Distribucion/entradaSalidaCanillaService';
|
||||
import publicacionService from '../../services/Distribucion/publicacionService';
|
||||
import canillaService from '../../services/Distribucion/canillaService';
|
||||
|
||||
import type { EntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/EntradaSalidaCanillaDto';
|
||||
import type { CreateEntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/CreateEntradaSalidaCanillaDto';
|
||||
import type { UpdateEntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/UpdateEntradaSalidaCanillaDto';
|
||||
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||
import type { CanillaDto } from '../../models/dtos/Distribucion/CanillaDto';
|
||||
import type { LiquidarMovimientosCanillaRequestDto } from '../../models/dtos/Distribucion/LiquidarMovimientosCanillaDto';
|
||||
|
||||
|
||||
import EntradaSalidaCanillaFormModal from '../../components/Modals/Distribucion/EntradaSalidaCanillaFormModal';
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
import axios from 'axios';
|
||||
|
||||
const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
const [movimientos, setMovimientos] = useState<EntradaSalidaCanillaDto[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||
const [filtroIdCanilla, setFiltroIdCanilla] = useState<number | string>('');
|
||||
const [filtroEstadoLiquidacion, setFiltroEstadoLiquidacion] = useState<'todos' | 'liquidados' | 'noLiquidados'>('noLiquidados');
|
||||
|
||||
|
||||
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||
const [canillitas, setCanillitas] = useState<CanillaDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingMovimiento, setEditingMovimiento] = useState<EntradaSalidaCanillaDto | null>(null);
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [selectedRow, setSelectedRow] = useState<EntradaSalidaCanillaDto | null>(null);
|
||||
const [selectedIdsParaLiquidar, setSelectedIdsParaLiquidar] = useState<Set<number>>(new Set());
|
||||
const [fechaLiquidacionDialog, setFechaLiquidacionDialog] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
const [openLiquidarDialog, setOpenLiquidarDialog] = useState(false);
|
||||
|
||||
|
||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||
// MC001 (Ver), MC002 (Crear), MC003 (Modificar), MC004 (Eliminar), MC005 (Liquidar)
|
||||
const puedeVer = isSuperAdmin || tienePermiso("MC001");
|
||||
const puedeCrear = isSuperAdmin || tienePermiso("MC002");
|
||||
const puedeModificar = isSuperAdmin || tienePermiso("MC003");
|
||||
const puedeEliminar = isSuperAdmin || tienePermiso("MC004");
|
||||
const puedeLiquidar = isSuperAdmin || tienePermiso("MC005");
|
||||
const puedeEliminarLiquidados = isSuperAdmin || tienePermiso("MC006");
|
||||
|
||||
const fetchFiltersDropdownData = useCallback(async () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const [pubsData, canData] = await Promise.all([
|
||||
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||
canillaService.getAllCanillas(undefined, undefined, true)
|
||||
]);
|
||||
setPublicaciones(pubsData);
|
||||
setCanillitas(canData);
|
||||
} catch (err) {
|
||||
console.error(err); setError("Error al cargar opciones de filtro.");
|
||||
} finally { setLoadingFiltersDropdown(false); }
|
||||
}, []);
|
||||
|
||||
useEffect(() => { fetchFiltersDropdownData(); }, [fetchFiltersDropdownData]);
|
||||
|
||||
const cargarMovimientos = useCallback(async () => {
|
||||
if (!puedeVer) { setError("No tiene permiso."); setLoading(false); return; }
|
||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
||||
try {
|
||||
let liquidadosFilter: boolean | null = null;
|
||||
let incluirNoLiquidadosFilter: boolean | null = true; // Por defecto mostrar no liquidados
|
||||
|
||||
if (filtroEstadoLiquidacion === 'liquidados') {
|
||||
liquidadosFilter = true;
|
||||
incluirNoLiquidadosFilter = false;
|
||||
} else if (filtroEstadoLiquidacion === 'noLiquidados') {
|
||||
liquidadosFilter = false;
|
||||
incluirNoLiquidadosFilter = true;
|
||||
} // Si es 'todos', ambos son null o true y false respectivamente (backend debe manejarlo)
|
||||
|
||||
|
||||
const params = {
|
||||
fechaDesde: filtroFechaDesde || null, fechaHasta: filtroFechaHasta || null,
|
||||
idPublicacion: filtroIdPublicacion ? Number(filtroIdPublicacion) : null,
|
||||
idCanilla: filtroIdCanilla ? Number(filtroIdCanilla) : null,
|
||||
liquidados: liquidadosFilter,
|
||||
incluirNoLiquidados: filtroEstadoLiquidacion === 'todos' ? null : incluirNoLiquidadosFilter,
|
||||
};
|
||||
const data = await entradaSalidaCanillaService.getAllEntradasSalidasCanilla(params);
|
||||
setMovimientos(data);
|
||||
setSelectedIdsParaLiquidar(new Set()); // Limpiar selección al recargar
|
||||
} catch (err) {
|
||||
console.error(err); setError('Error al cargar movimientos.');
|
||||
} finally { setLoading(false); }
|
||||
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdPublicacion, filtroIdCanilla, filtroEstadoLiquidacion]);
|
||||
|
||||
useEffect(() => { cargarMovimientos(); }, [cargarMovimientos]);
|
||||
|
||||
const handleOpenModal = (item?: EntradaSalidaCanillaDto) => {
|
||||
setEditingMovimiento(item || null); setApiErrorMessage(null); setModalOpen(true);
|
||||
};
|
||||
const handleCloseModal = () => { setModalOpen(false); setEditingMovimiento(null); };
|
||||
|
||||
const handleSubmitModal = async (data: CreateEntradaSalidaCanillaDto | UpdateEntradaSalidaCanillaDto, idParte?: number) => {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
if (idParte && editingMovimiento) {
|
||||
await entradaSalidaCanillaService.updateEntradaSalidaCanilla(idParte, data as UpdateEntradaSalidaCanillaDto);
|
||||
} else {
|
||||
await entradaSalidaCanillaService.createEntradaSalidaCanilla(data as CreateEntradaSalidaCanillaDto);
|
||||
}
|
||||
cargarMovimientos();
|
||||
} 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 (idParte: number) => {
|
||||
if (window.confirm(`¿Seguro de eliminar este movimiento (ID: ${idParte})?`)) {
|
||||
setApiErrorMessage(null);
|
||||
try { await entradaSalidaCanillaService.deleteEntradaSalidaCanilla(idParte); cargarMovimientos(); }
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.'; setApiErrorMessage(msg); }
|
||||
}
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: EntradaSalidaCanillaDto) => {
|
||||
setAnchorEl(event.currentTarget); setSelectedRow(item);
|
||||
};
|
||||
const handleMenuClose = () => { setAnchorEl(null); setSelectedRow(null); };
|
||||
|
||||
const handleSelectRowForLiquidar = (idParte: number) => {
|
||||
setSelectedIdsParaLiquidar(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(idParte)) newSet.delete(idParte);
|
||||
else newSet.add(idParte);
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
const handleSelectAllForLiquidar = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
const newSelectedIds = new Set(movimientos.filter(m => !m.liquidado).map(m => m.idParte));
|
||||
setSelectedIdsParaLiquidar(newSelectedIds);
|
||||
} else {
|
||||
setSelectedIdsParaLiquidar(new Set());
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenLiquidarDialog = () => {
|
||||
if (selectedIdsParaLiquidar.size === 0) {
|
||||
setApiErrorMessage("Seleccione al menos un movimiento para liquidar.");
|
||||
return;
|
||||
}
|
||||
setOpenLiquidarDialog(true);
|
||||
};
|
||||
const handleCloseLiquidarDialog = () => setOpenLiquidarDialog(false);
|
||||
const handleConfirmLiquidar = async () => {
|
||||
setApiErrorMessage(null); setLoading(true);
|
||||
const liquidarDto: LiquidarMovimientosCanillaRequestDto = {
|
||||
idsPartesALiquidar: Array.from(selectedIdsParaLiquidar),
|
||||
fechaLiquidacion: fechaLiquidacionDialog
|
||||
};
|
||||
try {
|
||||
await entradaSalidaCanillaService.liquidarMovimientos(liquidarDto);
|
||||
cargarMovimientos(); // Recargar para ver los cambios
|
||||
setOpenLiquidarDialog(false);
|
||||
} catch (err: any) {
|
||||
const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al liquidar.';
|
||||
setApiErrorMessage(msg);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||
};
|
||||
const displayData = movimientos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
||||
|
||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||
|
||||
const numSelectedToLiquidate = selectedIdsParaLiquidar.size;
|
||||
const numNotLiquidatedOnPage = displayData.filter(m => !m.liquidado).length;
|
||||
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h4" gutterBottom>Entradas/Salidas Canillitas</Typography>
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
||||
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} />
|
||||
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} />
|
||||
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }} disabled={loadingFiltersDropdown}>
|
||||
<InputLabel>Publicación</InputLabel>
|
||||
<Select value={filtroIdPublicacion} label="Publicación" onChange={(e) => setFiltroIdPublicacion(e.target.value as number | string)}>
|
||||
<MenuItem value=""><em>Todas</em></MenuItem>
|
||||
{publicaciones.map(p => <MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 200, flexGrow: 1 }} disabled={loadingFiltersDropdown}>
|
||||
<InputLabel>Canillita</InputLabel>
|
||||
<Select value={filtroIdCanilla} label="Canillita" onChange={(e) => setFiltroIdCanilla(e.target.value as number | string)}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
{canillitas.map(c => <MenuItem key={c.idCanilla} value={c.idCanilla}>{c.nomApe}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }}>
|
||||
<InputLabel>Estado Liquidación</InputLabel>
|
||||
<Select value={filtroEstadoLiquidacion} label="Estado Liquidación" onChange={(e) => setFiltroEstadoLiquidacion(e.target.value as 'todos' | 'liquidados' | 'noLiquidados')}>
|
||||
<MenuItem value="noLiquidados">No Liquidados</MenuItem>
|
||||
<MenuItem value="liquidados">Liquidados</MenuItem>
|
||||
<MenuItem value="todos">Todos</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
{puedeCrear && (<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()}>Registrar Movimiento</Button>)}
|
||||
{puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' && numSelectedToLiquidate > 0 && (
|
||||
<Button variant="contained" color="success" startIcon={<PlaylistAddCheckIcon />} onClick={handleOpenLiquidarDialog}>
|
||||
Liquidar Seleccionados ({numSelectedToLiquidate})
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</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>}
|
||||
|
||||
{!loading && !error && puedeVer && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead><TableRow>
|
||||
{puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' &&
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
indeterminate={numSelectedToLiquidate > 0 && numSelectedToLiquidate < numNotLiquidatedOnPage}
|
||||
checked={numNotLiquidatedOnPage > 0 && numSelectedToLiquidate === numNotLiquidatedOnPage}
|
||||
onChange={handleSelectAllForLiquidar}
|
||||
disabled={numNotLiquidatedOnPage === 0}
|
||||
/>
|
||||
</TableCell>
|
||||
}
|
||||
<TableCell>Fecha</TableCell><TableCell>Publicación</TableCell><TableCell>Canillita</TableCell>
|
||||
<TableCell align="right">Salida</TableCell><TableCell align="right">Entrada</TableCell>
|
||||
<TableCell align="right">Vendidos</TableCell><TableCell align="right">A Rendir</TableCell>
|
||||
<TableCell>Liquidado</TableCell><TableCell>F. Liq.</TableCell><TableCell>Obs.</TableCell>
|
||||
{(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{displayData.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' ? 12 : 11} align="center">No se encontraron movimientos.</TableCell></TableRow>
|
||||
) : (
|
||||
displayData.map((m) => (
|
||||
<TableRow key={m.idParte} hover selected={selectedIdsParaLiquidar.has(m.idParte)}>
|
||||
{puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' &&
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={selectedIdsParaLiquidar.has(m.idParte)}
|
||||
onChange={() => handleSelectRowForLiquidar(m.idParte)}
|
||||
disabled={m.liquidado}
|
||||
/>
|
||||
</TableCell>
|
||||
}
|
||||
<TableCell>{formatDate(m.fecha)}</TableCell>
|
||||
<TableCell>{m.nombrePublicacion}</TableCell>
|
||||
<TableCell>{m.nomApeCanilla}</TableCell>
|
||||
<TableCell align="right">{m.cantSalida}</TableCell>
|
||||
<TableCell align="right">{m.cantEntrada}</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>{m.vendidos}</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>${m.montoARendir.toFixed(2)}</TableCell>
|
||||
<TableCell align="center">{m.liquidado ? <Chip label="Sí" color="success" size="small" /> : <Chip label="No" size="small" />}</TableCell>
|
||||
<TableCell>{m.fechaLiquidado ? formatDate(m.fechaLiquidado) : '-'}</TableCell>
|
||||
<TableCell><Tooltip title={m.observacion || ''}><Box sx={{ maxWidth: 100, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.observacion || '-'}</Box></Tooltip></TableCell>
|
||||
{(puedeModificar || puedeEliminar) && (
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
onClick={(e) => handleMenuOpen(e, m)}
|
||||
disabled={
|
||||
// Deshabilitar si no tiene ningún permiso de eliminación O
|
||||
// si está liquidado y no tiene permiso para eliminar liquidados
|
||||
!((!m.liquidado && puedeEliminar) || (m.liquidado && puedeEliminarLiquidados))
|
||||
}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
)))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[10, 25, 50, 100]} component="div" count={movimientos.length}
|
||||
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
|
||||
/>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
{puedeModificar && selectedRow && !selectedRow.liquidado && (
|
||||
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{ mr: 1 }} /> Modificar</MenuItem>)}
|
||||
{selectedRow && (
|
||||
(!selectedRow.liquidado && puedeEliminar) || (selectedRow.liquidado && puedeEliminarLiquidados)
|
||||
) && (
|
||||
<MenuItem onClick={() => handleDelete(selectedRow.idParte)}>
|
||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
|
||||
<EntradaSalidaCanillaFormModal
|
||||
open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal}
|
||||
initialData={editingMovimiento} errorMessage={apiErrorMessage}
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
/>
|
||||
|
||||
<Dialog open={openLiquidarDialog} onClose={handleCloseLiquidarDialog}>
|
||||
<DialogTitle>Confirmar Liquidación</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Se marcarán como liquidados {selectedIdsParaLiquidar.size} movimiento(s).
|
||||
</DialogContentText>
|
||||
<TextField autoFocus margin="dense" id="fechaLiquidacion" label="Fecha de Liquidación" type="date"
|
||||
fullWidth variant="standard" value={fechaLiquidacionDialog}
|
||||
onChange={(e) => setFechaLiquidacionDialog(e.target.value)}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCloseLiquidarDialog} color="secondary" disabled={loading}>Cancelar</Button>
|
||||
<Button onClick={handleConfirmLiquidar} variant="contained" color="primary" disabled={loading || !fechaLiquidacionDialog}>
|
||||
{loading ? <CircularProgress size={24} /> : "Liquidar"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default GestionarEntradasSalidasCanillaPage;
|
||||
@@ -1,8 +1,9 @@
|
||||
// src/pages/Distribucion/GestionarEntradasSalidasDistPage.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, Tooltip
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
@@ -52,36 +53,30 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
||||
|
||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||
const puedeVer = isSuperAdmin || tienePermiso("MD001");
|
||||
const puedeGestionar = isSuperAdmin || tienePermiso("MD002"); // Para Crear, Editar, Eliminar
|
||||
const puedeGestionar = isSuperAdmin || tienePermiso("MD002");
|
||||
|
||||
const fetchFiltersDropdownData = useCallback(async () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const [pubsData, distData] = await Promise.all([
|
||||
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||
distribuidorService.getAllDistribuidores()
|
||||
]);
|
||||
setPublicaciones(pubsData);
|
||||
setDistribuidores(distData);
|
||||
const [pubsData, distData] = await Promise.all([
|
||||
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||
distribuidorService.getAllDistribuidores()
|
||||
]);
|
||||
setPublicaciones(pubsData);
|
||||
setDistribuidores(distData);
|
||||
} catch (err) {
|
||||
console.error("Error cargando datos para filtros:", err);
|
||||
setError("Error al cargar opciones de filtro.");
|
||||
} finally {
|
||||
setLoadingFiltersDropdown(false);
|
||||
}
|
||||
console.error(err); setError("Error al cargar opciones de filtro.");
|
||||
} finally { setLoadingFiltersDropdown(false); }
|
||||
}, []);
|
||||
|
||||
useEffect(() => { fetchFiltersDropdownData(); }, [fetchFiltersDropdownData]);
|
||||
|
||||
const cargarMovimientos = useCallback(async () => {
|
||||
if (!puedeVer) {
|
||||
setError("No tiene permiso para ver esta sección."); setLoading(false); return;
|
||||
}
|
||||
if (!puedeVer) { setError("No tiene permiso."); setLoading(false); return; }
|
||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
||||
try {
|
||||
const params = {
|
||||
fechaDesde: filtroFechaDesde || null,
|
||||
fechaHasta: filtroFechaHasta || null,
|
||||
fechaDesde: filtroFechaDesde || null, fechaHasta: filtroFechaHasta || null,
|
||||
idPublicacion: filtroIdPublicacion ? Number(filtroIdPublicacion) : null,
|
||||
idDistribuidor: filtroIdDistribuidor ? Number(filtroIdDistribuidor) : null,
|
||||
tipoMovimiento: filtroTipoMov || null,
|
||||
@@ -89,7 +84,7 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
||||
const data = await entradaSalidaDistService.getAllEntradasSalidasDist(params);
|
||||
setMovimientos(data);
|
||||
} catch (err) {
|
||||
console.error(err); setError('Error al cargar los movimientos.');
|
||||
console.error(err); setError('Error al cargar movimientos.');
|
||||
} finally { setLoading(false); }
|
||||
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdPublicacion, filtroIdDistribuidor, filtroTipoMov]);
|
||||
|
||||
@@ -98,9 +93,7 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
||||
const handleOpenModal = (item?: EntradaSalidaDistDto) => {
|
||||
setEditingMovimiento(item || null); setApiErrorMessage(null); setModalOpen(true);
|
||||
};
|
||||
const handleCloseModal = () => {
|
||||
setModalOpen(false); setEditingMovimiento(null);
|
||||
};
|
||||
const handleCloseModal = () => { setModalOpen(false); setEditingMovimiento(null); };
|
||||
|
||||
const handleSubmitModal = async (data: CreateEntradaSalidaDistDto | UpdateEntradaSalidaDistDto, idParte?: number) => {
|
||||
setApiErrorMessage(null);
|
||||
@@ -112,21 +105,16 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
||||
}
|
||||
cargarMovimientos();
|
||||
} catch (err: any) {
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar el movimiento.';
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar.';
|
||||
setApiErrorMessage(message); throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (idParte: number) => {
|
||||
if (window.confirm(`¿Seguro de eliminar este movimiento (ID: ${idParte})? Esta acción revertirá el impacto en el saldo del distribuidor.`)) {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
await entradaSalidaDistService.deleteEntradaSalidaDist(idParte);
|
||||
cargarMovimientos();
|
||||
} catch (err: any) {
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.';
|
||||
setApiErrorMessage(message);
|
||||
}
|
||||
if (window.confirm(`¿Seguro (ID: ${idParte})? Esto revertirá el saldo.`)) {
|
||||
setApiErrorMessage(null);
|
||||
try { await entradaSalidaDistService.deleteEntradaSalidaDist(idParte); cargarMovimientos(); }
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.'; setApiErrorMessage(msg); }
|
||||
}
|
||||
handleMenuClose();
|
||||
};
|
||||
@@ -134,9 +122,7 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: EntradaSalidaDistDto) => {
|
||||
setAnchorEl(event.currentTarget); setSelectedRow(item);
|
||||
};
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null); setSelectedRow(null);
|
||||
};
|
||||
const handleMenuClose = () => { setAnchorEl(null); setSelectedRow(null); };
|
||||
|
||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -145,97 +131,96 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
||||
const displayData = movimientos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
||||
|
||||
|
||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h4" gutterBottom>Entradas/Salidas Distribuidores</Typography>
|
||||
<Typography variant="h4" gutterBottom>Entradas/Salidas a Distribuidores</Typography>
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
||||
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170}}/>
|
||||
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170}}/>
|
||||
<FormControl size="small" sx={{minWidth: 200, flexGrow: 1}} disabled={loadingFiltersDropdown}>
|
||||
<InputLabel>Publicación</InputLabel>
|
||||
<Select value={filtroIdPublicacion} label="Publicación" onChange={(e) => setFiltroIdPublicacion(e.target.value as number | string)}>
|
||||
<MenuItem value=""><em>Todas</em></MenuItem>
|
||||
{publicaciones.map(p => <MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{minWidth: 200, flexGrow: 1}} disabled={loadingFiltersDropdown}>
|
||||
<InputLabel>Distribuidor</InputLabel>
|
||||
<Select value={filtroIdDistribuidor} label="Distribuidor" onChange={(e) => setFiltroIdDistribuidor(e.target.value as number | string)}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
{distribuidores.map(d => <MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{minWidth: 150, flexGrow: 1}}>
|
||||
<InputLabel>Tipo</InputLabel>
|
||||
<Select value={filtroTipoMov} label="Tipo" onChange={(e) => setFiltroTipoMov(e.target.value as 'Salida' | 'Entrada' | '')}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
<MenuItem value="Salida">Salida</MenuItem>
|
||||
<MenuItem value="Entrada">Entrada</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{puedeGestionar && (<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()}>Registrar Movimiento</Button>)}
|
||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
||||
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} />
|
||||
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} />
|
||||
<FormControl size="small" sx={{ minWidth: 200, flexGrow: 1 }} disabled={loadingFiltersDropdown}>
|
||||
<InputLabel>Publicación</InputLabel>
|
||||
<Select value={filtroIdPublicacion} label="Publicación" onChange={(e) => setFiltroIdPublicacion(e.target.value as number | string)}>
|
||||
<MenuItem value=""><em>Todas</em></MenuItem>
|
||||
{publicaciones.map(p => <MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 200, flexGrow: 1 }} disabled={loadingFiltersDropdown}>
|
||||
<InputLabel>Distribuidor</InputLabel>
|
||||
<Select value={filtroIdDistribuidor} label="Distribuidor" onChange={(e) => setFiltroIdDistribuidor(e.target.value as number | string)}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
{distribuidores.map(d => <MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 150, flexGrow: 1 }}>
|
||||
<InputLabel>Tipo</InputLabel>
|
||||
<Select value={filtroTipoMov} label="Tipo" onChange={(e) => setFiltroTipoMov(e.target.value as 'Salida' | 'Entrada' | '')}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
<MenuItem value="Salida">Salida</MenuItem>
|
||||
<MenuItem value="Entrada">Entrada</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{puedeGestionar && (<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()}>Registrar Movimiento</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>Fecha</TableCell><TableCell>Publicación (Empresa)</TableCell>
|
||||
<TableCell>Distribuidor</TableCell><TableCell>Tipo</TableCell>
|
||||
<TableCell align="right">Cantidad</TableCell><TableCell>Remito</TableCell>
|
||||
<TableCell align="right">Monto Afectado</TableCell><TableCell>Obs.</TableCell>
|
||||
{puedeGestionar && <TableCell align="right">Acciones</TableCell>}
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{displayData.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={puedeGestionar ? 9 : 8} align="center">No se encontraron movimientos.</TableCell></TableRow>
|
||||
) : (
|
||||
displayData.map((m) => (
|
||||
<TableRow key={m.idParte} hover>
|
||||
<TableCell>{formatDate(m.fecha)}</TableCell>
|
||||
<TableCell>{m.nombrePublicacion} ({m.nombreEmpresaPublicacion})</TableCell>
|
||||
<TableCell>{m.nombreDistribuidor}</TableCell>
|
||||
<TableCell>
|
||||
<Chip label={m.tipoMovimiento} color={m.tipoMovimiento === 'Salida' ? 'primary' : 'secondary'} size="small"/>
|
||||
</TableCell>
|
||||
<TableCell align="right">{m.cantidad}</TableCell>
|
||||
<TableCell>{m.remito}</TableCell>
|
||||
<TableCell align="right" sx={{color: m.montoCalculado < 0 ? 'green' : (m.montoCalculado > 0 ? 'red' : 'inherit')}}>
|
||||
${m.montoCalculado.toFixed(2)}
|
||||
</TableCell>
|
||||
<TableCell>{m.observacion || '-'}</TableCell>
|
||||
{puedeGestionar && (
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, m)} disabled={!puedeGestionar}><MoreVertIcon /></IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
)))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25, 50]} component="div" count={movimientos.length}
|
||||
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
|
||||
/>
|
||||
</TableContainer>
|
||||
)}
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead><TableRow>
|
||||
<TableCell>Fecha</TableCell><TableCell>Publicación (Empresa)</TableCell>
|
||||
<TableCell>Distribuidor</TableCell><TableCell>Tipo</TableCell>
|
||||
<TableCell align="right">Cantidad</TableCell><TableCell>Remito</TableCell>
|
||||
<TableCell align="right">Monto Afectado</TableCell><TableCell>Obs.</TableCell>
|
||||
{puedeGestionar && <TableCell align="right">Acciones</TableCell>}
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{displayData.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={puedeGestionar ? 9 : 8} align="center">No se encontraron movimientos.</TableCell></TableRow>
|
||||
) : (
|
||||
displayData.map((m) => (
|
||||
<TableRow key={m.idParte} hover>
|
||||
<TableCell>{formatDate(m.fecha)}</TableCell>
|
||||
<TableCell>{m.nombrePublicacion} <Chip label={m.nombreEmpresaPublicacion} size="small" variant="outlined" sx={{ ml: 0.5 }} /></TableCell>
|
||||
<TableCell>{m.nombreDistribuidor}</TableCell>
|
||||
<TableCell>
|
||||
<Chip label={m.tipoMovimiento} color={m.tipoMovimiento === 'Salida' ? 'success' : 'error'} size="small" variant="outlined" />
|
||||
</TableCell>
|
||||
<TableCell align="right">{m.cantidad}</TableCell>
|
||||
<TableCell>{m.remito}</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold', color: m.tipoMovimiento === 'Salida' ? 'success.main' : (m.montoCalculado === 0 ? 'inherit' : 'error.main') }}>
|
||||
{m.tipoMovimiento === 'Salida' ? '$'+m.montoCalculado.toFixed(2) : '$-'+m.montoCalculado.toFixed(2) }
|
||||
</TableCell>
|
||||
<TableCell><Tooltip title={m.observacion || ''}><Box sx={{ maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.observacion || '-'}</Box></Tooltip></TableCell>
|
||||
{puedeGestionar && (
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, m)} disabled={!puedeGestionar}><MoreVertIcon /></IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
)))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[10, 25, 50]} component="div" count={movimientos.length}
|
||||
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
|
||||
/>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
{puedeGestionar && selectedRow && (
|
||||
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{mr:1}}/> Modificar</MenuItem>)}
|
||||
{puedeGestionar && selectedRow && ( // O un permiso más específico si "eliminar" es diferente de "modificar"
|
||||
<MenuItem onClick={() => handleDelete(selectedRow.idParte)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar</MenuItem>)}
|
||||
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{ mr: 1 }} /> Modificar</MenuItem>)}
|
||||
{puedeGestionar && selectedRow && (
|
||||
<MenuItem onClick={() => handleDelete(selectedRow.idParte)}><DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar</MenuItem>)}
|
||||
</Menu>
|
||||
|
||||
<EntradaSalidaDistFormModal
|
||||
|
||||
Reference in New Issue
Block a user