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<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, 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(
|
||||
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||
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();
|
||||
parameters.Add("ReciboParam", recibo);
|
||||
parameters.Add("TipoMovParam", tipoMovimiento);
|
||||
@@ -85,12 +86,12 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||
try
|
||||
{
|
||||
using var connection = _cf.CreateConnection();
|
||||
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
||||
return await connection.QuerySingleOrDefaultAsync<PagoDistribuidor>(sqlBuilder.ToString(), parameters);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "Error en ExistsByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
|
||||
return true; // Asumir que existe en caso de error para prevenir duplicados
|
||||
_log.LogError(ex, "Error en GetByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
|
||||
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.");
|
||||
if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null)
|
||||
return (null, "Empresa no válida.");
|
||||
if (await _pagoRepo.ExistsByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento))
|
||||
return (null, $"Ya existe un pago '{createDto.TipoMovimiento}' con el número de recibo '{createDto.Recibo}'.");
|
||||
var pagoExistente = await _pagoRepo.GetByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento);
|
||||
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
|
||||
{
|
||||
|
||||
@@ -64,6 +64,7 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
|
||||
const isEditing = Boolean(initialData);
|
||||
|
||||
useEffect(() => {
|
||||
// Esta función se encarga de cargar los datos de los dropdowns.
|
||||
const fetchDropdownData = async () => {
|
||||
setLoadingDropdowns(true);
|
||||
try {
|
||||
@@ -133,7 +134,9 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
|
||||
idTipoPago: Number(idTipoPago),
|
||||
detalle: detalle || undefined,
|
||||
};
|
||||
// << INICIO DE LA CORRECCIÓN >>
|
||||
await onSubmit(dataToSubmit, initialData.idPago);
|
||||
// << FIN DE LA CORRECCIÓN >>
|
||||
} else {
|
||||
const dataToSubmit: CreatePagoDistribuidorDto = {
|
||||
idDistribuidor: Number(idDistribuidor),
|
||||
@@ -147,7 +150,9 @@ const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
|
||||
};
|
||||
await onSubmit(dataToSubmit);
|
||||
}
|
||||
|
||||
onClose();
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Error en submit de PagoDistribuidorFormModal:", error);
|
||||
} finally {
|
||||
|
||||
@@ -28,11 +28,11 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
const [pagos, setPagos] = useState<PagoDistribuidorDto[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
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]); //useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);//useState('');
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
|
||||
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
||||
const [filtroTipoMov, setFiltroTipoMov] = useState<'Recibido' | 'Realizado' | ''>('');
|
||||
@@ -50,7 +50,6 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
const [selectedRow, setSelectedRow] = useState<PagoDistribuidorDto | null>(null);
|
||||
|
||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||
// Permisos CP001 (Ver), CP002 (Crear), CP003 (Modificar), CP004 (Eliminar)
|
||||
const puedeVer = isSuperAdmin || tienePermiso("CP001");
|
||||
const puedeCrear = isSuperAdmin || tienePermiso("CP002");
|
||||
const puedeModificar = isSuperAdmin || tienePermiso("CP003");
|
||||
@@ -65,15 +64,21 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
]);
|
||||
setDistribuidores(distData);
|
||||
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); }
|
||||
}, []);
|
||||
|
||||
useEffect(() => { fetchFiltersDropdownData(); }, [fetchFiltersDropdownData]);
|
||||
|
||||
const clearModalApiErrorMessage = useCallback(() => {
|
||||
setModalApiErrorMessage(null);
|
||||
}, []);
|
||||
|
||||
|
||||
const cargarPagos = useCallback(async () => {
|
||||
if (!puedeVer) { setError("No tiene permiso."); setLoading(false); return; }
|
||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
||||
setLoading(true); setError(null); setPageApiErrorMessage(null);
|
||||
try {
|
||||
const params = {
|
||||
fechaDesde: filtroFechaDesde || null, fechaHasta: filtroFechaHasta || null,
|
||||
@@ -83,19 +88,20 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
};
|
||||
const data = await pagoDistribuidorService.getAllPagosDistribuidor(params);
|
||||
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); }
|
||||
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdDistribuidor, filtroIdEmpresa, filtroTipoMov]);
|
||||
|
||||
useEffect(() => { cargarPagos(); }, [cargarPagos]);
|
||||
|
||||
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 handleSubmitModal = async (data: CreatePagoDistribuidorDto | UpdatePagoDistribuidorDto, idPago?: number) => {
|
||||
setApiErrorMessage(null);
|
||||
setModalApiErrorMessage(null);
|
||||
try {
|
||||
if (idPago && editingPago) {
|
||||
await pagoDistribuidorService.updatePagoDistribuidor(idPago, data as UpdatePagoDistribuidorDto);
|
||||
@@ -105,15 +111,19 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
cargarPagos();
|
||||
} catch (err: any) {
|
||||
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) => {
|
||||
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(); }
|
||||
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();
|
||||
};
|
||||
@@ -130,18 +140,11 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
const displayData = pagos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||
const formatDate = (dateString?: string | null): string => {
|
||||
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 parts = datePart.split('-');
|
||||
|
||||
// Aseguramos que el formato sea el esperado antes de reordenar
|
||||
if (parts.length === 3) {
|
||||
// parts[0] = YYYY, parts[1] = MM, parts[2] = DD
|
||||
return `${parts[2]}/${parts[1]}/${parts[0]}`; // Formato DD/MM/YYYY
|
||||
return `${parts[2]}/${parts[1]}/${parts[0]}`;
|
||||
}
|
||||
|
||||
// Si el formato no es el esperado, devolvemos la parte de la fecha tal como vino.
|
||||
return datePart;
|
||||
};
|
||||
|
||||
@@ -183,7 +186,7 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
|
||||
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||
{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 && (
|
||||
<TableContainer component={Paper}>
|
||||
@@ -235,9 +238,12 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
</Menu>
|
||||
|
||||
<PagoDistribuidorFormModal
|
||||
open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal}
|
||||
initialData={editingPago} errorMessage={apiErrorMessage}
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
open={modalOpen}
|
||||
onClose={handleCloseModal}
|
||||
onSubmit={handleSubmitModal}
|
||||
initialData={editingPago}
|
||||
errorMessage={modalApiErrorMessage}
|
||||
clearErrorMessage={clearModalApiErrorMessage}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user