Added EndDate and NextStrikeDate in recurring payment models and view models
This commit is contained in:
parent
85ca0a4cb1
commit
ac711c7254
File diff suppressed because one or more lines are too long
@ -0,0 +1,50 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Marco.Pms.DataAccess.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Added_NextStrikeDate_And_EndDate_In_RecurringPayment_Table : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "NumberOfIteration",
|
||||||
|
table: "RecurringPayments");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "EndDate",
|
||||||
|
table: "RecurringPayments",
|
||||||
|
type: "datetime(6)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(2025, 11, 10, 10, 17, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "NextStrikeDate",
|
||||||
|
table: "RecurringPayments",
|
||||||
|
type: "datetime(6)",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "EndDate",
|
||||||
|
table: "RecurringPayments");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "NextStrikeDate",
|
||||||
|
table: "RecurringPayments");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "NumberOfIteration",
|
||||||
|
table: "RecurringPayments",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2854,6 +2854,9 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("EndDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
b.Property<Guid?>("ExpenseCategoryId")
|
b.Property<Guid?>("ExpenseCategoryId")
|
||||||
.HasColumnType("char(36)");
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
@ -2869,13 +2872,13 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
b.Property<DateTime?>("LatestPRGeneratedAt")
|
b.Property<DateTime?>("LatestPRGeneratedAt")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("NextStrikeDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
b.Property<string>("NotifyTo")
|
b.Property<string>("NotifyTo")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<int>("NumberOfIteration")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Payee")
|
b.Property<string>("Payee")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|||||||
@ -13,7 +13,7 @@ namespace Marco.Pms.Model.Dtos.Expenses
|
|||||||
public required DateTime StrikeDate { get; set; }
|
public required DateTime StrikeDate { get; set; }
|
||||||
public Guid? ProjectId { get; set; }
|
public Guid? ProjectId { get; set; }
|
||||||
public required int PaymentBufferDays { get; set; }
|
public required int PaymentBufferDays { get; set; }
|
||||||
public required int NumberOfIteration { get; set; }
|
public required DateTime EndDate { get; set; }
|
||||||
public required Guid ExpenseCategoryId { get; set; }
|
public required Guid ExpenseCategoryId { get; set; }
|
||||||
public required Guid StatusId { get; set; }
|
public required Guid StatusId { get; set; }
|
||||||
public required PLAN_FREQUENCY Frequency { get; set; }
|
public required PLAN_FREQUENCY Frequency { get; set; }
|
||||||
|
|||||||
@ -13,7 +13,7 @@ namespace Marco.Pms.Model.Dtos.Expenses
|
|||||||
public required double Amount { get; set; }
|
public required double Amount { get; set; }
|
||||||
public Guid? ProjectId { get; set; }
|
public Guid? ProjectId { get; set; }
|
||||||
public required int PaymentBufferDays { get; set; }
|
public required int PaymentBufferDays { get; set; }
|
||||||
public required int NumberOfIteration { get; set; }
|
public required DateTime EndDate { get; set; }
|
||||||
public required Guid ExpenseCategoryId { get; set; }
|
public required Guid ExpenseCategoryId { get; set; }
|
||||||
public required Guid StatusId { get; set; }
|
public required Guid StatusId { get; set; }
|
||||||
public required PLAN_FREQUENCY Frequency { get; set; }
|
public required PLAN_FREQUENCY Frequency { get; set; }
|
||||||
|
|||||||
@ -25,14 +25,15 @@ namespace Marco.Pms.Model.Expenses
|
|||||||
public CurrencyMaster? Currency { get; set; }
|
public CurrencyMaster? Currency { get; set; }
|
||||||
public double Amount { get; set; }
|
public double Amount { get; set; }
|
||||||
public DateTime StrikeDate { get; set; }
|
public DateTime StrikeDate { get; set; }
|
||||||
|
public DateTime EndDate { get; set; }
|
||||||
public DateTime? LatestPRGeneratedAt { get; set; }
|
public DateTime? LatestPRGeneratedAt { get; set; }
|
||||||
|
public DateTime? NextStrikeDate { get; set; }
|
||||||
public Guid? ProjectId { get; set; }
|
public Guid? ProjectId { get; set; }
|
||||||
|
|
||||||
[ValidateNever]
|
[ValidateNever]
|
||||||
[ForeignKey("ProjectId")]
|
[ForeignKey("ProjectId")]
|
||||||
public Project? Project { get; set; }
|
public Project? Project { get; set; }
|
||||||
public int PaymentBufferDays { get; set; }
|
public int PaymentBufferDays { get; set; }
|
||||||
public int NumberOfIteration { get; set; }
|
|
||||||
public Guid? ExpenseCategoryId { get; set; }
|
public Guid? ExpenseCategoryId { get; set; }
|
||||||
|
|
||||||
[ValidateNever]
|
[ValidateNever]
|
||||||
|
|||||||
@ -21,7 +21,8 @@ namespace Marco.Pms.Model.ViewModels.Expenses
|
|||||||
public DateTime? LatestPRGeneratedAt { get; set; }
|
public DateTime? LatestPRGeneratedAt { get; set; }
|
||||||
public BasicProjectVM? Project { get; set; }
|
public BasicProjectVM? Project { get; set; }
|
||||||
public int PaymentBufferDays { get; set; }
|
public int PaymentBufferDays { get; set; }
|
||||||
public int NumberOfIteration { get; set; }
|
public DateTime EndDate { get; set; }
|
||||||
|
public DateTime? NextStrikeDate { get; set; }
|
||||||
public List<BasicPaymentRequestVM>? PaymentRequests { get; set; }
|
public List<BasicPaymentRequestVM>? PaymentRequests { get; set; }
|
||||||
public ExpenseCategoryMasterVM? ExpenseCategory { get; set; }
|
public ExpenseCategoryMasterVM? ExpenseCategory { get; set; }
|
||||||
public RecurringPaymentStatus? Status { get; set; }
|
public RecurringPaymentStatus? Status { get; set; }
|
||||||
|
|||||||
@ -21,7 +21,8 @@ namespace Marco.Pms.Model.ViewModels.Expenses
|
|||||||
public DateTime? LatestPRGeneratedAt { get; set; }
|
public DateTime? LatestPRGeneratedAt { get; set; }
|
||||||
public BasicProjectVM? Project { get; set; }
|
public BasicProjectVM? Project { get; set; }
|
||||||
public int PaymentBufferDays { get; set; }
|
public int PaymentBufferDays { get; set; }
|
||||||
public int NumberOfIteration { get; set; }
|
public DateTime? NextStrikeDate { get; set; }
|
||||||
|
public DateTime EndDate { get; set; }
|
||||||
public ExpenseCategoryMasterVM? ExpenseCategory { get; set; }
|
public ExpenseCategoryMasterVM? ExpenseCategory { get; set; }
|
||||||
public RecurringPaymentStatus? Status { get; set; }
|
public RecurringPaymentStatus? Status { get; set; }
|
||||||
public PLAN_FREQUENCY Frequency { get; set; }
|
public PLAN_FREQUENCY Frequency { get; set; }
|
||||||
|
|||||||
@ -283,7 +283,10 @@ namespace Marco.Pms.Services.MappingProfiles
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ======================================================= Recurring Request =======================================================
|
#region ======================================================= Recurring Request =======================================================
|
||||||
CreateMap<CreateRecurringTemplateDto, RecurringPayment>();
|
CreateMap<CreateRecurringTemplateDto, RecurringPayment>()
|
||||||
|
.ForMember(
|
||||||
|
dest => dest.NextStrikeDate,
|
||||||
|
opt => opt.MapFrom(src => src.StrikeDate.Date));
|
||||||
CreateMap<UpdateRecurringTemplateDto, RecurringPayment>();
|
CreateMap<UpdateRecurringTemplateDto, RecurringPayment>();
|
||||||
CreateMap<RecurringPayment, RecurringPaymentVM>();
|
CreateMap<RecurringPayment, RecurringPaymentVM>();
|
||||||
CreateMap<RecurringPayment, RecurringPaymentDetailsVM>()
|
CreateMap<RecurringPayment, RecurringPaymentDetailsVM>()
|
||||||
|
|||||||
@ -56,6 +56,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
// Recurring payment status
|
// Recurring payment status
|
||||||
private static readonly Guid ActiveTemplateStatus = Guid.Parse("da462422-13b2-45cc-a175-910a225f6fc8");
|
private static readonly Guid ActiveTemplateStatus = Guid.Parse("da462422-13b2-45cc-a175-910a225f6fc8");
|
||||||
|
private static readonly Guid CompletedTemplateStatus = Guid.Parse("306856fb-5655-42eb-bf8b-808bb5e84725");
|
||||||
|
|
||||||
|
|
||||||
private static readonly string Collection = "ExpensesModificationLog";
|
private static readonly string Collection = "ExpensesModificationLog";
|
||||||
@ -2880,6 +2881,14 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Validate that EndDate is not earlier than today's UTC date
|
||||||
|
if (DateTime.UtcNow.Date > model.EndDate)
|
||||||
|
{
|
||||||
|
// Log a warning for an invalid EndDate value
|
||||||
|
_logger.LogWarning("Date validation error: EndDate ({EndDate}) cannot be earlier than today's date ({Today}).",
|
||||||
|
model.EndDate, DateTime.UtcNow.Date);
|
||||||
|
return ApiResponse<object>.ErrorResponse("End date cannot be earlier than today.", "End date cannot be earlier than today.", 400);
|
||||||
|
}
|
||||||
// Check if user has permission to create recurring payment templates
|
// Check if user has permission to create recurring payment templates
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
@ -3003,6 +3012,9 @@ namespace Marco.Pms.Services.Service
|
|||||||
var recurringPayments = await _context.RecurringPayments
|
var recurringPayments = await _context.RecurringPayments
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(rp => recurringTemplateIds.Contains(rp.Id)
|
.Where(rp => recurringTemplateIds.Contains(rp.Id)
|
||||||
|
&& rp.NextStrikeDate.HasValue
|
||||||
|
&& rp.NextStrikeDate.Value.Date == DateTime.UtcNow.Date
|
||||||
|
&& rp.EndDate.Date >= DateTime.UtcNow.Date
|
||||||
&& rp.IsActive
|
&& rp.IsActive
|
||||||
&& rp.StatusId == ActiveTemplateStatus
|
&& rp.StatusId == ActiveTemplateStatus
|
||||||
&& rp.TenantId == tenantId)
|
&& rp.TenantId == tenantId)
|
||||||
@ -3030,17 +3042,15 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
foreach (var recurringPayment in recurringPayments)
|
foreach (var recurringPayment in recurringPayments)
|
||||||
{
|
{
|
||||||
// Check if recurring payment is applicable for generating a new payment request
|
if (recurringPayment.NextStrikeDate.HasValue)
|
||||||
var isApplicable = IsRecurringApplicable(
|
|
||||||
recurringPayment.NumberOfIteration,
|
|
||||||
recurringPayment.Frequency,
|
|
||||||
recurringPayment.StrikeDate.Date,
|
|
||||||
recurringPayment.LatestPRGeneratedAt);
|
|
||||||
|
|
||||||
if (isApplicable)
|
|
||||||
{
|
{
|
||||||
// Update latest generated date to today (UTC)
|
// Update latest generated date to today (UTC)
|
||||||
recurringPayment.LatestPRGeneratedAt = DateTime.UtcNow.Date;
|
recurringPayment.LatestPRGeneratedAt = DateTime.UtcNow.Date;
|
||||||
|
recurringPayment.NextStrikeDate = GetNextStrikeDate(recurringPayment.Frequency, recurringPayment.NextStrikeDate.Value, recurringPayment.EndDate);
|
||||||
|
if (recurringPayment.EndDate.Date < DateTime.UtcNow.Date)
|
||||||
|
{
|
||||||
|
recurringPayment.StatusId = CompletedTemplateStatus;
|
||||||
|
}
|
||||||
updatedRecurringPayments.Add(recurringPayment);
|
updatedRecurringPayments.Add(recurringPayment);
|
||||||
|
|
||||||
// Create new payment request mapped from recurring payment data
|
// Create new payment request mapped from recurring payment data
|
||||||
@ -3091,10 +3101,12 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var emails = updatedRecurringPayments.Select(rp => rp.NotifyTo.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).ToList();
|
||||||
|
|
||||||
_logger.LogInfo("{Count} payment requests created successfully from recurring payments by EmployeeId: {EmployeeId} for TenantId: {TenantId}",
|
_logger.LogInfo("{Count} payment requests created successfully from recurring payments by EmployeeId: {EmployeeId} for TenantId: {TenantId}",
|
||||||
paymentRequests.Count, loggedInEmployee.Id, tenantId);
|
paymentRequests.Count, loggedInEmployee.Id, tenantId);
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(recurringTemplateIds, $"{paymentRequests.Count} conversion(s) to payment request completed successfully.", 201);
|
return ApiResponse<object>.SuccessResponse(emails, $"{paymentRequests.Count} conversion(s) to payment request completed successfully.", 201);
|
||||||
}
|
}
|
||||||
catch (DbUpdateException ex)
|
catch (DbUpdateException ex)
|
||||||
{
|
{
|
||||||
@ -3132,6 +3144,15 @@ namespace Marco.Pms.Services.Service
|
|||||||
"The employee Id in the path does not match the Id in the request body.", 400);
|
"The employee Id in the path does not match the Id in the request body.", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate that EndDate is not earlier than today's UTC date
|
||||||
|
if (DateTime.UtcNow.Date > model.EndDate)
|
||||||
|
{
|
||||||
|
// Log a warning for an invalid EndDate value
|
||||||
|
_logger.LogWarning("Date validation error: EndDate ({EndDate}) cannot be earlier than today's date ({Today}).",
|
||||||
|
model.EndDate, DateTime.UtcNow.Date);
|
||||||
|
return ApiResponse<object>.ErrorResponse("End date cannot be earlier than today.", "End date cannot be earlier than today.", 400);
|
||||||
|
}
|
||||||
|
|
||||||
// Permission check for managing recurring payments
|
// Permission check for managing recurring payments
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
@ -3860,6 +3881,41 @@ namespace Marco.Pms.Services.Service
|
|||||||
return null; // Validation successful, no error
|
return null; // Validation successful, no error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DateTime? GetNextStrikeDate(PLAN_FREQUENCY frequency, DateTime currentStrikeDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
var nextStrikeDate = currentStrikeDate.AddDays(1);
|
||||||
|
switch (frequency)
|
||||||
|
{
|
||||||
|
case PLAN_FREQUENCY.MONTHLY:
|
||||||
|
nextStrikeDate = currentStrikeDate.AddMonths(1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PLAN_FREQUENCY.QUARTERLY:
|
||||||
|
nextStrikeDate = currentStrikeDate.AddMonths(3);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PLAN_FREQUENCY.HALF_YEARLY:
|
||||||
|
nextStrikeDate = currentStrikeDate.AddMonths(6);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PLAN_FREQUENCY.YEARLY:
|
||||||
|
nextStrikeDate = currentStrikeDate.AddYears(1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PLAN_FREQUENCY.DAILY:
|
||||||
|
nextStrikeDate = currentStrikeDate.AddDays(1);
|
||||||
|
break;
|
||||||
|
case PLAN_FREQUENCY.WEEKLY:
|
||||||
|
nextStrikeDate = currentStrikeDate.AddDays(7);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (endDate < nextStrikeDate)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return nextStrikeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user