All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 5m17s
615 lines
28 KiB
TypeScript
615 lines
28 KiB
TypeScript
import React, { useState, useEffect, useCallback, useMemo } 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,
|
|
ToggleButtonGroup, ToggleButton
|
|
} from '@mui/material';
|
|
import AddIcon from '@mui/icons-material/Add';
|
|
import PrintIcon from '@mui/icons-material/Print';
|
|
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';
|
|
|
|
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 { UpdateEntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/UpdateEntradaSalidaCanillaDto';
|
|
import type { PublicacionDropdownDto } from '../../models/dtos/Distribucion/PublicacionDropdownDto';
|
|
import type { CanillaDropdownDto } from '../../models/dtos/Distribucion/CanillaDropdownDto';
|
|
import type { LiquidarMovimientosCanillaRequestDto } from '../../models/dtos/Distribucion/LiquidarMovimientosCanillaDto';
|
|
|
|
import EntradaSalidaCanillaFormModal from '../../components/Modals/Distribucion/EntradaSalidaCanillaFormModal';
|
|
import { usePermissions } from '../../hooks/usePermissions';
|
|
import axios from 'axios';
|
|
import reportesService from '../../services/Reportes/reportesService';
|
|
|
|
type TipoDestinatarioFiltro = 'canillitas' | 'accionistas';
|
|
|
|
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);
|
|
|
|
const [filtroFecha, setFiltroFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
|
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
|
const [filtroIdCanillitaSeleccionado, setFiltroIdCanillitaSeleccionado] = useState<number | string>('');
|
|
const [filtroTipoDestinatario, setFiltroTipoDestinatario] = useState<TipoDestinatarioFiltro>('canillitas');
|
|
|
|
const [loadingTicketPdf, setLoadingTicketPdf] = useState(false);
|
|
const [publicaciones, setPublicaciones] = useState<PublicacionDropdownDto[]>([]);
|
|
const [destinatariosDropdown, setDestinatariosDropdown] = useState<CanillaDropdownDto[]>([]);
|
|
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false)
|
|
|
|
const [modalOpen, setModalOpen] = useState(false);
|
|
const [editingMovimiento, setEditingMovimiento] = useState<EntradaSalidaCanillaDto | null>(null);
|
|
const [prefillModalData, setPrefillModalData] = useState<{
|
|
fecha?: string;
|
|
idCanilla?: number | string;
|
|
nombreCanilla?: string;
|
|
idPublicacion?: number | string;
|
|
} | null>(null);
|
|
|
|
const [page, setPage] = useState(0);
|
|
const [rowsPerPage, setRowsPerPage] = useState(25);
|
|
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();
|
|
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 formatDate = (dateString?: string | null): string => {
|
|
if (!dateString) return '-';
|
|
const datePart = dateString.split('T')[0];
|
|
const parts = datePart.split('-');
|
|
if (parts.length === 3) { return `${parts[2]}/${parts[1]}/${parts[0]}`; }
|
|
return datePart;
|
|
};
|
|
|
|
useEffect(() => {
|
|
const fetchDropdownData = async () => {
|
|
if (!puedeVer) {
|
|
setError("No tiene permiso para ver esta sección.");
|
|
setLoading(false); // Detiene el spinner principal
|
|
setLoadingFiltersDropdown(false); // Detiene el spinner de los filtros
|
|
return;
|
|
}
|
|
|
|
setLoadingFiltersDropdown(true);
|
|
setError(null);
|
|
try {
|
|
const pubsData = await publicacionService.getPublicacionesForDropdown(true);
|
|
setPublicaciones(pubsData);
|
|
// La carga de destinatarios se hará en el otro useEffect
|
|
} catch (err) {
|
|
console.error("Error cargando publicaciones para filtro:", err);
|
|
setError("Error al cargar publicaciones.");
|
|
} finally {
|
|
// La carga finaliza cuando se cargan los destinatarios también.
|
|
}
|
|
};
|
|
fetchDropdownData();
|
|
}, [puedeVer]); // << CAMBIO: Añadir `puedeVer` como dependencia
|
|
|
|
const fetchDestinatariosParaDropdown = useCallback(async () => {
|
|
if (!puedeVer) { return; }
|
|
|
|
setLoadingFiltersDropdown(true);
|
|
setFiltroIdCanillitaSeleccionado('');
|
|
setDestinatariosDropdown([]);
|
|
setError(null);
|
|
try {
|
|
const esAccionistaFilter = filtroTipoDestinatario === 'accionistas';
|
|
const data = await canillaService.getAllDropdownCanillas(true, esAccionistaFilter);
|
|
setDestinatariosDropdown(data);
|
|
} catch (err) {
|
|
console.error("Error cargando destinatarios para filtro:", err);
|
|
setError("Error al cargar canillitas/accionistas.");
|
|
} finally {
|
|
setLoadingFiltersDropdown(false);
|
|
}
|
|
}, [filtroTipoDestinatario, puedeVer]);
|
|
|
|
useEffect(() => {
|
|
fetchDestinatariosParaDropdown();
|
|
}, [fetchDestinatariosParaDropdown]);
|
|
|
|
const cargarMovimientos = useCallback(async () => {
|
|
if (!puedeVer) {
|
|
setError("No tiene permiso para ver esta sección.");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
if (!filtroFecha || !filtroIdCanillitaSeleccionado) {
|
|
if (loading) setLoading(false);
|
|
setMovimientos([]);
|
|
return;
|
|
}
|
|
setLoading(true); setError(null); setApiErrorMessage(null);
|
|
try {
|
|
const params = {
|
|
fechaDesde: filtroFecha,
|
|
fechaHasta: filtroFecha,
|
|
idPublicacion: filtroIdPublicacion ? Number(filtroIdPublicacion) : null,
|
|
idCanilla: Number(filtroIdCanillitaSeleccionado),
|
|
liquidados: null,
|
|
incluirNoLiquidados: null,
|
|
};
|
|
const data = await entradaSalidaCanillaService.getAllEntradasSalidasCanilla(params);
|
|
setMovimientos(data);
|
|
setSelectedIdsParaLiquidar(new Set());
|
|
} catch (err) {
|
|
console.error("Error al cargar movimientos:", err);
|
|
setError('Error al cargar movimientos.');
|
|
setMovimientos([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [puedeVer, filtroFecha, filtroIdPublicacion, filtroIdCanillitaSeleccionado]);
|
|
|
|
|
|
useEffect(() => {
|
|
if (filtroFecha && filtroIdCanillitaSeleccionado) {
|
|
cargarMovimientos();
|
|
} else {
|
|
setMovimientos([]);
|
|
if (loading) setLoading(false);
|
|
}
|
|
}, [cargarMovimientos, filtroFecha, filtroIdCanillitaSeleccionado]);
|
|
|
|
const handleOpenModal = (item?: EntradaSalidaCanillaDto) => {
|
|
if (!puedeCrear && !item) {
|
|
setApiErrorMessage("No tiene permiso para registrar nuevos movimientos.");
|
|
return;
|
|
}
|
|
if (item && !puedeModificar) {
|
|
setApiErrorMessage("No tiene permiso para modificar movimientos.");
|
|
return;
|
|
}
|
|
|
|
if (item) {
|
|
setEditingMovimiento(item);
|
|
setPrefillModalData(null);
|
|
} else {
|
|
const canillitaSeleccionado = destinatariosDropdown.find(
|
|
c => c.idCanilla === Number(filtroIdCanillitaSeleccionado)
|
|
);
|
|
setEditingMovimiento(null);
|
|
setPrefillModalData({
|
|
fecha: filtroFecha,
|
|
idCanilla: filtroIdCanillitaSeleccionado,
|
|
nombreCanilla: canillitaSeleccionado?.nomApe,
|
|
idPublicacion: filtroIdPublicacion
|
|
});
|
|
}
|
|
setApiErrorMessage(null);
|
|
setModalOpen(true);
|
|
};
|
|
|
|
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) => {
|
|
event.currentTarget.setAttribute('data-rowid', item.idParte.toString());
|
|
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 () => {
|
|
if (selectedIdsParaLiquidar.size === 0) { return; }
|
|
if (!fechaLiquidacionDialog) { return; }
|
|
const fechaLiquidacionDate = new Date(fechaLiquidacionDialog + 'T00:00:00Z');
|
|
let fechaMovimientoMasReciente: Date | null = null;
|
|
selectedIdsParaLiquidar.forEach(idParte => {
|
|
const movimiento = movimientos.find(m => m.idParte === idParte);
|
|
if (movimiento && movimiento.fecha) {
|
|
const movFecha = new Date(movimiento.fecha.split('T')[0] + 'T00:00:00Z');
|
|
if (fechaMovimientoMasReciente === null || movFecha.getTime() > (fechaMovimientoMasReciente as Date).getTime()) {
|
|
fechaMovimientoMasReciente = movFecha;
|
|
}
|
|
}
|
|
});
|
|
if (fechaMovimientoMasReciente !== null && fechaLiquidacionDate.getTime() < (fechaMovimientoMasReciente as Date).getTime()) {
|
|
setApiErrorMessage(`La fecha de liquidación (${fechaLiquidacionDate.toLocaleDateString('es-AR', {timeZone: 'UTC'})}) no puede ser inferior a la fecha del movimiento más reciente a liquidar (${(fechaMovimientoMasReciente as Date).toLocaleDateString('es-AR', {timeZone: 'UTC'})}).`);
|
|
return;
|
|
}
|
|
setApiErrorMessage(null);
|
|
setLoading(true);
|
|
const liquidarDto: LiquidarMovimientosCanillaRequestDto = {
|
|
idsPartesALiquidar: Array.from(selectedIdsParaLiquidar),
|
|
fechaLiquidacion: fechaLiquidacionDialog
|
|
};
|
|
try {
|
|
await entradaSalidaCanillaService.liquidarMovimientos(liquidarDto);
|
|
setOpenLiquidarDialog(false);
|
|
const primerIdParteLiquidado = Array.from(selectedIdsParaLiquidar)[0];
|
|
const movimientoParaTicket = movimientos.find(m => m.idParte === primerIdParteLiquidado);
|
|
|
|
await cargarMovimientos();
|
|
|
|
if (movimientoParaTicket && !movimientoParaTicket.canillaEsAccionista) {
|
|
console.log("Liquidación exitosa, generando ticket para canillita NO accionista:", movimientoParaTicket.idCanilla);
|
|
await handleImprimirTicketLiquidacion(
|
|
movimientoParaTicket.idCanilla,
|
|
movimientoParaTicket.fecha,
|
|
false
|
|
);
|
|
} else if (movimientoParaTicket && movimientoParaTicket.canillaEsAccionista) {
|
|
console.log("Liquidación exitosa para accionista. No se genera ticket automáticamente.");
|
|
} else {
|
|
console.warn("No se pudo encontrar información del movimiento para ticket post-liquidación.");
|
|
}
|
|
} 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 handleModalEditSubmit = async (data: UpdateEntradaSalidaCanillaDto, idParte: number) => {
|
|
setApiErrorMessage(null);
|
|
try {
|
|
await entradaSalidaCanillaService.updateEntradaSalidaCanilla(idParte, data);
|
|
} catch (err: any) {
|
|
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar los cambios.';
|
|
setApiErrorMessage(message);
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
const handleCloseModal = () => {
|
|
setModalOpen(false);
|
|
setEditingMovimiento(null);
|
|
setPrefillModalData(null);
|
|
if (!apiErrorMessage) {
|
|
cargarMovimientos();
|
|
}
|
|
};
|
|
|
|
const handleImprimirTicketLiquidacion = useCallback(async (
|
|
idCanilla: number, fecha: string, esAccionista: boolean
|
|
) => {
|
|
setLoadingTicketPdf(true);
|
|
setApiErrorMessage(null);
|
|
try {
|
|
const params = { fecha: fecha.split('T')[0], idCanilla, esAccionista };
|
|
const blob = await reportesService.getTicketLiquidacionCanillaPdf(params);
|
|
if (blob.type === "application/json") {
|
|
const text = await blob.text();
|
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar el ticket PDF.";
|
|
setApiErrorMessage(msg);
|
|
} else {
|
|
const url = URL.createObjectURL(blob);
|
|
const w = window.open(url, '_blank');
|
|
if (!w) alert("Permita popups para ver el PDF del ticket.");
|
|
}
|
|
} catch (error: any) {
|
|
console.error("Error al generar ticket:", error);
|
|
const message = axios.isAxiosError(error) && error.response?.data?.message ? error.response.data.message : 'Error al generar ticket.';
|
|
setApiErrorMessage(message);
|
|
} finally { setLoadingTicketPdf(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 totalARendirVisible = useMemo(() =>
|
|
displayData.filter(m => !m.liquidado).reduce((sum, item) => sum + item.montoARendir, 0)
|
|
, [displayData]);
|
|
|
|
if (!puedeVer) {
|
|
return (
|
|
<Box sx={{ p: 2 }}>
|
|
<Alert severity="error">
|
|
{error || "No tiene permiso para acceder a esta sección."}
|
|
</Alert>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
const numSelectedToLiquidate = selectedIdsParaLiquidar.size;
|
|
const numNotLiquidatedOnPage = displayData.filter(m => !m.liquidado).length;
|
|
|
|
return (
|
|
<Box sx={{ p: 1 }}>
|
|
<Typography variant="h5" gutterBottom>Entradas/Salidas Canillitas & Accionistas</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" type="date" size="small" value={filtroFecha}
|
|
onChange={(e) => setFiltroFecha(e.target.value)}
|
|
InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }}
|
|
required
|
|
error={!filtroFecha}
|
|
helperText={!filtroFecha ? "Fecha es obligatoria" : ""}
|
|
/>
|
|
<ToggleButtonGroup
|
|
color="primary"
|
|
value={filtroTipoDestinatario}
|
|
exclusive
|
|
onChange={(_, newValue: TipoDestinatarioFiltro | null) => {
|
|
if (newValue !== null) {
|
|
setFiltroTipoDestinatario(newValue);
|
|
}
|
|
}}
|
|
aria-label="Tipo de Destinatario"
|
|
size="small"
|
|
>
|
|
<ToggleButton value="canillitas">Canillitas</ToggleButton>
|
|
<ToggleButton value="accionistas">Accionistas</ToggleButton>
|
|
</ToggleButtonGroup>
|
|
|
|
<FormControl size="small" sx={{ minWidth: 220, flexGrow: 1 }} disabled={loadingFiltersDropdown} required error={!filtroIdCanillitaSeleccionado}>
|
|
<InputLabel>{filtroTipoDestinatario === 'canillitas' ? 'Canillita' : 'Accionista'}</InputLabel>
|
|
<Select
|
|
value={filtroIdCanillitaSeleccionado}
|
|
label={filtroTipoDestinatario === 'canillitas' ? 'Canillita' : 'Accionista'}
|
|
onChange={(e) => setFiltroIdCanillitaSeleccionado(e.target.value as number | string)}
|
|
>
|
|
<MenuItem value=""><em>Seleccione uno</em></MenuItem>
|
|
{destinatariosDropdown.map(c => <MenuItem key={c.idCanilla} value={c.idCanilla}>{c.nomApe} {c.legajo ? `(Leg: ${c.legajo})`: ''}</MenuItem>)}
|
|
</Select>
|
|
{!filtroIdCanillitaSeleccionado && <Typography component="p" color="error" variant="caption" sx={{ml:1.5, fontSize:'0.65rem'}}>Selección obligatoria</Typography>}
|
|
</FormControl>
|
|
|
|
<FormControl size="small" sx={{ minWidth: 180, flexGrow: 1 }} disabled={loadingFiltersDropdown}>
|
|
<InputLabel>Publicación (Opcional)</InputLabel>
|
|
<Select value={filtroIdPublicacion} label="Publicación (Opcional)" 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>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap:2 }}>
|
|
{puedeCrear && (
|
|
<Button
|
|
variant="contained"
|
|
startIcon={<AddIcon />}
|
|
onClick={() => handleOpenModal()}
|
|
disabled={!filtroFecha || !filtroIdCanillitaSeleccionado}
|
|
>
|
|
Registrar Movimiento
|
|
</Button>
|
|
)}
|
|
{puedeLiquidar && numSelectedToLiquidate > 0 && movimientos.some(m => selectedIdsParaLiquidar.has(m.idParte) && !m.liquidado) && (
|
|
<Button variant="contained" color="success" startIcon={<PlaylistAddCheckIcon />} onClick={handleOpenLiquidarDialog}>
|
|
Liquidar Seleccionados ({numSelectedToLiquidate})
|
|
</Button>
|
|
)}
|
|
</Box>
|
|
</Paper>
|
|
|
|
{!filtroFecha && <Alert severity="info" sx={{my:1}}>Por favor, seleccione una fecha.</Alert>}
|
|
{filtroFecha && !filtroIdCanillitaSeleccionado && <Alert severity="info" sx={{my:1}}>Por favor, seleccione un {filtroTipoDestinatario === 'canillitas' ? 'canillita' : 'accionista'}.</Alert>}
|
|
|
|
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
|
{error && !loading && !apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
|
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
|
{loadingTicketPdf && ( <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', my: 2 }}> <CircularProgress size={20} sx={{ mr: 1 }} /> <Typography variant="body2">Cargando ticket...</Typography> </Box> )}
|
|
|
|
|
|
{!loading && movimientos.length > 0 && (
|
|
<Paper sx={{ p: 1.5, mb: 2, mt:1, backgroundColor: 'grey.100' }}>
|
|
<Box sx={{display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
|
|
<Typography variant="subtitle1" sx={{mr:2}}>
|
|
Total a Liquidar:
|
|
</Typography>
|
|
<Typography variant="h6" sx={{fontWeight: 'bold', color: 'error.main'}}>
|
|
{totalARendirVisible.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}
|
|
</Typography>
|
|
</Box>
|
|
</Paper>
|
|
)}
|
|
|
|
{!loading && !error && puedeVer && filtroFecha && filtroIdCanillitaSeleccionado && (
|
|
<TableContainer component={Paper}>
|
|
<Table size="small">
|
|
<TableHead>
|
|
<TableRow>
|
|
{puedeLiquidar && (
|
|
<TableCell padding="checkbox">
|
|
<Checkbox
|
|
indeterminate={numSelectedToLiquidate > 0 && numSelectedToLiquidate < numNotLiquidatedOnPage && numNotLiquidatedOnPage > 0}
|
|
checked={numNotLiquidatedOnPage > 0 && numSelectedToLiquidate === numNotLiquidatedOnPage}
|
|
onChange={handleSelectAllForLiquidar}
|
|
disabled={numNotLiquidatedOnPage === 0}
|
|
/>
|
|
</TableCell>
|
|
)}
|
|
<TableCell>Fecha</TableCell>
|
|
<TableCell>Publicación</TableCell>
|
|
<TableCell>{filtroTipoDestinatario === 'canillitas' ? 'Canillita' : 'Accionista'}</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 || puedeLiquidar) && <TableCell align="right">Acciones</TableCell>}
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{displayData.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell
|
|
colSpan={
|
|
(puedeLiquidar ? 1 : 0) + 9 + ((puedeModificar || puedeEliminar || puedeLiquidar) ? 1 : 0)
|
|
}
|
|
align="center"
|
|
>
|
|
No se encontraron movimientos con los filtros aplicados.
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
displayData.map((m) => (
|
|
<TableRow key={m.idParte} hover selected={selectedIdsParaLiquidar.has(m.idParte)}>
|
|
{puedeLiquidar && (
|
|
<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.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}
|
|
</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 || puedeLiquidar) && (
|
|
<TableCell align="right">
|
|
<IconButton
|
|
onClick={(e) => handleMenuOpen(e, m)}
|
|
data-rowid={m.idParte.toString()}
|
|
disabled={m.liquidado && !puedeEliminarLiquidados && !puedeLiquidar}
|
|
>
|
|
<MoreVertIcon />
|
|
</IconButton>
|
|
</TableCell>
|
|
)}
|
|
</TableRow>
|
|
)))}
|
|
</TableBody>
|
|
</Table>
|
|
<TablePagination
|
|
rowsPerPageOptions={[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 && puedeLiquidar && (
|
|
<MenuItem
|
|
onClick={() => {
|
|
if (selectedRow) {
|
|
// Usar siempre selectedRow.fecha, que es la fecha original del movimiento
|
|
handleImprimirTicketLiquidacion(
|
|
selectedRow.idCanilla,
|
|
selectedRow.fecha, // Usar siempre la fecha del movimiento
|
|
selectedRow.canillaEsAccionista
|
|
);
|
|
}
|
|
handleMenuClose();
|
|
}}
|
|
disabled={loadingTicketPdf}
|
|
>
|
|
<PrintIcon fontSize="small" sx={{ mr: 1 }} />
|
|
{loadingTicketPdf && <CircularProgress size={16} sx={{ mr: 1 }} />}
|
|
Reimprimir Ticket Liq.
|
|
</MenuItem>
|
|
)}
|
|
{selectedRow && (
|
|
((!selectedRow.liquidado && puedeEliminar) || (selectedRow.liquidado && puedeEliminarLiquidados))
|
|
) && (
|
|
<MenuItem onClick={() => {if (selectedRow) handleDelete(selectedRow.idParte);}}>
|
|
<DeleteIcon fontSize="small" sx={{ mr: 1 }} />
|
|
Eliminar
|
|
</MenuItem>
|
|
)}
|
|
</Menu>
|
|
|
|
<EntradaSalidaCanillaFormModal
|
|
open={modalOpen}
|
|
onClose={handleCloseModal}
|
|
onSubmit={handleModalEditSubmit}
|
|
initialData={editingMovimiento}
|
|
prefillData={prefillModalData}
|
|
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; |