From 2b19890b5317b3a088731aad985c6e446984d3a3 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 13 Oct 2025 20:44:38 +0530 Subject: [PATCH] Added the create payment received invoice API --- .../Collection/ReceivedInvoicePaymentDto.cs | 11 +++ .../Collection/ReceivedInvoicePaymentVM.cs | 16 ++++ .../Controllers/CollectionController.cs | 96 ++++++++++++++++++- .../MappingProfiles/MappingProfile.cs | 3 + 4 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 Marco.Pms.Model/Dtos/Collection/ReceivedInvoicePaymentDto.cs create mode 100644 Marco.Pms.Model/ViewModels/Collection/ReceivedInvoicePaymentVM.cs diff --git a/Marco.Pms.Model/Dtos/Collection/ReceivedInvoicePaymentDto.cs b/Marco.Pms.Model/Dtos/Collection/ReceivedInvoicePaymentDto.cs new file mode 100644 index 0000000..51d69a3 --- /dev/null +++ b/Marco.Pms.Model/Dtos/Collection/ReceivedInvoicePaymentDto.cs @@ -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; } + } +} diff --git a/Marco.Pms.Model/ViewModels/Collection/ReceivedInvoicePaymentVM.cs b/Marco.Pms.Model/ViewModels/Collection/ReceivedInvoicePaymentVM.cs new file mode 100644 index 0000000..552b6f3 --- /dev/null +++ b/Marco.Pms.Model/ViewModels/Collection/ReceivedInvoicePaymentVM.cs @@ -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; } + } +} diff --git a/Marco.Pms.Services/Controllers/CollectionController.cs b/Marco.Pms.Services/Controllers/CollectionController.cs index 6d7537a..f4f908b 100644 --- a/Marco.Pms.Services/Controllers/CollectionController.cs +++ b/Marco.Pms.Services/Controllers/CollectionController.cs @@ -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 CreateInvoiceAsync(InvoiceDto model) + public async Task 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.SuccessResponse(response, "Invoice Created Successfully", 201)); } + /// + /// Creates a new received invoice payment record after validating business rules. + /// + /// The received invoice payment data transfer object containing payment details. + /// An action result containing the created payment view model or error response. + + [HttpPost("invoice/payment/received")] + public async Task CreateReceivedInvoicePaymentAsync([FromBody] ReceivedInvoicePaymentDto model) + { + // Validate input model + if (model == null) + { + _logger.LogWarning("Received invoice payment creation request with null model"); + return BadRequest(ApiResponse.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.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.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.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.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(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(receivedInvoicePayment); + + _logger.LogInfo("Successfully created received payment {PaymentId} for invoice {InvoiceId}", + receivedInvoicePayment.Id, model.InvoiceId); + + return Ok(ApiResponse.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.ErrorResponse( + "Internal server error", + "An unexpected error occurred while processing the request", 500)); + } + } } } diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index c8bbc67..fecece5 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -261,6 +261,9 @@ namespace Marco.Pms.Services.MappingProfiles #region ======================================================= Collection ======================================================= CreateMap(); CreateMap(); + + CreateMap(); + CreateMap(); #endregion #region ======================================================= Master =======================================================