Added the update invoice API
This commit is contained in:
parent
47bb49fac6
commit
5a9b06cca6
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user