Added the update invoice API

This commit is contained in:
ashutosh.nehete 2025-10-14 14:24:59 +05:30
parent 47bb49fac6
commit 5a9b06cca6

View File

@ -1,8 +1,10 @@
using AutoMapper;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers.Utility;
using Marco.Pms.Model.Collection;
using Marco.Pms.Model.DocumentManager;
using Marco.Pms.Model.Dtos.Collection;
using Marco.Pms.Model.MongoDBModels.Utility;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Collection;
@ -45,6 +47,8 @@ namespace Marco.Pms.Services.Controllers
tenantId = userhelper.GetTenantId();
}
#region =================================================================== Get Functions ===================================================================
[HttpGet("invoice/list")]
public async Task<IActionResult> GetInvoiceListAsync([FromQuery] string? searchString, [FromQuery] DateTime? fromDate, [FromQuery] DateTime? toDate, [FromQuery] int pageSize = 20, [FromQuery] int pageNumber = 1
, [FromQuery] bool isActive = true, [FromQuery] bool isPending = false)
@ -221,6 +225,10 @@ namespace Marco.Pms.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse(response, "Invoice details fetched successfully", 200));
}
#endregion
#region =================================================================== Post Functions ===================================================================
[HttpPost("invoice/create")]
public async Task<IActionResult> CreateInvoiceAsync([FromBody] InvoiceDto model)
{
@ -533,6 +541,7 @@ namespace Marco.Pms.Services.Controllers
/// </summary>
/// <param name="model">DTO containing InvoiceId and Comment text.</param>
/// <returns>201 Created with comment details, or error codes for validation/invoice not found.</returns>
[HttpPost("invoice/add/comment")]
public async Task<IActionResult> AddCommentToInvoiceAsync([FromBody] InvoiceCommentDto model)
{
@ -588,6 +597,194 @@ namespace Marco.Pms.Services.Controllers
201));
}
#endregion
#region =================================================================== Put Functions ===================================================================
/// <summary>
/// Updates an existing invoice if it exists, has no payments, and the model is valid.
/// </summary>
/// <param name="id">The unique identifier of the invoice to update.</param>
/// <param name="model">The updated invoice data transfer object.</param>
/// <returns>Success response on update, or appropriate error if validation fails.</returns>
[HttpPut("invoice/edit/{id}")]
public async Task<IActionResult> UpdateInvoiceAsync(Guid id, [FromBody] InvoiceDto model)
{
// Validate route and model ID consistency
if (!model.Id.HasValue || id != model.Id)
{
_logger.LogWarning("Invoice ID mismatch: route ID {RouteId} does not match model ID {ModelId}", id, model.Id ?? Guid.Empty);
return BadRequest(ApiResponse<object>.ErrorResponse(
"Invalid invoice ID",
"The invoice ID in the URL does not match the ID in the request body.",
400));
}
await using var _context = await _dbContextFactory.CreateDbContextAsync();
using var scope = _serviceScopeFactory.CreateScope();
var _updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Retrieve the invoice with tenant isolation
var invoice = await _context.Invoices
.FirstOrDefaultAsync(i => i.Id == id && i.TenantId == tenantId);
if (invoice == null)
{
_logger.LogWarning("Invoice not found for ID {InvoiceId} and TenantId {TenantId}", id, tenantId);
return NotFound(ApiResponse<object>.ErrorResponse(
"Invoice not found",
"The specified invoice does not exist for this tenant.",
404));
}
// Fetch project
var project = await _context.Projects
.FirstOrDefaultAsync(p => p.Id == model.ProjectId && p.TenantId == tenantId);
if (project == null)
{
_logger.LogWarning("Project not found: ProjectId {ProjectId}, TenantId {TenantId}",
model.ProjectId, tenantId);
return NotFound(ApiResponse<object>.ErrorResponse("Project not found", "Project not found", 404));
}
// Prevent modification if any payment has already been received
var receivedPaymentExists = await _context.ReceivedInvoicePayments
.AnyAsync(rip => rip.InvoiceId == id && rip.TenantId == tenantId);
if (receivedPaymentExists)
{
_logger.LogWarning("Update blocked: Payment already received for InvoiceId {InvoiceId}, TenantId {TenantId}", id, tenantId);
return BadRequest(ApiResponse<object>.ErrorResponse(
"Invoice cannot be updated",
"This invoice has received payments and cannot be modified.",
400));
}
try
{
var invoiceStateBeforeChange = _updateLogHelper.EntityToBsonDocument(invoice);
// Map updated data to existing invoice entity
_mapper.Map(model, invoice);
invoice.UpdatedAt = DateTime.UtcNow;
invoice.UpdatedById = loggedInEmployee.Id;
// Handle attachment updates if provided
if (model.Attachments?.Any() ?? false)
{
var inactiveDocumentIds = model.Attachments
.Where(a => !a.IsActive && a.DocumentId.HasValue)
.Select(a => a.DocumentId!.Value)
.ToList();
var newAttachments = model.Attachments
.Where(a => a.IsActive && !string.IsNullOrWhiteSpace(a.Base64Data))
.ToList();
// Remove inactive attachments
if (inactiveDocumentIds.Any())
{
var existingInvoiceAttachments = await _context.InvoiceAttachments
.AsNoTracking()
.Where(ia => inactiveDocumentIds.Contains(ia.DocumentId) && ia.TenantId == tenantId)
.ToListAsync();
_context.InvoiceAttachments.RemoveRange(existingInvoiceAttachments);
_logger.LogInfo("Removed {Count} inactive attachments for InvoiceId {InvoiceId}", existingInvoiceAttachments.Count, id);
}
// Process and upload new attachments
if (newAttachments.Any())
{
var batchId = Guid.NewGuid();
var documents = new List<Document>();
var invoiceAttachments = new List<InvoiceAttachment>();
foreach (var attachment in newAttachments)
{
string base64Data = attachment.Base64Data?.Split(',').LastOrDefault() ?? string.Empty;
if (string.IsNullOrWhiteSpace(base64Data))
{
_logger.LogWarning("Base64 data missing for attachment: {FileName}", attachment.FileName ?? "Unknown");
return BadRequest(ApiResponse<object>.ErrorResponse(
"Invalid attachment data",
"Base64 data is missing or malformed for one or more attachments.",
400));
}
var contentType = _s3Service.GetContentTypeFromBase64(base64Data);
var fileName = _s3Service.GenerateFileName(contentType, tenantId, "invoice");
var objectKey = $"tenant-{tenantId}/Project/{model.ProjectId}/Invoice/{fileName}";
// Upload file to S3
await _s3Service.UploadFileAsync(base64Data, contentType, objectKey);
// Create document record
var document = new Document
{
Id = Guid.NewGuid(),
BatchId = batchId,
UploadedById = loggedInEmployee.Id,
FileName = attachment.FileName ?? fileName,
ContentType = contentType,
S3Key = objectKey,
FileSize = attachment.FileSize,
UploadedAt = DateTime.UtcNow,
TenantId = tenantId
};
documents.Add(document);
// Link document to invoice
var invoiceAttachment = new InvoiceAttachment
{
InvoiceId = invoice.Id,
DocumentId = document.Id,
TenantId = tenantId
};
invoiceAttachments.Add(invoiceAttachment);
}
_context.Documents.AddRange(documents);
_context.InvoiceAttachments.AddRange(invoiceAttachments);
_logger.LogInfo("Added {Count} new attachments to InvoiceId {InvoiceId}", invoiceAttachments.Count, id);
}
}
// Save all changes in a single transaction
await _context.SaveChangesAsync();
await _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
{
EntityId = invoice.Id.ToString(),
UpdatedById = loggedInEmployee.Id.ToString(),
OldObject = invoiceStateBeforeChange,
UpdatedAt = DateTime.UtcNow
}, "InvoiceModificationLog");
_logger.LogInfo("Invoice {InvoiceId} updated successfully by EmployeeId {EmployeeId}, TenantId {TenantId}",
invoice.Id, loggedInEmployee.Id, tenantId);
// Build response
var response = _mapper.Map<InvoiceListVM>(invoice);
response.Project = _mapper.Map<BasicProjectVM>(project);
response.UpdatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
response.BalanceAmount = response.BasicAmount + response.TaxAmount;
return Ok(ApiResponse<object>.SuccessResponse(response, "Invoice updated successfully", 200));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while updating InvoiceId {InvoiceId}, TenantId {TenantId}", id, tenantId);
return StatusCode(500, ApiResponse<object>.ErrorResponse(
"Internal server error",
"An unexpected error occurred while updating the invoice.",
500));
}
}
/// <summary>
/// Marks the specified invoice as completed if it exists and is not already completed.
@ -645,6 +842,10 @@ namespace Marco.Pms.Services.Controllers
}
}
#endregion
#region =================================================================== Helper Functions ===================================================================
/// <summary>
/// Loads invoice comments asynchronously with related metadata.
/// </summary>
@ -686,5 +887,7 @@ namespace Marco.Pms.Services.Controllers
.ToListAsync();
}
#endregion
}
}