2025-05-23 15:47:39 -03:00
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' ;
2025-06-03 13:45:20 -03:00
import PrintIcon from '@mui/icons-material/Print' ;
2025-05-23 15:47:39 -03:00
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' ;
2025-06-03 13:45:20 -03:00
import PlaylistAddCheckIcon from '@mui/icons-material/PlaylistAddCheck' ;
2025-05-23 15:47:39 -03:00
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 { 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' ;
2025-06-03 13:45:20 -03:00
import reportesService from '../../services/Reportes/reportesService' ;
2025-05-23 15:47:39 -03:00
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 ) ;
2025-06-03 13:45:20 -03:00
const [ filtroFechaDesde , setFiltroFechaDesde ] = useState < string > ( new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ) ;
const [ filtroFechaHasta , setFiltroFechaHasta ] = useState < string > ( new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ) ;
2025-05-23 15:47:39 -03:00
const [ filtroIdPublicacion , setFiltroIdPublicacion ] = useState < number | string > ( '' ) ;
const [ filtroIdCanilla , setFiltroIdCanilla ] = useState < number | string > ( '' ) ;
const [ filtroEstadoLiquidacion , setFiltroEstadoLiquidacion ] = useState < 'todos' | 'liquidados' | 'noLiquidados' > ( 'noLiquidados' ) ;
2025-06-03 13:45:20 -03:00
const [ loadingTicketPdf , setLoadingTicketPdf ] = useState ( false ) ;
2025-05-23 15:47:39 -03:00
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 ( ) ;
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" ) ;
2025-06-03 13:45:20 -03:00
// Función para formatear fechas YYYY-MM-DD a DD/MM/YYYY
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 ;
} ;
2025-05-23 15:47:39 -03:00
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 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 ) = > {
2025-06-03 13:45:20 -03:00
// Almacenar el idParte en el propio elemento del menú para referencia
event . currentTarget . setAttribute ( 'data-rowid' , item . idParte . toString ( ) ) ;
setAnchorEl ( event . currentTarget ) ;
setSelectedRow ( item ) ;
2025-05-23 15:47:39 -03:00
} ;
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 ( ) = > {
2025-06-03 13:45:20 -03:00
if ( selectedIdsParaLiquidar . size === 0 ) {
setApiErrorMessage ( "No hay movimientos seleccionados para liquidar." ) ;
return ;
}
if ( ! fechaLiquidacionDialog ) {
setApiErrorMessage ( "Debe seleccionar una fecha de liquidación." ) ;
return ;
}
// --- VALIDACIÓN DE FECHA ---
const fechaLiquidacionDate = new Date ( fechaLiquidacionDialog + 'T00:00:00Z' ) ; // Usar Z para consistencia con formatDate si es necesario, o T00:00:00 para local
let fechaMovimientoMasReciente : Date | null = null ;
selectedIdsParaLiquidar . forEach ( idParte = > {
const movimiento = movimientos . find ( m = > m . idParte === idParte ) ;
if ( movimiento && movimiento . fecha ) { // Asegurarse que movimiento.fecha existe
const movFecha = new Date ( movimiento . fecha . split ( 'T' ) [ 0 ] + 'T00:00:00Z' ) ; // Consistencia con Z
if ( fechaMovimientoMasReciente === null || movFecha . getTime ( ) > ( fechaMovimientoMasReciente as Date ) . getTime ( ) ) { // Comparar usando getTime()
fechaMovimientoMasReciente = movFecha ;
}
}
} ) ;
if ( fechaMovimientoMasReciente !== null && fechaLiquidacionDate . getTime ( ) < ( fechaMovimientoMasReciente as Date ) . getTime ( ) ) { // Comparar usando 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 ) ; // Usar el loading general para la operación de liquidar
2025-05-23 15:47:39 -03:00
const liquidarDto : LiquidarMovimientosCanillaRequestDto = {
idsPartesALiquidar : Array.from ( selectedIdsParaLiquidar ) ,
2025-06-03 13:45:20 -03:00
fechaLiquidacion : fechaLiquidacionDialog // El backend espera YYYY-MM-DD
2025-05-23 15:47:39 -03:00
} ;
2025-06-03 13:45:20 -03:00
2025-05-23 15:47:39 -03:00
try {
await entradaSalidaCanillaService . liquidarMovimientos ( liquidarDto ) ;
2025-06-03 13:45:20 -03:00
setOpenLiquidarDialog ( false ) ;
const primerIdParteLiquidado = Array . from ( selectedIdsParaLiquidar ) [ 0 ] ;
const movimientoParaTicket = movimientos . find ( m = > m . idParte === primerIdParteLiquidado ) ;
await cargarMovimientos ( ) ;
if ( movimientoParaTicket ) {
console . log ( "Liquidación exitosa, intentando generar ticket para canillita:" , movimientoParaTicket . idCanilla ) ;
await handleImprimirTicketLiquidacion (
movimientoParaTicket . idCanilla ,
fechaLiquidacionDialog ,
movimientoParaTicket . canillaEsAccionista
) ;
} else {
console . warn ( "No se pudo encontrar información del movimiento para generar el ticket post-liquidación." ) ;
}
2025-05-23 15:47:39 -03:00
} catch ( err : any ) {
const msg = axios . isAxiosError ( err ) && err . response ? . data ? . message ? err . response . data . message : 'Error al liquidar.' ;
2025-06-03 13:45:20 -03:00
setApiErrorMessage ( msg ) ;
2025-05-23 15:47:39 -03:00
} finally {
setLoading ( false ) ;
}
} ;
2025-06-03 13:45:20 -03:00
// Esta función se pasa al modal para que la invoque al hacer submit en MODO EDICIÓN
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 ) ;
// Recargar siempre que se cierre el modal y no haya un error pendiente a nivel de página
// Opcionalmente, podrías tener una bandera ' cambiosGuardados' que el modal active
// para ser más selectivo con la recarga.
if ( ! apiErrorMessage ) {
cargarMovimientos ( ) ;
}
} ;
const handleImprimirTicketLiquidacion = useCallback ( async (
// Parámetros necesarios para el ticket
idCanilla : number ,
fecha : string , // Fecha para la que se genera el ticket (probablemente fechaLiquidacionDialog)
esAccionista : boolean
) = > {
setLoadingTicketPdf ( true ) ;
setApiErrorMessage ( null ) ;
try {
const params = {
fecha : fecha.split ( 'T' ) [ 0 ] , // Asegurar formato YYYY-MM-DD
idCanilla : idCanilla ,
esAccionista : 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 de liquidación:" , error ) ;
const message = axios . isAxiosError ( error ) && error . response ? . data ? . message
? error . response . data . message
: 'Ocurrió un error al generar el ticket.' ;
setApiErrorMessage ( message ) ;
} finally {
setLoadingTicketPdf ( false ) ;
// No cerramos el menú aquí si se llama desde handleConfirmLiquidar
}
} , [ ] ) ; // Dependencias vacías si no usa nada del scope exterior que cambie, o añadir si es necesario
2025-05-23 15:47:39 -03:00
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 ) ;
if ( ! loading && ! puedeVer && ! loadingFiltersDropdown ) return < Box sx = { { p : 2 } } > < Alert severity = "error" > { error || "Acceso denegado." } < / Alert > < / Box > ;
const numSelectedToLiquidate = selectedIdsParaLiquidar . size ;
2025-06-03 13:45:20 -03:00
// Corregido: numNotLiquidatedOnPage debe calcularse sobre 'movimientos' filtrados, no solo 'displayData'
// O, si la selección es solo por página, displayData está bien. Asumamos selección por página por ahora.
2025-05-23 15:47:39 -03:00
const numNotLiquidatedOnPage = displayData . filter ( m = > ! m . liquidado ) . length ;
return (
2025-06-03 13:45:20 -03:00
< Box sx = { { p : 1 } } >
< Typography variant = "h5" gutterBottom > Entradas / Salidas Canillitas < / Typography >
2025-05-23 15:47:39 -03:00
< 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 > }
2025-06-03 13:45:20 -03:00
{ error && ! loading && ! apiErrorMessage && < Alert severity = "error" sx = { { my : 2 } } > { error } < / Alert > }
2025-05-23 15:47:39 -03:00
{ apiErrorMessage && < Alert severity = "error" sx = { { my : 2 } } > { apiErrorMessage } < / Alert > }
2025-06-03 13:45:20 -03:00
{ loadingTicketPdf &&
< Box sx = { { display : 'flex' , justifyContent : 'center' , alignItems : 'center' , my : 2 } } >
< CircularProgress size = { 20 } sx = { { mr : 1 } } / >
< Typography variant = "body2" > Cargando ticket . . . < / Typography >
< / Box >
}
2025-05-23 15:47:39 -03:00
{ ! loading && ! error && puedeVer && (
< TableContainer component = { Paper } >
< Table size = "small" >
2025-06-03 13:45:20 -03:00
< TableHead >
< TableRow >
{ puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' && (
< 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 > 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 || puedeLiquidar ) && < TableCell align = "right" > Acciones < / TableCell > }
< / TableRow >
< / TableHead >
2025-05-23 15:47:39 -03:00
< TableBody >
{ displayData . length === 0 ? (
2025-06-03 13:45:20 -03:00
< TableRow >
< TableCell
colSpan = {
( puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' ? 1 : 0 ) +
9 +
( ( puedeModificar || puedeEliminar || puedeLiquidar ) ? 1 : 0 )
}
align = "center"
>
No se encontraron movimientos .
< / TableCell >
< / TableRow >
2025-05-23 15:47:39 -03:00
) : (
displayData . map ( ( m ) = > (
< TableRow key = { m . idParte } hover selected = { selectedIdsParaLiquidar . has ( m . idParte ) } >
2025-06-03 13:45:20 -03:00
{ puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' && (
2025-05-23 15:47:39 -03:00
< TableCell padding = "checkbox" >
< Checkbox
checked = { selectedIdsParaLiquidar . has ( m . idParte ) }
onChange = { ( ) = > handleSelectRowForLiquidar ( m . idParte ) }
disabled = { m . liquidado }
/ >
< / TableCell >
2025-06-03 13:45:20 -03:00
) }
2025-05-23 15:47:39 -03:00
< 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 >
2025-06-03 13:45:20 -03:00
< TableCell align = "right" sx = { { fontWeight : 'bold' } } >
{ m . montoARendir . toLocaleString ( 'es-AR' , { style : 'currency' , currency : 'ARS' } ) }
< / TableCell >
2025-05-23 15:47:39 -03:00
< 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 >
2025-06-03 13:45:20 -03:00
< TableCell >
< Tooltip title = { m . observacion || '' } >
< Box sx = { { maxWidth : 100 , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' } } >
{ m . observacion || '-' }
< / Box >
< / Tooltip >
< / TableCell >
{ ( puedeModificar || puedeEliminar || puedeLiquidar ) && (
2025-05-23 15:47:39 -03:00
< TableCell align = "right" >
2025-06-03 13:45:20 -03:00
< IconButton
onClick = { ( e ) = > handleMenuOpen ( e , m ) }
data - rowid = { m . idParte . toString ( ) } // Guardar el id de la fila aquí
disabled = { m . liquidado && ! puedeEliminarLiquidados && ! puedeLiquidar } // Lógica simplificada, refinar si es necesario
>
< MoreVertIcon / >
< / IconButton >
2025-05-23 15:47:39 -03:00
< / 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 > ) }
2025-06-03 13:45:20 -03:00
{ /* Opción de Imprimir Ticket Liq. */ }
{ selectedRow && selectedRow . liquidado && ( // Solo mostrar si ya está liquidado (para reimprimir)
< MenuItem
onClick = { ( ) = > {
if ( selectedRow ) { // selectedRow no será null aquí debido a la condición anterior
handleImprimirTicketLiquidacion (
selectedRow . idCanilla ,
selectedRow . fechaLiquidado || selectedRow . fecha , // Usar fechaLiquidado si existe, sino la fecha del movimiento
selectedRow . canillaEsAccionista
) ;
}
// handleMenuClose() es llamado por handleImprimirTicketLiquidacion
} }
disabled = { loadingTicketPdf }
>
< PrintIcon fontSize = "small" sx = { { mr : 1 } } / >
{ loadingTicketPdf && < CircularProgress size = { 16 } sx = { { mr : 1 } } / > }
Reimprimir Ticket Liq .
< / MenuItem >
) }
{ selectedRow && ( // Opción de Eliminar
( ( ! selectedRow . liquidado && puedeEliminar ) || ( selectedRow . liquidado && puedeEliminarLiquidados ) )
2025-05-23 15:47:39 -03:00
) && (
2025-06-03 13:45:20 -03:00
< MenuItem onClick = { ( ) = > {
if ( selectedRow ) handleDelete ( selectedRow . idParte ) ;
} } >
2025-05-23 15:47:39 -03:00
< DeleteIcon fontSize = "small" sx = { { mr : 1 } } / > Eliminar
< / MenuItem >
) }
< / Menu >
< EntradaSalidaCanillaFormModal
2025-06-03 13:45:20 -03:00
open = { modalOpen }
onClose = { handleCloseModal }
onSubmit = { handleModalEditSubmit }
initialData = { editingMovimiento }
errorMessage = { apiErrorMessage }
2025-05-23 15:47:39 -03:00
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 ;