Added the create payment received invoice API

This commit is contained in:
ashutosh.nehete 2025-10-13 20:44:38 +05:30
parent 5ff87cd870
commit 2b19890b53
4 changed files with 122 additions and 4 deletions

View File

@ -0,0 +1,11 @@
namespace Marco.Pms.Model.Dtos.Collection
{
public class ReceivedInvoicePaymentDto
{
public Guid? Id { get; set; }
public required Guid InvoiceId { get; set; }
public required DateTime PaymentReceivedDate { get; set; }
public required string TransactionId { get; set; } = default!;
public required double Amount { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using Marco.Pms.Model.ViewModels.Activities;
namespace Marco.Pms.Model.ViewModels.Collection
{
public class ReceivedInvoicePaymentVM
{
public Guid Id { get; set; }
public Guid InvoiceId { get; set; }
public DateTime PaymentReceivedDate { get; set; }
public string TransactionId { get; set; } = default!;
public double Amount { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; }
public BasicEmployeeVM? CreatedBy { get; set; }
}
}

View File

@ -13,6 +13,7 @@ using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
namespace Marco.Pms.Services.Controllers
{
@ -52,10 +53,10 @@ namespace Marco.Pms.Services.Controllers
"Fetching invoice list: Page {PageNumber}, Size {PageSize}, Active={IsActive}, PendingOnly={IsPending}, Search='{SearchString}', From={From}, To={To}",
pageNumber, pageSize, isActive, isPending, searchString ?? "", fromDate?.Date ?? DateTime.MinValue, toDate?.Date ?? DateTime.MaxValue);
await using var context = await _dbContextFactory.CreateDbContextAsync();
await using var _context = await _dbContextFactory.CreateDbContextAsync();
// Build base query with required includes and no tracking
var invoicesQuery = context.Invoices
var invoicesQuery = _context.Invoices
.Include(i => i.Project)
.Include(i => i.CreatedBy).ThenInclude(e => e!.JobRole)
.Include(i => i.UpdatedBy).ThenInclude(e => e!.JobRole)
@ -104,7 +105,7 @@ namespace Marco.Pms.Services.Controllers
// Fetch all related payment data in a single query
var invoiceIds = invoices.Select(i => i.Id).ToList();
var paymentGroups = await context.ReceivedInvoicePayments
var paymentGroups = await _context.ReceivedInvoicePayments
.AsNoTracking()
.Where(rip => invoiceIds.Contains(rip.InvoiceId) && rip.TenantId == tenantId)
.GroupBy(rip => rip.InvoiceId)
@ -149,7 +150,7 @@ namespace Marco.Pms.Services.Controllers
[HttpPost("invoice/create")]
public async Task<IActionResult> CreateInvoiceAsync(InvoiceDto model)
public async Task<IActionResult> CreateInvoiceAsync([FromBody] InvoiceDto model)
{
await using var _context = await _dbContextFactory.CreateDbContextAsync();
//using var scope = _serviceScopeFactory.CreateScope();
@ -309,6 +310,93 @@ namespace Marco.Pms.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse(response, "Invoice Created Successfully", 201));
}
/// <summary>
/// Creates a new received invoice payment record after validating business rules.
/// </summary>
/// <param name="model">The received invoice payment data transfer object containing payment details.</param>
/// <returns>An action result containing the created payment view model or error response.</returns>
[HttpPost("invoice/payment/received")]
public async Task<IActionResult> CreateReceivedInvoicePaymentAsync([FromBody] ReceivedInvoicePaymentDto model)
{
// Validate input model
if (model == null)
{
_logger.LogWarning("Received invoice payment creation request with null model");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid model", "Request payload cannot be null", 400));
}
await using var _context = await _dbContextFactory.CreateDbContextAsync();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Retrieve invoice with tenant isolation and no tracking for read-only access
var invoice = await _context.Invoices
.AsNoTracking()
.FirstOrDefaultAsync(i => i.Id == model.InvoiceId && i.TenantId == tenantId);
if (invoice == null)
{
_logger.LogWarning("Invoice not found for ID {InvoiceId} and TenantId {TenantId}", model.InvoiceId, tenantId);
return NotFound(ApiResponse<object>.ErrorResponse("Invoice not found", "The specified invoice does not exist", 404));
}
// Check if invoice is already marked as completed
if (invoice.MarkAsCompleted)
{
_logger.LogWarning("Attempt to add payment to completed invoice {InvoiceId}", model.InvoiceId);
return BadRequest(ApiResponse<object>.ErrorResponse(
"Cannot add received payment to completed invoice",
"Payments cannot be added to invoices that are already marked as completed", 400));
}
// Validate payment received date is not in the future
if (model.PaymentReceivedDate.Date > DateTime.UtcNow.Date)
{
_logger.LogWarning("Future payment date {PaymentReceivedDate} provided for invoice {InvoiceId}",
model.PaymentReceivedDate, model.InvoiceId);
return BadRequest(ApiResponse<object>.ErrorResponse(
"Payment received date cannot be in the future",
"The payment received date must not be later than the current date", 400));
}
// Validate client submitted date is not later than payment received date
if (invoice.ClientSubmitedDate.Date > model.PaymentReceivedDate.Date)
{
_logger.LogWarning("Client submitted date {ClientSubmitedDate} is later than payment received date {PaymentReceivedDate} for invoice {InvoiceId}",
invoice.ClientSubmitedDate, model.PaymentReceivedDate, model.InvoiceId);
return BadRequest(ApiResponse<object>.ErrorResponse(
"Client submitted date is later than payment received date",
"The client submission date cannot be later than the payment received date", 400));
}
try
{
// Map DTO to entity and set creation metadata
var receivedInvoicePayment = _mapper.Map<ReceivedInvoicePayment>(model);
receivedInvoicePayment.CreatedAt = DateTime.UtcNow;
receivedInvoicePayment.CreatedById = loggedInEmployee.Id;
receivedInvoicePayment.TenantId = tenantId;
// Add new payment record and save changes
_context.ReceivedInvoicePayments.Add(receivedInvoicePayment);
await _context.SaveChangesAsync();
// Map entity to view model for response
var response = _mapper.Map<ReceivedInvoicePaymentVM>(receivedInvoicePayment);
_logger.LogInfo("Successfully created received payment {PaymentId} for invoice {InvoiceId}",
receivedInvoicePayment.Id, model.InvoiceId);
return Ok(ApiResponse<object>.SuccessResponse(response, "Payment invoice received successfully", 201));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while creating received payment for invoice {InvoiceId}", model.InvoiceId);
return StatusCode(500, ApiResponse<object>.ErrorResponse(
"Internal server error",
"An unexpected error occurred while processing the request", 500));
}
}
}
}

View File

@ -261,6 +261,9 @@ namespace Marco.Pms.Services.MappingProfiles
#region ======================================================= Collection =======================================================
CreateMap<InvoiceDto, Invoice>();
CreateMap<Invoice, InvoiceListVM>();
CreateMap<ReceivedInvoicePaymentDto, ReceivedInvoicePayment>();
CreateMap<ReceivedInvoicePayment, ReceivedInvoicePaymentVM>();
#endregion
#region ======================================================= Master =======================================================