Fix: Captura y Muestra del Error Por Recibo Duplicado
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 2m2s
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 2m2s
This commit is contained in:
@@ -16,7 +16,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
|||||||
Task<PagoDistribuidor?> CreateAsync(PagoDistribuidor nuevoPago, int idUsuario, IDbTransaction transaction);
|
Task<PagoDistribuidor?> CreateAsync(PagoDistribuidor nuevoPago, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction);
|
Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction);
|
Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
|
Task<PagoDistribuidor?> GetByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
|
||||||
Task<IEnumerable<(PagoDistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
Task<IEnumerable<(PagoDistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
||||||
DateTime? fechaDesde, DateTime? fechaHasta,
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
int? idUsuarioModifico, string? tipoModificacion,
|
int? idUsuarioModifico, string? tipoModificacion,
|
||||||
|
|||||||
@@ -70,9 +70,10 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null)
|
public async Task<PagoDistribuidor?> GetByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.cue_PagosDistribuidor WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam");
|
var sqlBuilder = new StringBuilder(SelectQueryBase()); // Reutiliza la consulta base
|
||||||
|
sqlBuilder.Append(" WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam");
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
parameters.Add("ReciboParam", recibo);
|
parameters.Add("ReciboParam", recibo);
|
||||||
parameters.Add("TipoMovParam", tipoMovimiento);
|
parameters.Add("TipoMovParam", tipoMovimiento);
|
||||||
@@ -85,12 +86,12 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var connection = _cf.CreateConnection();
|
using var connection = _cf.CreateConnection();
|
||||||
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
return await connection.QuerySingleOrDefaultAsync<PagoDistribuidor>(sqlBuilder.ToString(), parameters);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_log.LogError(ex, "Error en ExistsByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
|
_log.LogError(ex, "Error en GetByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
|
||||||
return true; // Asumir que existe en caso de error para prevenir duplicados
|
throw; // Relanzar para que el servicio lo maneje
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,8 +93,18 @@ namespace GestionIntegral.Api.Services.Contables
|
|||||||
return (null, "Tipo de pago no válido.");
|
return (null, "Tipo de pago no válido.");
|
||||||
if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null)
|
if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null)
|
||||||
return (null, "Empresa no válida.");
|
return (null, "Empresa no válida.");
|
||||||
if (await _pagoRepo.ExistsByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento))
|
var pagoExistente = await _pagoRepo.GetByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento);
|
||||||
return (null, $"Ya existe un pago '{createDto.TipoMovimiento}' con el número de recibo '{createDto.Recibo}'.");
|
if (pagoExistente != null)
|
||||||
|
{
|
||||||
|
// Si encontramos un duplicado, obtenemos los detalles para el mensaje de error
|
||||||
|
var distribuidor = await _distribuidorRepo.GetByIdSimpleAsync(pagoExistente.IdDistribuidor);
|
||||||
|
var empresa = await _empresaRepo.GetByIdAsync(pagoExistente.IdEmpresa);
|
||||||
|
|
||||||
|
string mensajeError = $"El recibo N° {createDto.Recibo} ya fue registrado como '{pagoExistente.TipoMovimiento}' el {pagoExistente.Fecha:dd/MM/yyyy} " +
|
||||||
|
$"para el distribuidor '{distribuidor?.Nombre ?? "Desconocido"}' en la empresa '{empresa?.Nombre ?? "Desconocida"}'.";
|
||||||
|
|
||||||
|
return (null, mensajeError);
|
||||||
|
}
|
||||||
|
|
||||||
var nuevoPago = new PagoDistribuidor
|
var nuevoPago = new PagoDistribuidor
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
|
|||||||
const isEditing = Boolean(initialData);
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Esta función se encarga de cargar los datos de los dropdowns.
|
||||||
const fetchDropdownData = async () => {
|
const fetchDropdownData = async () => {
|
||||||
setLoadingDropdowns(true);
|
setLoadingDropdowns(true);
|
||||||
try {
|
try {
|
||||||
@@ -133,7 +134,9 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
|
|||||||
idTipoPago: Number(idTipoPago),
|
idTipoPago: Number(idTipoPago),
|
||||||
detalle: detalle || undefined,
|
detalle: detalle || undefined,
|
||||||
};
|
};
|
||||||
|
// << INICIO DE LA CORRECCIÓN >>
|
||||||
await onSubmit(dataToSubmit, initialData.idPago);
|
await onSubmit(dataToSubmit, initialData.idPago);
|
||||||
|
// << FIN DE LA CORRECCIÓN >>
|
||||||
} else {
|
} else {
|
||||||
const dataToSubmit: CreatePagoDistribuidorDto = {
|
const dataToSubmit: CreatePagoDistribuidorDto = {
|
||||||
idDistribuidor: Number(idDistribuidor),
|
idDistribuidor: Number(idDistribuidor),
|
||||||
@@ -147,7 +150,9 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
|
|||||||
};
|
};
|
||||||
await onSubmit(dataToSubmit);
|
await onSubmit(dataToSubmit);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Error en submit de PagoDistribuidorFormModal:", error);
|
console.error("Error en submit de PagoDistribuidorFormModal:", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
|||||||
const [pagos, setPagos] = useState<PagoDistribuidorDto[]>([]);
|
const [pagos, setPagos] = useState<PagoDistribuidorDto[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
const [pageApiErrorMessage, setPageApiErrorMessage] = useState<string | null>(null);
|
||||||
|
const [modalApiErrorMessage, setModalApiErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
// Filtros
|
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);//useState('');
|
|
||||||
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
|
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
|
||||||
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
||||||
const [filtroTipoMov, setFiltroTipoMov] = useState<'Recibido' | 'Realizado' | ''>('');
|
const [filtroTipoMov, setFiltroTipoMov] = useState<'Recibido' | 'Realizado' | ''>('');
|
||||||
@@ -50,7 +50,6 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
|||||||
const [selectedRow, setSelectedRow] = useState<PagoDistribuidorDto | null>(null);
|
const [selectedRow, setSelectedRow] = useState<PagoDistribuidorDto | null>(null);
|
||||||
|
|
||||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||||
// Permisos CP001 (Ver), CP002 (Crear), CP003 (Modificar), CP004 (Eliminar)
|
|
||||||
const puedeVer = isSuperAdmin || tienePermiso("CP001");
|
const puedeVer = isSuperAdmin || tienePermiso("CP001");
|
||||||
const puedeCrear = isSuperAdmin || tienePermiso("CP002");
|
const puedeCrear = isSuperAdmin || tienePermiso("CP002");
|
||||||
const puedeModificar = isSuperAdmin || tienePermiso("CP003");
|
const puedeModificar = isSuperAdmin || tienePermiso("CP003");
|
||||||
@@ -65,15 +64,21 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
|||||||
]);
|
]);
|
||||||
setDistribuidores(distData);
|
setDistribuidores(distData);
|
||||||
setEmpresas(empData);
|
setEmpresas(empData);
|
||||||
} catch (err) { console.error(err); setError("Error al cargar opciones de filtro.");
|
} catch (err) {
|
||||||
|
console.error(err); setError("Error al cargar opciones de filtro.");
|
||||||
} finally { setLoadingFiltersDropdown(false); }
|
} finally { setLoadingFiltersDropdown(false); }
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => { fetchFiltersDropdownData(); }, [fetchFiltersDropdownData]);
|
useEffect(() => { fetchFiltersDropdownData(); }, [fetchFiltersDropdownData]);
|
||||||
|
|
||||||
|
const clearModalApiErrorMessage = useCallback(() => {
|
||||||
|
setModalApiErrorMessage(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const cargarPagos = useCallback(async () => {
|
const cargarPagos = useCallback(async () => {
|
||||||
if (!puedeVer) { setError("No tiene permiso."); setLoading(false); return; }
|
if (!puedeVer) { setError("No tiene permiso."); setLoading(false); return; }
|
||||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
setLoading(true); setError(null); setPageApiErrorMessage(null);
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
fechaDesde: filtroFechaDesde || null, fechaHasta: filtroFechaHasta || null,
|
fechaDesde: filtroFechaDesde || null, fechaHasta: filtroFechaHasta || null,
|
||||||
@@ -83,19 +88,20 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
const data = await pagoDistribuidorService.getAllPagosDistribuidor(params);
|
const data = await pagoDistribuidorService.getAllPagosDistribuidor(params);
|
||||||
setPagos(data);
|
setPagos(data);
|
||||||
} catch (err) { console.error(err); setError('Error al cargar los pagos.');
|
} catch (err) {
|
||||||
|
console.error(err); setError('Error al cargar los pagos.');
|
||||||
} finally { setLoading(false); }
|
} finally { setLoading(false); }
|
||||||
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdDistribuidor, filtroIdEmpresa, filtroTipoMov]);
|
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdDistribuidor, filtroIdEmpresa, filtroTipoMov]);
|
||||||
|
|
||||||
useEffect(() => { cargarPagos(); }, [cargarPagos]);
|
useEffect(() => { cargarPagos(); }, [cargarPagos]);
|
||||||
|
|
||||||
const handleOpenModal = (item?: PagoDistribuidorDto) => {
|
const handleOpenModal = (item?: PagoDistribuidorDto) => {
|
||||||
setEditingPago(item || null); setApiErrorMessage(null); setModalOpen(true);
|
setEditingPago(item || null); setModalApiErrorMessage(null); setModalOpen(true);
|
||||||
};
|
};
|
||||||
const handleCloseModal = () => { setModalOpen(false); setEditingPago(null); };
|
const handleCloseModal = () => { setModalOpen(false); setEditingPago(null); };
|
||||||
|
|
||||||
const handleSubmitModal = async (data: CreatePagoDistribuidorDto | UpdatePagoDistribuidorDto, idPago?: number) => {
|
const handleSubmitModal = async (data: CreatePagoDistribuidorDto | UpdatePagoDistribuidorDto, idPago?: number) => {
|
||||||
setApiErrorMessage(null);
|
setModalApiErrorMessage(null);
|
||||||
try {
|
try {
|
||||||
if (idPago && editingPago) {
|
if (idPago && editingPago) {
|
||||||
await pagoDistribuidorService.updatePagoDistribuidor(idPago, data as UpdatePagoDistribuidorDto);
|
await pagoDistribuidorService.updatePagoDistribuidor(idPago, data as UpdatePagoDistribuidorDto);
|
||||||
@@ -105,15 +111,19 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
|||||||
cargarPagos();
|
cargarPagos();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar el pago.';
|
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar el pago.';
|
||||||
setApiErrorMessage(message); throw err;
|
setModalApiErrorMessage(message);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (idPago: number) => {
|
const handleDelete = async (idPago: number) => {
|
||||||
if (window.confirm(`¿Seguro de eliminar este pago (ID: ${idPago})? Esta acción revertirá el impacto en el saldo.`)) {
|
if (window.confirm(`¿Seguro de eliminar este pago (ID: ${idPago})? Esta acción revertirá el impacto en el saldo.`)) {
|
||||||
setApiErrorMessage(null);
|
setPageApiErrorMessage(null);
|
||||||
try { await pagoDistribuidorService.deletePagoDistribuidor(idPago); cargarPagos(); }
|
try { await pagoDistribuidorService.deletePagoDistribuidor(idPago); cargarPagos(); }
|
||||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.'; setApiErrorMessage(msg); }
|
catch (err: any) {
|
||||||
|
const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.';
|
||||||
|
setPageApiErrorMessage(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
};
|
};
|
||||||
@@ -130,18 +140,11 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
|||||||
const displayData = pagos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
const displayData = pagos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||||
const formatDate = (dateString?: string | null): string => {
|
const formatDate = (dateString?: string | null): string => {
|
||||||
if (!dateString) return '-';
|
if (!dateString) return '-';
|
||||||
// La fecha llega como "YYYY-MM-DD" o "YYYY-MM-DDTHH:mm:ss"
|
|
||||||
// Tomamos solo la parte de la fecha para evitar problemas de zona horaria.
|
|
||||||
const datePart = dateString.split('T')[0];
|
const datePart = dateString.split('T')[0];
|
||||||
const parts = datePart.split('-');
|
const parts = datePart.split('-');
|
||||||
|
|
||||||
// Aseguramos que el formato sea el esperado antes de reordenar
|
|
||||||
if (parts.length === 3) {
|
if (parts.length === 3) {
|
||||||
// parts[0] = YYYY, parts[1] = MM, parts[2] = DD
|
return `${parts[2]}/${parts[1]}/${parts[0]}`;
|
||||||
return `${parts[2]}/${parts[1]}/${parts[0]}`; // Formato DD/MM/YYYY
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si el formato no es el esperado, devolvemos la parte de la fecha tal como vino.
|
|
||||||
return datePart;
|
return datePart;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,7 +186,7 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
|||||||
|
|
||||||
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||||
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
{apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>}
|
{pageApiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{pageApiErrorMessage}</Alert>}
|
||||||
|
|
||||||
{!loading && !error && puedeVer && (
|
{!loading && !error && puedeVer && (
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
@@ -235,9 +238,12 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
|||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<PagoDistribuidorFormModal
|
<PagoDistribuidorFormModal
|
||||||
open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal}
|
open={modalOpen}
|
||||||
initialData={editingPago} errorMessage={apiErrorMessage}
|
onClose={handleCloseModal}
|
||||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
onSubmit={handleSubmitModal}
|
||||||
|
initialData={editingPago}
|
||||||
|
errorMessage={modalApiErrorMessage}
|
||||||
|
clearErrorMessage={clearModalApiErrorMessage}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user