diff --git a/Marco.Pms.Model/Filters/PaymentRequestFilter.cs b/Marco.Pms.Model/Filters/PaymentRequestFilter.cs index 8b404af..12b2bf6 100644 --- a/Marco.Pms.Model/Filters/PaymentRequestFilter.cs +++ b/Marco.Pms.Model/Filters/PaymentRequestFilter.cs @@ -7,6 +7,7 @@ public List? CreatedByIds { get; set; } public List? CurrencyIds { get; set; } public List? ExpenseCategoryIds { get; set; } + public List? Payees { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } } diff --git a/Marco.Pms.Services/Service/ExpensesService.cs b/Marco.Pms.Services/Service/ExpensesService.cs index 24b22dd..0e7d022 100644 --- a/Marco.Pms.Services/Service/ExpensesService.cs +++ b/Marco.Pms.Services/Service/ExpensesService.cs @@ -1096,72 +1096,130 @@ namespace Marco.Pms.Services.Service #endregion #region =================================================================== Payment Request Functions =================================================================== - public async Task> GetPaymentRequestListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId) { - var paymentRequestQuery = _context.PaymentRequests - .Include(pr => pr.Currency) - .Include(pr => pr.Project) - .Include(pr => pr.RecurringPayment) - .Include(pr => pr.ExpenseCategory) - .Include(pr => pr.ExpenseStatus) - .Include(pr => pr.CreatedBy) - .ThenInclude(e => e!.JobRole) - .Where(pr => pr.TenantId == tenantId && - pr.IsActive == isActive && - pr.Currency != null && - pr.ExpenseCategory != null && - pr.ExpenseStatus != null && - pr.CreatedBy != null && - pr.CreatedBy.JobRole != null); + _logger.LogInfo("Start GetPaymentRequestListAsync: TenantId={TenantId}, PageNumber={PageNumber}, PageSize={PageSize}, EmployeeId={EmployeeId}", + tenantId, pageNumber, pageSize, loggedInEmployee.Id); - //PaymentRequestFilter? paymentRequestFilter = TryDeserializePaymentRequestFilter(filter); - - //if (paymentRequestFilter != null) - //{ - // if (paymentRequestFilter.ProjectIds?.Any() ?? false) - // { - // paymentRequestQuery = paymentRequestQuery - // .Where(pr => pr.ProjectId.HasValue && - // paymentRequestFilter.ProjectIds.Contains(pr.ProjectId.Value)); - // } - // if (paymentRequestFilter.StatusIds?.Any() ?? false) - // { - // paymentRequestQuery = paymentRequestQuery - // .Where(pr => paymentRequestFilter.StatusIds.Contains(pr.ExpenseStatusId)); - // } - // if (paymentRequestFilter.CreatedByIds?.Any() ?? false) - // { - // paymentRequestQuery = paymentRequestQuery - // .Where(pr => paymentRequestFilter.CreatedByIds.Contains(pr.CreatedById)); - // } - // if (paymentRequestFilter.ExpenseCategoryIds?.Any() ?? false) - // { - // paymentRequestQuery = paymentRequestQuery - // .Where(pr => paymentRequestFilter.ExpenseCategoryIds.Contains(pr.ExpenseCategoryId)); - // } - //} - - var totalEntites = await paymentRequestQuery.CountAsync(); - - var paymentRequests = await paymentRequestQuery - .OrderByDescending(e => e.CreatedAt) - .Skip((pageNumber - 1) * pageSize) - .Take(pageSize).ToListAsync(); - - - var totalPages = (int)Math.Ceiling((double)totalEntites / pageSize); - - var response = paymentRequests.Select(pr => + try { - var result = _mapper.Map(pr); - result.PaymentRequestUID = $"{pr.UIDPrefix}/{pr.UIDPostfix:D5}"; - return result; - }).ToList(); + // Initial query including the necessary navigation properties and basic multi-tenant/security constraints + var paymentRequestQuery = _context.PaymentRequests + .Include(pr => pr.Currency) + .Include(pr => pr.Project) + .Include(pr => pr.RecurringPayment) + .Include(pr => pr.ExpenseCategory) + .Include(pr => pr.ExpenseStatus) + .Include(pr => pr.CreatedBy) + .ThenInclude(e => e!.JobRole) + .Where(pr => pr.TenantId == tenantId && + pr.IsActive == isActive && + pr.Currency != null && + pr.ExpenseCategory != null && + pr.ExpenseStatus != null && + pr.CreatedBy != null && + pr.CreatedBy.JobRole != null); - return ApiResponse.SuccessResponse(response, $"{0} Payment request fetched successfully", 200); + // Deserialize and apply advanced filter if provided + PaymentRequestFilter? paymentRequestFilter = TryDeserializePaymentRequestFilter(filter); + + if (paymentRequestFilter != null) + { + if (paymentRequestFilter.ProjectIds?.Any() ?? false) + { + paymentRequestQuery = paymentRequestQuery + .Where(pr => pr.ProjectId.HasValue && paymentRequestFilter.ProjectIds.Contains(pr.ProjectId.Value)); + } + if (paymentRequestFilter.StatusIds?.Any() ?? false) + { + paymentRequestQuery = paymentRequestQuery + .Where(pr => paymentRequestFilter.StatusIds.Contains(pr.ExpenseStatusId)); + } + if (paymentRequestFilter.CreatedByIds?.Any() ?? false) + { + paymentRequestQuery = paymentRequestQuery + .Where(pr => paymentRequestFilter.CreatedByIds.Contains(pr.CreatedById)); + } + if (paymentRequestFilter.CurrencyIds?.Any() ?? false) + { + paymentRequestQuery = paymentRequestQuery + .Where(pr => paymentRequestFilter.CurrencyIds.Contains(pr.CurrencyId)); + } + if (paymentRequestFilter.ExpenseCategoryIds?.Any() ?? false) + { + paymentRequestQuery = paymentRequestQuery + .Where(pr => pr.ExpenseCategoryId.HasValue && paymentRequestFilter.ExpenseCategoryIds.Contains(pr.ExpenseCategoryId.Value)); + } + if (paymentRequestFilter.Payees?.Any() ?? false) + { + paymentRequestQuery = paymentRequestQuery + .Where(pr => paymentRequestFilter.Payees.Contains(pr.Payee)); + } + if (paymentRequestFilter.StartDate.HasValue && paymentRequestFilter.EndDate.HasValue) + { + DateTime startDate = paymentRequestFilter.StartDate.Value.Date; + DateTime endDate = paymentRequestFilter.EndDate.Value.Date; + + paymentRequestQuery = paymentRequestQuery + .Where(pr => pr.CreatedAt.Date >= startDate && pr.CreatedAt.Date <= endDate); + } + } + + // Full-text search by payee, title, or UID + if (!string.IsNullOrWhiteSpace(searchString)) + { + paymentRequestQuery = paymentRequestQuery + .Where(pr => + pr.Payee.Contains(searchString) || + pr.Title.Contains(searchString) || + ($"{pr.UIDPrefix}/{pr.UIDPostfix:D5}").Contains(searchString) + ); + } + + // Get total count for pagination + var totalEntities = await paymentRequestQuery.CountAsync(); + + // Fetch paginated result set + var paymentRequests = await paymentRequestQuery + .OrderByDescending(e => e.CreatedAt) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + var totalPages = (int)Math.Ceiling((double)totalEntities / pageSize); + + var results = paymentRequests.Select(pr => + { + var result = _mapper.Map(pr); + result.PaymentRequestUID = $"{pr.UIDPrefix}/{pr.UIDPostfix:D5}"; + return result; + }).ToList(); + + var response = new + { + CurrentPage = pageNumber, + TotalPages = totalPages, + TotalEntities = totalEntities, + Data = results, + }; + + _logger.LogInfo("GetPaymentRequestListAsync: {ResultCount} payment requests fetched successfully for TenantId={TenantId} Page={PageNumber}/{TotalPages}", + results.Count, tenantId, pageNumber, totalPages); + + return ApiResponse.SuccessResponse(response, $"{results.Count} payment requests fetched successfully.", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occurred in GetPaymentRequestListAsync for TenantId={TenantId}, EmployeeId={EmployeeId}: {Message}", tenantId, loggedInEmployee.Id, ex.Message); + return ApiResponse.ErrorResponse("An error occurred while fetching payment requests.", ex.Message, 500); + } + finally + { + _logger.LogInfo("End GetPaymentRequestListAsync for TenantId={TenantId}, EmployeeId={EmployeeId}", tenantId, loggedInEmployee.Id); + } } + public async Task> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId) { _logger.LogInfo("Start CreatePaymentRequestAsync for EmployeeId: {EmployeeId} TenantId: {TenantId}", loggedInEmployee.Id, tenantId);