Added BaseAmount In Expense And PaymentRequest Tables

This commit is contained in:
ashutosh.nehete 2025-11-05 15:17:54 +05:30
parent 411e63db1b
commit a758fb8c35
18 changed files with 7889 additions and 10 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,91 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_BaseAmount_In_Expense_And_PaymentRequest_Tables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<double>(
name: "BaseAmount",
table: "PaymentRequests",
type: "double",
nullable: true);
migrationBuilder.AddColumn<double>(
name: "TaxAmount",
table: "PaymentRequests",
type: "double",
nullable: true);
migrationBuilder.AddColumn<double>(
name: "BaseAmount",
table: "Expenses",
type: "double",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "CurrencyId",
table: "Expenses",
type: "char(36)",
nullable: false,
defaultValue: new Guid("78e96e4a-7ce0-4164-ae3a-c833ad45ec2c"),
collation: "ascii_general_ci");
migrationBuilder.AddColumn<double>(
name: "TaxAmount",
table: "Expenses",
type: "double",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Expenses_CurrencyId",
table: "Expenses",
column: "CurrencyId");
migrationBuilder.AddForeignKey(
name: "FK_Expenses_CurrencyMaster_CurrencyId",
table: "Expenses",
column: "CurrencyId",
principalTable: "CurrencyMaster",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Expenses_CurrencyMaster_CurrencyId",
table: "Expenses");
migrationBuilder.DropIndex(
name: "IX_Expenses_CurrencyId",
table: "Expenses");
migrationBuilder.DropColumn(
name: "BaseAmount",
table: "PaymentRequests");
migrationBuilder.DropColumn(
name: "TaxAmount",
table: "PaymentRequests");
migrationBuilder.DropColumn(
name: "BaseAmount",
table: "Expenses");
migrationBuilder.DropColumn(
name: "CurrencyId",
table: "Expenses");
migrationBuilder.DropColumn(
name: "TaxAmount",
table: "Expenses");
}
}
}

View File

