diff --git a/Marco.Pms.Services/Service/ExpensesService.cs b/Marco.Pms.Services/Service/ExpensesService.cs index c838aab..7d30672 100644 --- a/Marco.Pms.Services/Service/ExpensesService.cs +++ b/Marco.Pms.Services/Service/ExpensesService.cs @@ -206,34 +206,12 @@ namespace Marco.Pms.Services.Service catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Databsae Exception occured while fetching list expenses"); - return ApiResponse.ErrorResponse("Databsae Exception", new - { - Message = dbEx.Message, - StackTrace = dbEx.StackTrace, - Source = dbEx.Source, - InnerException = new - { - Message = dbEx.InnerException?.Message, - StackTrace = dbEx.InnerException?.StackTrace, - Source = dbEx.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Databsae Exception", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Error occured while fetching list expenses"); - return ApiResponse.ErrorResponse("Error Occured", new - { - Message = ex.Message, - StackTrace = ex.StackTrace, - Source = ex.Source, - InnerException = new - { - Message = ex.InnerException?.Message, - StackTrace = ex.InnerException?.StackTrace, - Source = ex.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Error Occured", ExceptionMapper(ex), 500); } } public async Task> GetExpenseDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId) @@ -259,18 +237,7 @@ namespace Marco.Pms.Services.Service catch (Exception ex) { _logger.LogError(ex, "An unhandled exception occurred while fetching an expense details {ExpenseId}.", id); - return ApiResponse.ErrorResponse("An internal server error occurred.", new - { - Message = ex.Message, - StackTrace = ex.StackTrace, - Source = ex.Source, - InnerException = new - { - Message = ex.InnerException?.Message, - StackTrace = ex.InnerException?.StackTrace, - Source = ex.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("An internal server error occurred.", ExceptionMapper(ex), 500); } } public async Task> GetSupplerNameListAsync(Employee loggedInEmployee, Guid tenantId) @@ -284,18 +251,7 @@ namespace Marco.Pms.Services.Service catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Databsae Exception occured while fetching suppler name list from expense"); - return ApiResponse.ErrorResponse("Databsae Exception", new - { - Message = dbEx.Message, - StackTrace = dbEx.StackTrace, - Source = dbEx.Source, - InnerException = new - { - Message = dbEx.InnerException?.Message, - StackTrace = dbEx.InnerException?.StackTrace, - Source = dbEx.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Databsae Exception", ExceptionMapper(dbEx), 500); } } @@ -448,50 +404,17 @@ namespace Marco.Pms.Services.Service { await transaction.RollbackAsync(); _logger.LogError(dbEx, "Databsae Exception occured while adding expense"); - return ApiResponse.ErrorResponse("Databsae Exception", new - { - Message = dbEx.Message, - StackTrace = dbEx.StackTrace, - Source = dbEx.Source, - InnerException = new - { - Message = dbEx.InnerException?.Message, - StackTrace = dbEx.InnerException?.StackTrace, - Source = dbEx.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Databsae Exception", ExceptionMapper(dbEx), 500); } catch (ArgumentException ex) // Catches bad Base64 from attachment pre-validation { _logger.LogError(ex, "Invalid argument during expense creation for project {ProjectId}.", dto.ProjectId); - return ApiResponse.ErrorResponse("Invalid Request Data.", new - { - Message = ex.Message, - StackTrace = ex.StackTrace, - Source = ex.Source, - InnerException = new - { - Message = ex.InnerException?.Message, - StackTrace = ex.InnerException?.StackTrace, - Source = ex.InnerException?.Source, - } - }, 400); + return ApiResponse.ErrorResponse("Invalid Request Data.", ExceptionMapper(ex), 400); } catch (Exception ex) // General-purpose catch for unexpected errors (e.g., S3 or DB connection failure) { _logger.LogError(ex, "An unhandled exception occurred while creating an expense for project {ProjectId}.", dto.ProjectId); - return ApiResponse.ErrorResponse("An internal server error occurred.", new - { - Message = ex.Message, - StackTrace = ex.StackTrace, - Source = ex.Source, - InnerException = new - { - Message = ex.InnerException?.Message, - StackTrace = ex.InnerException?.StackTrace, - Source = ex.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("An internal server error occurred.", ExceptionMapper(ex), 500); } } @@ -705,8 +628,6 @@ namespace Marco.Pms.Services.Service .Include(e => e.CreatedBy) .FirstOrDefaultAsync(e => e.Id == model.Id && - e.CreatedById == loggedInEmployee.Id && - (e.StatusId == Draft || e.StatusId == Rejected) && e.TenantId == tenantId); @@ -716,6 +637,17 @@ namespace Marco.Pms.Services.Service return ApiResponse.ErrorResponse("Expense not found", "Expense not found", 404); } + if (existingExpense.StatusId != Draft && existingExpense.StatusId != Rejected) + { + _logger.LogWarning("User attempted to update expense with ID {ExpenseId}, but donot have status of DRAFT or REJECTED", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Expense connot be updated", "Expense connot be updated", 400); + } + if (existingExpense.CreatedById != loggedInEmployee.Id) + { + _logger.LogWarning("User attempted to update expense with ID {ExpenseId} which not created by them", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("You donot have access to update this expense", "You donot have access to update this expense", 400); + } + var existingEntityBson = _updateLogHelper.EntityToBsonDocument(existingExpense); // Capture state for audit log BEFORE changes _mapper.Map(model, existingExpense); _context.Entry(existingExpense).State = EntityState.Modified; @@ -748,18 +680,7 @@ namespace Marco.Pms.Services.Service catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Databsae Exception occured while adding new attachments during updating expense"); - return ApiResponse.ErrorResponse("Databsae Exception", new - { - Message = dbEx.Message, - StackTrace = dbEx.StackTrace, - Source = dbEx.Source, - InnerException = new - { - Message = dbEx.InnerException?.Message, - StackTrace = dbEx.InnerException?.StackTrace, - Source = dbEx.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Databsae Exception", ExceptionMapper(dbEx), 500); } } @@ -777,34 +698,12 @@ namespace Marco.Pms.Services.Service catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Databsae Exception occured while deleting attachments during updating expense"); - return ApiResponse.ErrorResponse("Databsae Exception", new - { - Message = dbEx.Message, - StackTrace = dbEx.StackTrace, - Source = dbEx.Source, - InnerException = new - { - Message = dbEx.InnerException?.Message, - StackTrace = dbEx.InnerException?.StackTrace, - Source = dbEx.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Databsae Exception", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while deleting attachments during updating expense"); - return ApiResponse.ErrorResponse("Exception occured while deleting attachments during updating expense ", new - { - Message = ex.Message, - StackTrace = ex.StackTrace, - Source = ex.Source, - InnerException = new - { - Message = ex.InnerException?.Message, - StackTrace = ex.InnerException?.StackTrace, - Source = ex.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Exception occured while deleting attachments during updating expense ", ExceptionMapper(ex), 500); } } } @@ -874,7 +773,11 @@ namespace Marco.Pms.Services.Service public async Task> DeleteExpanseAsync(Guid id, Employee loggedInEmployee, Guid tenantId) { - var expenseQuery = _context.Expenses.Where(e => e.Id == id && e.StatusId == Draft && e.TenantId == tenantId); + var expenseTask = Task.Run(async () => + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); + return await dbContext.Expenses.Where(e => e.Id == id && e.StatusId == Draft && e.TenantId == tenantId).FirstOrDefaultAsync(); + }); var hasAprrovePermissionTask = Task.Run(async () => { @@ -883,13 +786,11 @@ namespace Marco.Pms.Services.Service return await permissionService.HasPermission(PermissionsMaster.ExpenseApprove, loggedInEmployee.Id); }); - var hasAprrovePermission = await hasAprrovePermissionTask; - if (!hasAprrovePermission) - { - expenseQuery = expenseQuery.Where(e => e.CreatedById == loggedInEmployee.Id); - } + await Task.WhenAll(expenseTask, hasAprrovePermissionTask); + + var hasAprrovePermission = hasAprrovePermissionTask.Result; + var existingExpense = expenseTask.Result; - var existingExpense = await expenseQuery.FirstOrDefaultAsync(); if (existingExpense == null) { var message = hasAprrovePermission ? "Expenses not found" : "Expense cannot be deleted"; @@ -903,6 +804,19 @@ namespace Marco.Pms.Services.Service } return ApiResponse.ErrorResponse(message, message, 400); } + if (existingExpense.StatusId != Draft) + { + _logger.LogWarning("User attempted to delete expense with ID {ExpenseId}, but donot have status of DRAFT or REJECTED", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Expense connot be deleted", "Expense connot be deleted", 400); + } + + if (!hasAprrovePermission && existingExpense.CreatedById != loggedInEmployee.Id) + { + _logger.LogWarning("User attempted to delete expense with ID {ExpenseId} which not created by them", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("You donot have access to delete this expense", "You donot have access to delete this expense", 400); + + } + var documentIds = await _context.BillAttachments .Where(ba => ba.ExpensesId == existingExpense.Id) .Select(ba => ba.DocumentId) @@ -919,18 +833,7 @@ namespace Marco.Pms.Services.Service catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Databsae Exception occured while deleting expense"); - return ApiResponse.ErrorResponse("Databsae Exception", new - { - Message = dbEx.Message, - StackTrace = dbEx.StackTrace, - Source = dbEx.Source, - InnerException = new - { - Message = dbEx.InnerException?.Message, - StackTrace = dbEx.InnerException?.StackTrace, - Source = dbEx.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Databsae Exception", ExceptionMapper(dbEx), 500); } try { @@ -956,34 +859,12 @@ namespace Marco.Pms.Services.Service catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Databsae Exception occured while deleting attachments during updating expense"); - return ApiResponse.ErrorResponse("Databsae Exception", new - { - Message = dbEx.Message, - StackTrace = dbEx.StackTrace, - Source = dbEx.Source, - InnerException = new - { - Message = dbEx.InnerException?.Message, - StackTrace = dbEx.InnerException?.StackTrace, - Source = dbEx.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Databsae Exception", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while deleting attachments during updating expense"); - return ApiResponse.ErrorResponse("Exception occured while deleting attachments during updating expense ", new - { - Message = ex.Message, - StackTrace = ex.StackTrace, - Source = ex.Source, - InnerException = new - { - Message = ex.InnerException?.Message, - StackTrace = ex.InnerException?.StackTrace, - Source = ex.InnerException?.Source, - } - }, 500); + return ApiResponse.ErrorResponse("Exception occured while deleting attachments during updating expense ", ExceptionMapper(ex), 500); } return ApiResponse.SuccessResponse("Success", "Expense Deleted Successfully", 200); } @@ -991,7 +872,21 @@ namespace Marco.Pms.Services.Service #endregion #region =================================================================== Helper Functions =================================================================== - + private static object ExceptionMapper(Exception ex) + { + return new + { + Message = ex.Message, + StackTrace = ex.StackTrace, + Source = ex.Source, + InnerException = new + { + Message = ex.InnerException?.Message, + StackTrace = ex.InnerException?.StackTrace, + Source = ex.InnerException?.Source, + } + }; + } private async Task> GetAllExpnesRelatedTables(List model, Guid tenantId) { List expenseList = new List(); diff --git a/Marco.Pms.Services/Service/MasterService.cs b/Marco.Pms.Services/Service/MasterService.cs index 16d98b2..6d789bb 100644 --- a/Marco.Pms.Services/Service/MasterService.cs +++ b/Marco.Pms.Services/Service/MasterService.cs @@ -455,7 +455,7 @@ namespace Marco.Pms.Services.Service return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } - var expensesStatus = await _context.ExpensesStatusMaster.FirstOrDefaultAsync(et => et.Id == id && et.TenantId == tenantId && !et.IsSystem); + var expensesStatus = await _context.ExpensesStatusMaster.FirstOrDefaultAsync(et => et.Id == id && et.TenantId == tenantId); // Checking if Expense Status exists if (expensesStatus == null) @@ -464,6 +464,12 @@ namespace Marco.Pms.Services.Service return ApiResponse.ErrorResponse("Expense Status not found", "Expense Status not found", 404); } + if (expensesStatus.IsSystem) + { + _logger.LogWarning("Employee {Employee} attempts to {Action} Expense status, but status is system defined", loggedInEmployee.Id, action); + return ApiResponse.ErrorResponse($"Expense Status is system defined cannot able to {action}", $"Expense Status is system defined cannot able to {action}", 400); + } + // Mapping ExpensesStatusMaster to BsonDocument var existingEntityBson = _updateLogHelper.EntityToBsonDocument(expensesStatus);