Added the new API to create invoice
This commit is contained in:
parent
4684b438f6
commit
b30369baa5
18
Marco.Pms.Model/Dtos/Collection/InvoiceDto.cs
Normal file
18
Marco.Pms.Model/Dtos/Collection/InvoiceDto.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using Marco.Pms.Model.Utilities;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Model.Dtos.Collection
|
||||||
|
{
|
||||||
|
public class InvoiceDto
|
||||||
|
{
|
||||||
|
public Guid? Id { get; set; }
|
||||||
|
public required string Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public required string InvoiceNumber { get; set; }
|
||||||
|
public required Guid ProjectId { get; set; }
|
||||||
|
public required DateTime InvoiceDate { get; set; }
|
||||||
|
public required DateTime ClientSubmitedDate { get; set; }
|
||||||
|
public required DateTime ExceptedPaymentDate { get; set; }
|
||||||
|
public required double Amount { get; set; }
|
||||||
|
public List<FileUploadModel>? Attachments { get; set; }
|
||||||
|
}
|
||||||
|
}
|
23
Marco.Pms.Model/ViewModels/Collection/InvoiceListVM.cs
Normal file
23
Marco.Pms.Model/ViewModels/Collection/InvoiceListVM.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Marco.Pms.Model.ViewModels.Activities;
|
||||||
|
using Marco.Pms.Model.ViewModels.Projects;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Model.ViewModels.Collection
|
||||||
|
{
|
||||||
|
public class InvoiceListVM
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Title { get; set; } = default!;
|
||||||
|
public string Description { get; set; } = default!;
|
||||||
|
public string InvoiceNumber { get; set; } = default!;
|
||||||
|
public BasicProjectVM? Project { get; set; }
|
||||||
|
public DateTime InvoiceDate { get; set; }
|
||||||
|
public DateTime ClientSubmitedDate { get; set; }
|
||||||
|
public DateTime ExceptedPaymentDate { get; set; }
|
||||||
|
public double Amount { get; set; }
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public BasicEmployeeVM? CreatedBy { get; set; }
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
public BasicEmployeeVM? UpdatedBy { get; set; }
|
||||||
|
}
|
||||||
|
}
|
175
Marco.Pms.Services/Controllers/CollectionController.cs
Normal file
175
Marco.Pms.Services/Controllers/CollectionController.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
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<ApplicationDbContext> _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<ApplicationDbContext> 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<IActionResult> CreateInvoiceAsync(InvoiceDto model)
|
||||||
|
{
|
||||||
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||||
|
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<object>.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<object>.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<object>.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<Invoice>(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<Document>();
|
||||||
|
var invoiceAttachments = new List<InvoiceAttachment>();
|
||||||
|
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<object>.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<object>.ErrorResponse(
|
||||||
|
"Transaction failed: " + ex.Message,
|
||||||
|
"An error occurred while creating the invoice", 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build response
|
||||||
|
var response = _mapper.Map<InvoiceListVM>(invoice);
|
||||||
|
response.Project = _mapper.Map<BasicProjectVM>(project);
|
||||||
|
response.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
|
||||||
|
|
||||||
|
return Ok(ApiResponse<object>.SuccessResponse(response, "Invoice Created Successfully", 201));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Marco.Pms.Model.AppMenu;
|
using Marco.Pms.Model.AppMenu;
|
||||||
|
using Marco.Pms.Model.Collection;
|
||||||
using Marco.Pms.Model.Directory;
|
using Marco.Pms.Model.Directory;
|
||||||
using Marco.Pms.Model.DocumentManager;
|
using Marco.Pms.Model.DocumentManager;
|
||||||
using Marco.Pms.Model.Dtos.Activities;
|
using Marco.Pms.Model.Dtos.Activities;
|
||||||
using Marco.Pms.Model.Dtos.AppMenu;
|
using Marco.Pms.Model.Dtos.AppMenu;
|
||||||
|
using Marco.Pms.Model.Dtos.Collection;
|
||||||
using Marco.Pms.Model.Dtos.Directory;
|
using Marco.Pms.Model.Dtos.Directory;
|
||||||
using Marco.Pms.Model.Dtos.DocumentManager;
|
using Marco.Pms.Model.Dtos.DocumentManager;
|
||||||
using Marco.Pms.Model.Dtos.Employees;
|
using Marco.Pms.Model.Dtos.Employees;
|
||||||
@ -26,6 +28,7 @@ using Marco.Pms.Model.Projects;
|
|||||||
using Marco.Pms.Model.TenantModels;
|
using Marco.Pms.Model.TenantModels;
|
||||||
using Marco.Pms.Model.TenantModels.MongoDBModel;
|
using Marco.Pms.Model.TenantModels.MongoDBModel;
|
||||||
using Marco.Pms.Model.ViewModels.Activities;
|
using Marco.Pms.Model.ViewModels.Activities;
|
||||||
|
using Marco.Pms.Model.ViewModels.Collection;
|
||||||
using Marco.Pms.Model.ViewModels.Directory;
|
using Marco.Pms.Model.ViewModels.Directory;
|
||||||
using Marco.Pms.Model.ViewModels.DocumentManager;
|
using Marco.Pms.Model.ViewModels.DocumentManager;
|
||||||
using Marco.Pms.Model.ViewModels.Employee;
|
using Marco.Pms.Model.ViewModels.Employee;
|
||||||
@ -255,6 +258,11 @@ namespace Marco.Pms.Services.MappingProfiles
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region ======================================================= Collection =======================================================
|
||||||
|
CreateMap<InvoiceDto, Invoice>();
|
||||||
|
CreateMap<Invoice, InvoiceListVM>();
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region ======================================================= Master =======================================================
|
#region ======================================================= Master =======================================================
|
||||||
|
|
||||||
CreateMap<FeaturePermission, FeaturePermissionVM>();
|
CreateMap<FeaturePermission, FeaturePermissionVM>();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user