@ -2333,12 +2333,18 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<Guid?>("ApprovedById")
.HasColumnType("char(36)");
b.Property<double?>("BaseAmount")
.HasColumnType("double");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid>("CreatedById")
.HasColumnType("char(36)");
b.Property<Guid>("CurrencyId")
.HasColumnType("char(36)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
@ -2392,6 +2398,9 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<double?>("TDSPercentage")
.HasColumnType("double");
b.Property<double?>("TaxAmount")
.HasColumnType("double");
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
@ -2414,6 +2423,8 @@ namespace Marco.Pms.DataAccess.Migrations
b.HasIndex("CreatedById");
b.HasIndex("CurrencyId");
b.HasIndex("ExpenseCategoryId");
b.HasIndex("PaidById");
@ -2573,6 +2584,9 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<double>("Amount")
.HasColumnType("double");
b.Property<double?>("BaseAmount")
.HasColumnType("double");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
@ -2626,6 +2640,9 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<double?>("TDSPercentage")
.HasColumnType("double");
b.Property<double?>("TaxAmount")
.HasColumnType("double");
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
@ -6411,6 +6428,12 @@ namespace Marco.Pms.DataAccess.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Master.CurrencyMaster", "Currency")
.WithMany()
.HasForeignKey("CurrencyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Expenses.ExpenseCategoryMaster", "ExpenseCategory")
.WithMany()
.HasForeignKey("ExpenseCategoryId")
@ -6463,6 +6486,8 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("CreatedBy");
b.Navigation("Currency");
b.Navigation("ExpenseCategory");
b.Navigation("PaidBy");

View File

@ -15,6 +15,7 @@ namespace Marco.Pms.Model.Dtos.Expenses
public string? GSTNumber { get; set; }
public required string SupplerName { get; set; }
public required double Amount { get; set; }
public Guid? CurrencyId { get; set; }
public int? NoOfPersons { get; set; } = 0;
public bool PreApproved { get; set; } = false;
public required List<FileUploadModel> BillAttachments { get; set; }

View File

@ -6,6 +6,8 @@
public Guid StatusId { get; set; }
public string? Comment { get; set; }
public double? TDSPercentage { get; set; }
public double? BaseAmount { get; set; }
public double? TaxAmount { get; set; }
public string? ReimburseTransactionId { get; set; }
public DateTime? ReimburseDate { get; set; }
public Guid? ReimburseById { get; set; }

View File

@ -0,0 +1,7 @@
namespace Marco.Pms.Model.Dtos.Expenses
{
public class PaymentRequestConversionDto
{
public required List<Guid> RecurringTemplateIds { get; set; }
}
}

View File

@ -1,4 +1,6 @@
namespace Marco.Pms.Model.Dtos.Expenses
using Marco.Pms.Model.Master;
namespace Marco.Pms.Model.Dtos.Expenses
{
public class PaymentRequestRecordDto
{
@ -7,6 +9,8 @@
public string? Comment { get; set; }
public string? PaidTransactionId { get; set; }
public double? TDSPercentage { get; set; }
public double? BaseAmount { get; set; }
public double? TaxAmount { get; set; }
public DateTime? PaidAt { get; set; }
public Guid? PaidById { get; set; }
}

View File

@ -16,6 +16,7 @@ namespace Marco.Pms.Model.Dtos.Expenses
public string? GSTNumber { get; set; }
public required string SupplerName { get; set; }
public required double Amount { get; set; }
public Guid? CurrencyId { get; set; }
public int? NoOfPersons { get; set; } = 0;
public bool PreApproved { get; set; } = false;
public List<FileUploadModel>? BillAttachments { get; set; }

View File

@ -60,7 +60,14 @@ namespace Marco.Pms.Model.Expenses
public string? Location { get; set; }
public string? GSTNumber { get; set; }
public string SupplerName { get; set; } = string.Empty;
public Guid CurrencyId { get; set; }
[ValidateNever]
[ForeignKey("CurrencyId")]
public CurrencyMaster? Currency { get; set; }
public double Amount { get; set; }
public double? BaseAmount { get; set; }
public double? TaxAmount { get; set; }
public double? TDSPercentage { get; set; }
public int? NoOfPersons { get; set; }
public Guid? PaymentRequestId { get; set; }

View File

@ -22,6 +22,8 @@ namespace Marco.Pms.Model.Expenses
[ForeignKey("CurrencyId")]
public CurrencyMaster? Currency { get; set; }
public double Amount { get; set; }
public double? BaseAmount { get; set; }
public double? TaxAmount { get; set; }
public double? TDSPercentage { get; set; }
public DateTime DueDate { get; set; }
public Guid? ProjectId { get; set; }

View File

@ -1,4 +1,5 @@
using Marco.Pms.Model.MongoDBModels.Employees;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.MongoDBModels.Employees;
using Marco.Pms.Model.MongoDBModels.Masters;
using Marco.Pms.Model.MongoDBModels.Project;
@ -20,6 +21,9 @@ namespace Marco.Pms.Model.MongoDBModels.Expenses
public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1);
public string SupplerName { get; set; } = string.Empty;
public double Amount { get; set; }
public CurrencyMaster? Currency { get; set; }
public double? BaseAmount { get; set; }
public double? TaxAmount { get; set; }
public string? ExpenseUId { get; set; }
public ExpensesStatusMasterMongoDB Status { get; set; } = new ExpensesStatusMasterMongoDB();
public List<ExpensesStatusMasterMongoDB> NextStatus { get; set; } = new List<ExpensesStatusMasterMongoDB>();

View File

@ -1,4 +1,5 @@
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.DocumentManager;
using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Projects;
@ -20,6 +21,9 @@ namespace Marco.Pms.Model.ViewModels.Expenses
public DateTime CreatedAt { get; set; }
public string SupplerName { get; set; } = string.Empty;
public double Amount { get; set; }
public CurrencyMaster? Currency { get; set; }
public double? BaseAmount { get; set; }
public double? TaxAmount { get; set; }
public ExpensesStatusMasterVM? Status { get; set; }
public List<ExpensesStatusMasterVM>? NextStatus { get; set; }
public bool PreApproved { get; set; } = false;

View File

@ -1,4 +1,5 @@
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Projects;
@ -22,6 +23,9 @@ namespace Marco.Pms.Model.ViewModels.Expanses
public string Description { get; set; } = string.Empty;
public string TransactionId { get; set; } = string.Empty;
public double Amount { get; set; }
public CurrencyMaster? Currency { get; set; }
public double? BaseAmount { get; set; }
public double? TaxAmount { get; set; }
public ExpensesStatusMasterVM? Status { get; set; }
public List<ExpensesStatusMasterVM>? NextStatus { get; set; }
public bool PreApproved { get; set; } = false;

View File

@ -15,6 +15,8 @@ namespace Marco.Pms.Model.ViewModels.Expenses
public string? Payee { get; set; }
public CurrencyMaster? Currency { get; set; }
public double Amount { get; set; }
public double? BaseAmount { get; set; }
public double? TaxAmount { get; set; }
public DateTime DueDate { get; set; }
public BasicProjectVM? Project { get; set; }
public RecurringPayment? RecurringPayment { get; set; }

View File

@ -227,17 +227,34 @@ namespace Marco.Pms.Services.Controllers
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _expensesService.CreateRecurringPaymentAsync(model, loggedInEmployee, tenantId);
if (response.Success)
{
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Payment_Request", Response = response.Data };
await _signalR.SendNotificationAsync(notification);
}
return StatusCode(response.StatusCode, response);
}
[HttpPost("recurring-payment/convert/payment-request")]
public async Task<IActionResult> PaymentRequestConversion(PaymentRequestConversionDto model)
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _expensesService.PaymentRequestConversionAsync(model.RecurringTemplateIds, loggedInEmployee, tenantId);
return StatusCode(response.StatusCode, response);
}
[HttpPost("recurring-payment/edit/{id}")]
public async Task<IActionResult> EditRecurringPaymentAsync(Guid id, [FromBody] RecurringTemplateDto model)
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _expensesService.EditRecurringPaymentAsync(id, model, loggedInEmployee, tenantId);
if (response.Success)
{
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Payment_Request", Response = response.Data };
await _signalR.SendNotificationAsync(notification);
}
return StatusCode(response.StatusCode, response);
}

