// 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([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [apiErrorMessage, setApiErrorMessage] = useState(null); // Filtros const [filtroFechaDesde, setFiltroFechaDesde] = useState(''); const [filtroFechaHasta, setFiltroFechaHasta] = useState(''); const [filtroIdPublicacion, setFiltroIdPublicacion] = useState(''); const [filtroIdCanilla, setFiltroIdCanilla] = useState(''); const [filtroEstadoLiquidacion, setFiltroEstadoLiquidacion] = useState<'todos' | 'liquidados' | 'noLiquidados'>('noLiquidados'); const [publicaciones, setPublicaciones] = useState([]); const [canillitas, setCanillitas] = useState([]); const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [editingMovimiento, setEditingMovimiento] = useState(null); const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const [anchorEl, setAnchorEl] = useState(null); const [selectedRow, setSelectedRow] = useState(null); const [selectedIdsParaLiquidar, setSelectedIdsParaLiquidar] = useState>(new Set()); const [fechaLiquidacionDialog, setFechaLiquidacionDialog] = useState(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, 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) => { 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) => { 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 {error || "Acceso denegado."}; const numSelectedToLiquidate = selectedIdsParaLiquidar.size; const numNotLiquidatedOnPage = displayData.filter(m => !m.liquidado).length; return ( Entradas/Salidas Canillitas Filtros setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} /> setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{ minWidth: 170 }} /> Publicación Canillita Estado Liquidación {puedeCrear && ()} {puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' && numSelectedToLiquidate > 0 && ( )} {loading && } {error && !loading && {error}} {apiErrorMessage && {apiErrorMessage}} {!loading && !error && puedeVer && ( {puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' && 0 && numSelectedToLiquidate < numNotLiquidatedOnPage} checked={numNotLiquidatedOnPage > 0 && numSelectedToLiquidate === numNotLiquidatedOnPage} onChange={handleSelectAllForLiquidar} disabled={numNotLiquidatedOnPage === 0} /> } FechaPublicaciónCanillita SalidaEntrada VendidosA Rendir LiquidadoF. Liq.Obs. {(puedeModificar || puedeEliminar) && Acciones} {displayData.length === 0 ? ( No se encontraron movimientos. ) : ( displayData.map((m) => ( {puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' && handleSelectRowForLiquidar(m.idParte)} disabled={m.liquidado} /> } {formatDate(m.fecha)} {m.nombrePublicacion} {m.nomApeCanilla} {m.cantSalida} {m.cantEntrada} {m.vendidos} ${m.montoARendir.toFixed(2)} {m.liquidado ? : } {m.fechaLiquidado ? formatDate(m.fechaLiquidado) : '-'} {m.observacion || '-'} {(puedeModificar || puedeEliminar) && ( 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)) } > )} )))}
)} {puedeModificar && selectedRow && !selectedRow.liquidado && ( { handleOpenModal(selectedRow); handleMenuClose(); }}> Modificar)} {selectedRow && ( (!selectedRow.liquidado && puedeEliminar) || (selectedRow.liquidado && puedeEliminarLiquidados) ) && ( handleDelete(selectedRow.idParte)}> Eliminar )} setApiErrorMessage(null)} /> Confirmar Liquidación Se marcarán como liquidados {selectedIdsParaLiquidar.size} movimiento(s). setFechaLiquidacionDialog(e.target.value)} InputLabelProps={{ shrink: true }} />
); }; export default GestionarEntradasSalidasCanillaPage;