using AutoMapper; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Collection; using Marco.Pms.Model.DocumentManager; using Marco.Pms.Model.Dtos.Collection; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Activities; using Marco.Pms.Model.ViewModels.Collection; using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Marco.Pms.Services.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class CollectionController : ControllerBase { private readonly IDbContextFactory _dbContextFactory; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly UserHelper _userHelper; private readonly S3UploadService _s3Service; private readonly IMapper _mapper; private readonly ILoggingService _logger; private readonly Guid tenantId; public CollectionController(IDbContextFactory dbContextFactory, IServiceScopeFactory serviceScopeFactory, S3UploadService s3Service, UserHelper userhelper, ILoggingService logger, IMapper mapper) { _dbContextFactory = dbContextFactory; _serviceScopeFactory = serviceScopeFactory; _userHelper = userhelper; _s3Service = s3Service; _mapper = mapper; _logger = logger; tenantId = userhelper.GetTenantId(); } [HttpPost("invoice/create")] public async Task CreateInvoiceAsync(InvoiceDto model) { await using var context = await _dbContextFactory.CreateDbContextAsync(); using var scope = _serviceScopeFactory.CreateScope(); var permissionService = scope.ServiceProvider.GetRequiredService(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); _logger.LogInfo("Starting invoice creation for ProjectId: {ProjectId} by EmployeeId: {EmployeeId}", model.ProjectId, loggedInEmployee.Id); // Validate date sequence if (model.InvoiceDate.Date > model.ClientSubmitedDate.Date) { _logger.LogWarning("Invoice date {InvoiceDate} is later than client submitted date {ClientSubmitedDate}", model.InvoiceDate, model.ClientSubmitedDate); return BadRequest(ApiResponse.ErrorResponse( "Invoice date is later than client submitted date", "Invoice date is later than client submitted date", 400)); } if (model.ClientSubmitedDate.Date > model.ExceptedPaymentDate.Date) { _logger.LogWarning("Client submitted date {ClientSubmitedDate} is later than expected payment date {ExpectedPaymentDate}", model.ClientSubmitedDate, model.ExceptedPaymentDate); return BadRequest(ApiResponse.ErrorResponse( "Client submitted date is later than expected payment date", "Client submitted date is later than expected payment date", 400)); } // 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.ErrorResponse("Project not found", "Project not found", 404)); } // Begin transaction scope with async flow support await using var transaction = await context.Database.BeginTransactionAsync(); var invoice = new Invoice(); try { // Map and create invoice invoice = _mapper.Map(model); invoice.IsActive = true; invoice.CreatedAt = DateTime.UtcNow; invoice.CreatedById = loggedInEmployee.Id; invoice.TenantId = tenantId; context.Invoices.Add(invoice); await context.SaveChangesAsync(); // Save to generate invoice.Id // Handle attachments var documents = new List(); var invoiceAttachments = new List(); if (model.Attachments?.Any() == true) { var batchId = Guid.NewGuid(); foreach (var attachment in model.Attachments) { string base64 = attachment.Base64Data?.Split(',').LastOrDefault() ?? ""; if (string.IsNullOrWhiteSpace(base64)) { _logger.LogWarning("Base64 data is missing for attachment {FileName}", attachment.FileName ?? ""); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Image data missing", 400)); } var fileType = _s3Service.GetContentTypeFromBase64(base64); var fileName = _s3Service.GenerateFileName(fileType, tenantId, "invoice"); var objectKey = $"tenant-{tenantId}/Project/{model.ProjectId}/Invoice/{fileName}"; await _s3Service.UploadFileAsync(base64, fileType, objectKey); var document = new Document { Id = Guid.NewGuid(), BatchId = batchId, UploadedById = loggedInEmployee.Id, FileName = attachment.FileName ?? fileName, ContentType = attachment.ContentType, S3Key = objectKey, FileSize = attachment.FileSize, UploadedAt = DateTime.UtcNow, TenantId = tenantId }; documents.Add(document); var invoiceAttachment = new InvoiceAttachment { InvoiceId = invoice.Id, DocumentId = document.Id, TenantId = tenantId }; invoiceAttachments.Add(invoiceAttachment); } context.Documents.AddRange(documents); context.InvoiceAttachments.AddRange(invoiceAttachments); await context.SaveChangesAsync(); // Save attachments and mappings } // Commit transaction await transaction.CommitAsync(); _logger.LogInfo("Invoice {InvoiceId} created successfully with {AttachmentCount} attachments.", invoice.Id, documents.Count); } catch (Exception ex) { await transaction.RollbackAsync(); _logger.LogError(ex, "Transaction rolled back during invoice creation for ProjectId {ProjectId}", model.ProjectId); return StatusCode(500, ApiResponse.ErrorResponse( "Transaction failed: " + ex.Message, "An error occurred while creating the invoice", 500)); } // Build response var response = _mapper.Map(invoice); response.Project = _mapper.Map(project); response.CreatedBy = _mapper.Map(loggedInEmployee); return Ok(ApiResponse.SuccessResponse(response, "Invoice Created Successfully", 201)); } } }