View File

@ -455,8 +455,12 @@ namespace Marco.Pms.Services.Controllers
using var scope = _serviceScopeFactory.CreateScope();
DateTime today = DateTime.Today;
DateTime firstDayOfMonth = new DateTime(today.Year, today.Month, 1);
DateTime firstDayOfNextMonth = firstDayOfMonth.AddMonths(1);
//DateTime firstDayOfMonth = new DateTime(today.Year, today.Month, 1);
//DateTime firstDayOfNextMonth = firstDayOfMonth.AddMonths(-1);
DateTime firstDayOfNextMonth = new DateTime(today.Year, today.Month, 1);
DateTime firstDayOfMonth = firstDayOfNextMonth.AddMonths(-1);
// Generate list of all dates in the month
var allDates = Enumerable.Range(0, (firstDayOfNextMonth - firstDayOfMonth).Days)

View File

@ -13,6 +13,7 @@ using Marco.Pms.Model.MongoDBModels.Expenses;
using Marco.Pms.Model.MongoDBModels.Masters;
using Marco.Pms.Model.MongoDBModels.Project;
using Marco.Pms.Model.MongoDBModels.Utility;
using Marco.Pms.Model.TenantModels;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Expanses;
@ -40,6 +41,7 @@ namespace Marco.Pms.Services.Service
private readonly UtilityMongoDBHelper _updateLogHelper;
private readonly CacheUpdateHelper _cache;
private readonly IMapper _mapper;
private static readonly Guid Draft = Guid.Parse("297e0d8f-f668-41b5-bfea-e03b354251c8");
private static readonly Guid Review = Guid.Parse("6537018f-f4e9-4cb3-a210-6c3b2da999d7");
private static readonly Guid RejectedByReviewer = Guid.Parse("965eda62-7907-4963-b4a1-657fb0b2724b");
@ -47,7 +49,12 @@ namespace Marco.Pms.Services.Service
private static readonly Guid RejectedByApprover = Guid.Parse("d1ee5eec-24b6-4364-8673-a8f859c60729");
private static readonly Guid ProcessPending = Guid.Parse("f18c5cfd-7815-4341-8da2-2c2d65778e27");
private static readonly Guid Processed = Guid.Parse("61578360-3a49-4c34-8604-7b35a3787b95");
private static readonly Guid AdvancePayment = Guid.Parse("f67beee6-6763-4108-922c-03bd86b9178d");
private static readonly Guid ActiveTemplateStatus = Guid.Parse("da462422-13b2-45cc-a175-910a225f6fc8");
private static readonly string Collection = "ExpensesModificationLog";
public ExpensesService(
IDbContextFactory<ApplicationDbContext> dbContextFactory,
@ -145,6 +152,7 @@ namespace Marco.Pms.Services.Service
.Include(e => e.PaymentMode)
.Include(e => e.ExpenseCategory)
.Include(e => e.Status)
.Include(e => e.Currency)
.Where(e => e.TenantId == tenantId); // Always filter by TenantId first.
if (cacheList == null)
@ -315,6 +323,7 @@ namespace Marco.Pms.Services.Service
.Include(e => e.PaymentMode)
.Include(e => e.ExpenseCategory)
.Include(e => e.Status)
.Include(e => e.Currency)
.AsNoTracking().FirstOrDefaultAsync(e => (e.Id == id || (e.UIDPrefix + "/" + e.UIDPostfix.ToString().PadLeft(5, '0')) == expenseUId) && e.TenantId == tenantId);
if (expense == null)
@ -553,6 +562,7 @@ namespace Marco.Pms.Services.Service
// 3. Entity Creation
var expense = _mapper.Map<Expenses>(dto);
expense.CurrencyId = dto.CurrencyId ?? Guid.Parse("78e96e4a-7ce0-4164-ae3a-c833ad45ec2c");
expense.UIDPostfix = uIDPostfix;
expense.UIDPrefix = uIDPrefix;
expense.CreatedById = loggedInEmployee.Id;
@ -626,6 +636,7 @@ namespace Marco.Pms.Services.Service
.Include(e => e.PaidBy).ThenInclude(e => e!.JobRole)
.Include(e => e.PaymentMode)
.Include(e => e.Status)
.Include(e => e.Currency)
.Include(e => e.CreatedBy)
.Include(e => e.ReviewedBy)
.Include(e => e.ApprovedBy)
@ -791,7 +802,21 @@ namespace Marco.Pms.Services.Service
}
else if (model.StatusId == Processed)
{
var totalAmount = model.BaseAmount + model.TaxAmount;
if (!totalAmount.HasValue || totalAmount != expense.Amount)
{
// Log the mismatch error with relevant details
_logger.LogWarning("Payment amount mismatch: calculated totalAmount = {TotalAmount}, expected Amount = {ExpectedAmount}", totalAmount ?? 0, expense.Amount);
// Return a structured error response indicating the amount discrepancy
return ApiResponse<object>.ErrorResponse(
"Payment amount validation failed.",
$"The sum of the base amount and tax amount ({totalAmount}) does not match the expected expense amount ({expense.Amount}).",
400);
}
expense.ProcessedById = loggedInEmployee.Id;
expense.BaseAmount = model.BaseAmount;
expense.TaxAmount = model.TaxAmount;
}
// 7. Add Reimbursement if applicable
@ -1821,10 +1846,24 @@ namespace Marco.Pms.Services.Service
// 7. Add Reimbursement if applicable
if (model.StatusId == Processed)
{
var totalAmount = model.BaseAmount + model.TaxAmount;
if (totalAmount.HasValue || totalAmount != paymentRequest.Amount)
{
// Log the mismatch error with relevant details
_logger.LogWarning("Payment amount mismatch: calculated totalAmount = {TotalAmount}, expected Amount = {ExpectedAmount}", totalAmount ?? 0, paymentRequest.Amount);
// Return a structured error response indicating the amount discrepancy
return ApiResponse<object>.ErrorResponse(
"Payment amount validation failed.",
$"The sum of the base amount and tax amount ({totalAmount}) does not match the expected payment request amount ({paymentRequest.Amount}).",
400);
}
paymentRequest.PaidAt = model.PaidAt;
paymentRequest.PaidById = model.PaidById;
paymentRequest.PaidTransactionId = model.PaidTransactionId;
paymentRequest.TDSPercentage = model.TDSPercentage;
paymentRequest.BaseAmount = model.BaseAmount;
paymentRequest.TaxAmount = model.TaxAmount;
var lastTransaction = await _context.AdvancePaymentTransactions.OrderByDescending(apt => apt.CreatedAt).FirstOrDefaultAsync(apt => apt.TenantId == tenantId);
double lastBalance = 0;
@ -2012,6 +2051,8 @@ namespace Marco.Pms.Services.Service
GSTNumber = model.GSTNumber,
SupplerName = paymentRequest.Payee,
Amount = paymentRequest.Amount,
BaseAmount = paymentRequest.BaseAmount,
TaxAmount = paymentRequest.TaxAmount,
TDSPercentage = paymentRequest.TDSPercentage,
PaymentRequestId = paymentRequest.Id,
StatusId = Processed,
@ -2465,7 +2506,6 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("End GetRecurringPaymentListAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id);
}
}
public async Task<ApiResponse<object>> CreateRecurringPaymentAsync(RecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start CreateRecurringPaymentAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}", loggedInEmployee.Id, tenantId);
@ -2578,6 +2618,134 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("End CreateRecurringPaymentAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id);
}
}
public async Task<ApiResponse<object>> PaymentRequestConversionAsync(List<Guid> recurringTemplateIds, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start PaymentRequestConversionAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId} with RecurringTemplateIds: {RecurringTemplateIds}",
loggedInEmployee.Id, tenantId, recurringTemplateIds);
// SuperAdmin check - restrict access only to specific user
var superAdminId = Guid.Parse("08dd8b35-d98b-44f1-896d-12aec3f035aa");
if (loggedInEmployee.Id != superAdminId)
{
_logger.LogWarning("Access denied for EmployeeId: {EmployeeId}. Only super admin can perform this operation.", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission to access this function", 403);
}
// Get active recurring payments matching the provided IDs and tenant
var recurringPayments = await _context.RecurringPayments
.AsNoTracking()
.Where(rp => recurringTemplateIds.Contains(rp.Id)
&& rp.IsActive
&& rp.StatusId == ActiveTemplateStatus
&& rp.TenantId == tenantId)
.ToListAsync();
if (!recurringPayments.Any())
{
_logger.LogWarning("No active recurring payments found for TenantId: {TenantId} and given IDs.", tenantId);
return ApiResponse<object>.ErrorResponse("Recurring template not found", "Recurring template not found", 404);
}
var updatedRecurringPayments = new List<RecurringPayment>();
var paymentRequests = new List<PaymentRequest>();
// Generate UID prefix for payment requests for this period (month/year)
string uIDPrefix = $"PR/{DateTime.Now:MM.yy}";
// Get last generated payment request for UID postfixing (to maintain unique numbering)
var lastPR = await _context.PaymentRequests
.Where(pr => pr.UIDPrefix == uIDPrefix)
.OrderByDescending(pr => pr.UIDPostfix)
.FirstOrDefaultAsync();
int uIDPostfix = lastPR == null ? 1 : lastPR.UIDPostfix + 1;
foreach (var recurringPayment in recurringPayments)
{
// Check if recurring payment is applicable for generating a new payment request
var isApplicable = IsRecurringApplicable(
recurringPayment.NumberOfIteration,
recurringPayment.Frequency,
recurringPayment.StrikeDate.Date,
recurringPayment.LatestPRGeneratedAt);
if (isApplicable)
{
// Update latest generated date to today (UTC)
recurringPayment.LatestPRGeneratedAt = DateTime.UtcNow.Date;
updatedRecurringPayments.Add(recurringPayment);
// Create new payment request mapped from recurring payment data
var newPaymentRequest = new PaymentRequest
{
Id = Guid.NewGuid(),
Title = recurringPayment.Title,
Description = recurringPayment.Description,
UIDPrefix = uIDPrefix,
UIDPostfix = uIDPostfix,
Payee = recurringPayment.Payee,
IsAdvancePayment = false,
CurrencyId = recurringPayment.CurrencyId,
Amount = recurringPayment.Amount,
DueDate = DateTime.UtcNow.AddDays(recurringPayment.PaymentBufferDays),
ProjectId = recurringPayment.ProjectId,
RecurringPaymentId = recurringPayment.Id,
ExpenseCategoryId = recurringPayment.ExpenseCategoryId,
ExpenseStatusId = Review,
IsExpenseCreated = false,
CreatedAt = DateTime.UtcNow,
CreatedById = loggedInEmployee.Id,
IsActive = true,
TenantId = recurringPayment.TenantId
};
paymentRequests.Add(newPaymentRequest);
uIDPostfix++;
}
}
if (!updatedRecurringPayments.Any())
{
_logger.LogWarning("No applicable recurring payments for conversion found for TenantId: {TenantId}.", tenantId);
return ApiResponse<object>.ErrorResponse("No applicable recurring templates found to convert", "No applicable recurring templates found", 404);
}
try
{
// Update recurring payments with latest generated dates
_context.RecurringPayments.UpdateRange(updatedRecurringPayments);
// Add newly created payment requests
if (paymentRequests.Any())
{
_context.PaymentRequests.AddRange(paymentRequests);
}
await _context.SaveChangesAsync();
_logger.LogInfo("{Count} payment requests created successfully from recurring payments by EmployeeId: {EmployeeId} for TenantId: {TenantId}",
paymentRequests.Count, loggedInEmployee.Id, tenantId);
return ApiResponse<object>.SuccessResponse(recurringTemplateIds, $"{paymentRequests.Count} conversion(s) to payment request completed successfully.", 201);
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, "Database error during PaymentRequestConversionAsync for TenantId: {TenantId}, EmployeeId: {EmployeeId}: {Message}. Inner exception: {InnerException}",
tenantId, loggedInEmployee.Id, ex.Message, ex.InnerException?.Message ?? "");
return ApiResponse<object>.ErrorResponse("Database error occurred", ExceptionMapper(ex), 500);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error during PaymentRequestConversionAsync for TenantId: {TenantId}, EmployeeId: {EmployeeId}: {Message}",
tenantId, loggedInEmployee.Id, ex.Message);
return ApiResponse<object>.ErrorResponse("An unexpected error occurred", ex.Message, 500);
}
finally
{
_logger.LogInfo("End PaymentRequestConversionAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}", loggedInEmployee.Id, tenantId);
}
}
public async Task<ApiResponse<object>> EditRecurringPaymentAsync(Guid id, RecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start EditRecurringPaymentAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}, RecurringPaymentId: {RecurringPaymentId}",
@ -2922,7 +3090,6 @@ namespace Marco.Pms.Services.Service
return response;
}
private ExpensesFilter? TryDeserializeFilter(string? filter)
{
if (string.IsNullOrWhiteSpace(filter))
@ -3242,6 +3409,83 @@ namespace Marco.Pms.Services.Service
await Task.WhenAll(attachmentTask, documentsTask);
}
private static bool IsRecurringApplicable(int numberOfIteration, PLAN_FREQUENCY frequency, DateTime strikeDate, DateTime? latestPRGeneratedAt)
{
List<DateTime> dates = new List<DateTime>();
DateTime currentDate = strikeDate;
DateTime endDate = DateTime.UtcNow.Date;
switch (frequency)
{
case PLAN_FREQUENCY.MONTHLY:
while (currentDate <= endDate)
{
dates.Add(currentDate);
currentDate = currentDate.AddMonths(1);
}
break;
case PLAN_FREQUENCY.QUARTERLY:
while (currentDate <= endDate)
{
dates.Add(currentDate);
currentDate = currentDate.AddMonths(3);
}
break;
case PLAN_FREQUENCY.HALF_YEARLY:
while (currentDate <= endDate)
{
dates.Add(currentDate);
currentDate = currentDate.AddMonths(6);
}
break;
case PLAN_FREQUENCY.YEARLY:
while (currentDate <= endDate)
{
dates.Add(currentDate);
currentDate = currentDate.AddYears(1);
}
break;
case PLAN_FREQUENCY.DAILY:
while (currentDate <= endDate)
{
dates.Add(currentDate);
currentDate = currentDate.AddDays(1);
}
break;
case PLAN_FREQUENCY.WEEKLY:
while (currentDate <= endDate)
{
dates.Add(currentDate);
currentDate = currentDate.AddDays(7);
}
break;
}
if (!dates.Any() || dates.Count > numberOfIteration)
{
return false;
}
if (dates.Last() != endDate)
{
return false;
}
if (latestPRGeneratedAt.HasValue && latestPRGeneratedAt.Value == endDate)
{
return false;
}
return true;
}
#endregion
}
}
// Ensure the endDate is included if it matches the frequency
//if (dates.Last() != endDate && (endDate - strikeDate).Ticks % frequency.Ticks == 0)
//{
// dates.Add(endDate);
//}

View File

@ -32,6 +32,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<object>> GetRecurringPaymentListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> CreateRecurringPaymentAsync(RecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> EditRecurringPaymentAsync(Guid id, RecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> PaymentRequestConversionAsync(List<Guid> RecurringTemplateIds, Employee loggedInEmployee, Guid tenantId);
#endregion
#region =================================================================== Advance Payment Functions ===================================================================