2025-05-23 15:47:39 -03:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
|
|
|
|
FormControl, InputLabel, Select, MenuItem, RadioGroup, FormControlLabel, Radio, InputAdornment
|
|
|
|
|
} from '@mui/material';
|
|
|
|
|
import type { PagoDistribuidorDto } from '../../../models/dtos/Contables/PagoDistribuidorDto';
|
|
|
|
|
import type { CreatePagoDistribuidorDto } from '../../../models/dtos/Contables/CreatePagoDistribuidorDto';
|
|
|
|
|
import type { UpdatePagoDistribuidorDto } from '../../../models/dtos/Contables/UpdatePagoDistribuidorDto';
|
|
|
|
|
import type { DistribuidorDto } from '../../../models/dtos/Distribucion/DistribuidorDto';
|
|
|
|
|
import type { TipoPago } from '../../../models/Entities/TipoPago';
|
|
|
|
|
import type { EmpresaDto } from '../../../models/dtos/Distribucion/EmpresaDto';
|
|
|
|
|
import distribuidorService from '../../../services/Distribucion/distribuidorService';
|
|
|
|
|
import tipoPagoService from '../../../services/Contables/tipoPagoService';
|
|
|
|
|
import empresaService from '../../../services/Distribucion/empresaService';
|
|
|
|
|
|
|
|
|
|
const modalStyle = { /* ... (mismo estilo) ... */
|
|
|
|
|
position: 'absolute' as 'absolute',
|
|
|
|
|
top: '50%',
|
|
|
|
|
left: '50%',
|
|
|
|
|
transform: 'translate(-50%, -50%)',
|
|
|
|
|
width: { xs: '90%', sm: 600 },
|
|
|
|
|
bgcolor: 'background.paper',
|
|
|
|
|
border: '2px solid #000',
|
|
|
|
|
boxShadow: 24,
|
|
|
|
|
p: 4,
|
|
|
|
|
maxHeight: '90vh',
|
|
|
|
|
overflowY: 'auto'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
interface PagoDistribuidorFormModalProps {
|
|
|
|
|
open: boolean;
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
onSubmit: (data: CreatePagoDistribuidorDto | UpdatePagoDistribuidorDto, idPago?: number) => Promise<void>;
|
|
|
|
|
initialData?: PagoDistribuidorDto | null;
|
|
|
|
|
errorMessage?: string | null;
|
|
|
|
|
clearErrorMessage: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
|
|
|
|
|
open,
|
|
|
|
|
onClose,
|
|
|
|
|
onSubmit,
|
|
|
|
|
initialData,
|
|
|
|
|
errorMessage,
|
|
|
|
|
clearErrorMessage
|
|
|
|
|
}) => {
|
|
|
|
|
const [idDistribuidor, setIdDistribuidor] = useState<number | string>('');
|
|
|
|
|
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
|
|
|
|
const [tipoMovimiento, setTipoMovimiento] = useState<'Recibido' | 'Realizado'>('Recibido');
|
|
|
|
|
const [recibo, setRecibo] = useState<string>('');
|
|
|
|
|
const [monto, setMonto] = useState<string>('');
|
|
|
|
|
const [idTipoPago, setIdTipoPago] = useState<number | string>('');
|
|
|
|
|
const [detalle, setDetalle] = useState('');
|
|
|
|
|
const [idEmpresa, setIdEmpresa] = useState<number | string>('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
|
|
|
|
|
const [tiposPago, setTiposPago] = useState<TipoPago[]>([]);
|
|
|
|
|
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
|
|
|
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
|
|
|
|
|
|
|
|
|
const isEditing = Boolean(initialData);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-11-05 13:52:14 -03:00
|
|
|
// Esta función se encarga de cargar los datos de los dropdowns.
|
2025-05-23 15:47:39 -03:00
|
|
|
const fetchDropdownData = async () => {
|
|
|
|
|
setLoadingDropdowns(true);
|
|
|
|
|
try {
|
|
|
|
|
const [distData, tiposPagoData, empresasData] = await Promise.all([
|
|
|
|
|
distribuidorService.getAllDistribuidores(),
|
|
|
|
|
tipoPagoService.getAllTiposPago(),
|
|
|
|
|
empresaService.getAllEmpresas()
|
|
|
|
|
]);
|
|
|
|
|
setDistribuidores(distData);
|
|
|
|
|
setTiposPago(tiposPagoData);
|
|
|
|
|
setEmpresas(empresasData);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error al cargar datos para dropdowns", error);
|
|
|
|
|
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar datos necesarios.'}));
|
|
|
|
|
} finally {
|
|
|
|
|
setLoadingDropdowns(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (open) {
|
|
|
|
|
fetchDropdownData();
|
|
|
|
|
setIdDistribuidor(initialData?.idDistribuidor || '');
|
|
|
|
|
setFecha(initialData?.fecha || new Date().toISOString().split('T')[0]);
|
|
|
|
|
setTipoMovimiento(initialData?.tipoMovimiento || 'Recibido');
|
|
|
|
|
setRecibo(initialData?.recibo?.toString() || '');
|
|
|
|
|
setMonto(initialData?.monto?.toString() || '');
|
|
|
|
|
setIdTipoPago(initialData?.idTipoPago || '');
|
|
|
|
|
setDetalle(initialData?.detalle || '');
|
|
|
|
|
setIdEmpresa(initialData?.idEmpresa || '');
|
|
|
|
|
setLocalErrors({});
|
|
|
|
|
}
|
2026-05-07 12:03:26 -03:00
|
|
|
}, [open, initialData]);
|
2025-05-23 15:47:39 -03:00
|
|
|
|
|
|
|
|
const validate = (): boolean => {
|
|
|
|
|
const errors: { [key: string]: string | null } = {};
|
|
|
|
|
if (!idDistribuidor) errors.idDistribuidor = 'Seleccione un distribuidor.';
|
|
|
|
|
if (!fecha.trim()) errors.fecha = 'La fecha es obligatoria.';
|
|
|
|
|
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fecha)) errors.fecha = 'Formato de fecha inválido.';
|
|
|
|
|
if (!tipoMovimiento) errors.tipoMovimiento = 'Seleccione un tipo de movimiento.';
|
|
|
|
|
if (!recibo.trim() || isNaN(parseInt(recibo)) || parseInt(recibo) <= 0) errors.recibo = 'Nro. Recibo es obligatorio y numérico.';
|
|
|
|
|
if (!monto.trim() || isNaN(parseFloat(monto)) || parseFloat(monto) <= 0) errors.monto = 'Monto debe ser un número positivo.';
|
|
|
|
|
if (!idTipoPago) errors.idTipoPago = 'Seleccione un tipo de pago.';
|
|
|
|
|
if (!idEmpresa) errors.idEmpresa = 'Seleccione una empresa (para el saldo).';
|
|
|
|
|
|
|
|
|
|
setLocalErrors(errors);
|
|
|
|
|
return Object.keys(errors).length === 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleInputChange = (fieldName: string) => {
|
|
|
|
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
|
|
|
|
if (errorMessage) clearErrorMessage();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
clearErrorMessage();
|
|
|
|
|
if (!validate()) return;
|
|
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const montoNum = parseFloat(monto);
|
2025-11-05 13:52:14 -03:00
|
|
|
|
2025-05-23 15:47:39 -03:00
|
|
|
if (isEditing && initialData) {
|
|
|
|
|
const dataToSubmit: UpdatePagoDistribuidorDto = {
|
|
|
|
|
monto: montoNum,
|
|
|
|
|
idTipoPago: Number(idTipoPago),
|
|
|
|
|
detalle: detalle || undefined,
|
|
|
|
|
};
|
2025-11-05 13:52:14 -03:00
|
|
|
// << INICIO DE LA CORRECCIÓN >>
|
2025-05-23 15:47:39 -03:00
|
|
|
await onSubmit(dataToSubmit, initialData.idPago);
|
2025-11-05 13:52:14 -03:00
|
|
|
// << FIN DE LA CORRECCIÓN >>
|
2025-05-23 15:47:39 -03:00
|
|
|
} else {
|
|
|
|
|
const dataToSubmit: CreatePagoDistribuidorDto = {
|
|
|
|
|
idDistribuidor: Number(idDistribuidor),
|
|
|
|
|
fecha,
|
|
|
|
|
tipoMovimiento,
|
|
|
|
|
recibo: parseInt(recibo, 10),
|
|
|
|
|
monto: montoNum,
|
|
|
|
|
idTipoPago: Number(idTipoPago),
|
|
|
|
|
detalle: detalle || undefined,
|
|
|
|
|
idEmpresa: Number(idEmpresa),
|
|
|
|
|
};
|
|
|
|
|
await onSubmit(dataToSubmit);
|
|
|
|
|
}
|
2025-11-05 13:52:14 -03:00
|
|
|
|
2025-05-23 15:47:39 -03:00
|
|
|
onClose();
|
2025-11-05 13:52:14 -03:00
|
|
|
|
2025-05-23 15:47:39 -03:00
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error("Error en submit de PagoDistribuidorFormModal:", error);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Modal open={open} onClose={onClose}>
|
|
|
|
|
<Box sx={modalStyle}>
|
|
|
|
|
<Typography variant="h6" component="h2" gutterBottom>
|
|
|
|
|
{isEditing ? 'Editar Pago de Distribuidor' : 'Registrar Nuevo Pago de Distribuidor'}
|
|
|
|
|
</Typography>
|
|
|
|
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
|
|
|
|
<FormControl fullWidth margin="dense" error={!!localErrors.idDistribuidor} required disabled={isEditing}>
|
|
|
|
|
<InputLabel id="distribuidor-pago-select-label">Distribuidor</InputLabel>
|
|
|
|
|
<Select labelId="distribuidor-pago-select-label" label="Distribuidor" value={idDistribuidor}
|
|
|
|
|
onChange={(e) => {setIdDistribuidor(e.target.value as number); handleInputChange('idDistribuidor');}}
|
|
|
|
|
disabled={loading || loadingDropdowns || isEditing}
|
|
|
|
|
>
|
|
|
|
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
|
|
|
|
{distribuidores.map((d) => (<MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>))}
|
|
|
|
|
</Select>
|
|
|
|
|
{localErrors.idDistribuidor && <Typography color="error" variant="caption">{localErrors.idDistribuidor}</Typography>}
|
|
|
|
|
</FormControl>
|
|
|
|
|
|
|
|
|
|
<FormControl fullWidth margin="dense" error={!!localErrors.idEmpresa} required disabled={isEditing}>
|
|
|
|
|
<InputLabel id="empresa-pago-select-label">Empresa (del Saldo)</InputLabel>
|
|
|
|
|
<Select labelId="empresa-pago-select-label" label="Empresa (del Saldo)" value={idEmpresa}
|
|
|
|
|
onChange={(e) => {setIdEmpresa(e.target.value as number); handleInputChange('idEmpresa');}}
|
|
|
|
|
disabled={loading || loadingDropdowns || isEditing}
|
|
|
|
|
>
|
|
|
|
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
|
|
|
|
{empresas.map((e) => (<MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>))}
|
|
|
|
|
</Select>
|
|
|
|
|
{localErrors.idEmpresa && <Typography color="error" variant="caption">{localErrors.idEmpresa}</Typography>}
|
|
|
|
|
</FormControl>
|
|
|
|
|
|
|
|
|
|
<TextField label="Fecha Pago" type="date" value={fecha} required
|
|
|
|
|
onChange={(e) => {setFecha(e.target.value); handleInputChange('fecha');}}
|
|
|
|
|
margin="dense" fullWidth error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
|
|
|
|
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<FormControl component="fieldset" margin="dense" error={!!localErrors.tipoMovimiento} required>
|
|
|
|
|
<Typography component="legend" variant="body2" sx={{mb:0.5}}>Tipo de Movimiento</Typography>
|
|
|
|
|
<RadioGroup row value={tipoMovimiento} onChange={(e) => {setTipoMovimiento(e.target.value as 'Recibido' | 'Realizado'); handleInputChange('tipoMovimiento');}} >
|
|
|
|
|
<FormControlLabel value="Recibido" control={<Radio size="small"/>} label="Recibido de Distribuidor" disabled={loading || isEditing}/>
|
|
|
|
|
<FormControlLabel value="Realizado" control={<Radio size="small"/>} label="Realizado a Distribuidor" disabled={loading || isEditing}/>
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
{localErrors.tipoMovimiento && <Typography color="error" variant="caption">{localErrors.tipoMovimiento}</Typography>}
|
|
|
|
|
</FormControl>
|
|
|
|
|
|
|
|
|
|
<TextField label="Nro. Recibo" type="number" value={recibo} required
|
|
|
|
|
onChange={(e) => {setRecibo(e.target.value); handleInputChange('recibo');}}
|
|
|
|
|
margin="dense" fullWidth error={!!localErrors.recibo} helperText={localErrors.recibo || ''}
|
|
|
|
|
disabled={loading || isEditing} inputProps={{min:1}}
|
|
|
|
|
/>
|
|
|
|
|
<TextField label="Monto" type="number" value={monto} required
|
|
|
|
|
onChange={(e) => {setMonto(e.target.value); handleInputChange('monto');}}
|
|
|
|
|
margin="dense" fullWidth error={!!localErrors.monto} helperText={localErrors.monto || ''}
|
2025-06-12 19:36:21 -03:00
|
|
|
disabled={loading} inputProps={{step: "0.01", min:0.05, lang:"es-AR" }}
|
2025-05-23 15:47:39 -03:00
|
|
|
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
|
|
|
|
|
/>
|
|
|
|
|
<FormControl fullWidth margin="dense" error={!!localErrors.idTipoPago} required>
|
|
|
|
|
<InputLabel id="tipopago-pago-select-label">Tipo de Pago</InputLabel>
|
|
|
|
|
<Select labelId="tipopago-pago-select-label" label="Tipo de Pago" value={idTipoPago}
|
|
|
|
|
onChange={(e) => {setIdTipoPago(e.target.value as number); handleInputChange('idTipoPago');}}
|
|
|
|
|
disabled={loading || loadingDropdowns}
|
|
|
|
|
>
|
|
|
|
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
|
|
|
|
{tiposPago.map((tp) => (<MenuItem key={tp.idTipoPago} value={tp.idTipoPago}>{tp.nombre}</MenuItem>))}
|
|
|
|
|
</Select>
|
|
|
|
|
{localErrors.idTipoPago && <Typography color="error" variant="caption">{localErrors.idTipoPago}</Typography>}
|
|
|
|
|
</FormControl>
|
|
|
|
|
<TextField label="Detalle (Opcional)" value={detalle}
|
|
|
|
|
onChange={(e) => setDetalle(e.target.value)}
|
|
|
|
|
margin="dense" fullWidth multiline rows={2} disabled={loading}
|
|
|
|
|
/>
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
|
|
|
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
|
|
|
|
|
|
|
|
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
|
|
|
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
|
|
|
|
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns}>
|
|
|
|
|
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Registrar Pago')}
|
|
|
|
|
</Button>
|
|
|
|
|
</Box>
|
|
|
|
|
</Box>
|
|
|
|
|
</Box>
|
|
|
|
|
</Modal>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default PagoDistribuidorFormModal;
|