Added BaseAmount In Expense And PaymentRequest Tables
This commit is contained in:
parent
411e63db1b
commit
a758fb8c35
File diff suppressed because one or more lines are too long
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
namespace Marco.Pms.Model.Dtos.Expenses
|
||||
{
|
||||
public class PaymentRequestConversionDto
|
||||
{
|
||||
public required List<Guid> RecurringTemplateIds { get; set; }
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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>();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
//}
|
||||
@ -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 ===================================================================
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user