diff --git a/Marco.Pms.Model/Dtos/Expenses/CreateExpensesDto.cs b/Marco.Pms.Model/Dtos/Expenses/CreateExpensesDto.cs index d1d8ec4..d4e9b8d 100644 --- a/Marco.Pms.Model/Dtos/Expenses/CreateExpensesDto.cs +++ b/Marco.Pms.Model/Dtos/Expenses/CreateExpensesDto.cs @@ -5,8 +5,9 @@ namespace Marco.Pms.Model.Dtos.Expenses public class CreateExpensesDto { public required Guid ProjectId { get; set; } - public Guid ExpensesTypeId { get; set; } - public Guid PaymentModeId { get; set; } + public required Guid ExpensesTypeId { get; set; } + public required Guid PaymentModeId { get; set; } + public required Guid PaidById { get; set; } public DateTime TransactionDate { get; set; } = DateTime.Now; public string? TransactionId { get; set; } public required string Description { get; set; } diff --git a/Marco.Pms.Services/Controllers/ExpanseController.cs b/Marco.Pms.Services/Controllers/ExpanseController.cs new file mode 100644 index 0000000..b642495 --- /dev/null +++ b/Marco.Pms.Services/Controllers/ExpanseController.cs @@ -0,0 +1,199 @@ +using Marco.Pms.DataAccess.Data; +using Marco.Pms.Model.Dtos.Expenses; +using Marco.Pms.Model.Entitlements; +using Marco.Pms.Model.Expenses; +using Marco.Pms.Model.Utilities; +using Marco.Pms.Services.Service; +using MarcoBMS.Services.Helpers; +using MarcoBMS.Services.Service; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Document = Marco.Pms.Model.DocumentManager.Document; + +// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 + +namespace Marco.Pms.Services.Controllers +{ + [Route("api/[controller]")] + [ApiController] + [Authorize] + public class ExpanseController : ControllerBase + { + private readonly ApplicationDbContext _context; + private readonly UserHelper _userHelper; + private readonly PermissionServices _permission; + private readonly ILoggingService _logger; + private readonly S3UploadService _s3Service; + private readonly Guid tenantId; + public ExpanseController( + ApplicationDbContext context, + UserHelper userHelper, + PermissionServices permission, + ILoggingService logger, + S3UploadService s3Service) + { + _context = context; + _userHelper = userHelper; + _permission = permission; + _logger = logger; + tenantId = userHelper.GetTenantId(); + _s3Service = s3Service; + } + + [HttpGet] + public IEnumerable Get() + { + return new string[] { "value1", "value2" }; + } + + [HttpGet("{id}")] + public string Get(int id) + { + return "value"; + } + + [HttpPost] + public async Task Post([FromBody] CreateExpensesDto dto) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var hasUploadPermission = await _permission.HasPermission(PermissionsMaster.ExpenseUpload, loggedInEmployee.Id); + var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, dto.ProjectId); + if (!hasUploadPermission || !hasProjectPermission) + { + _logger.LogWarning("Access DENIED for employee {EmployeeId} for uploading expense on project {ProjectId}.", loggedInEmployee.Id, dto.ProjectId); + return StatusCode(403, ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Upload expenses for this project", 403)); + } + var isExpensesTypeExist = await _context.ExpensesTypeMaster.AnyAsync(et => et.Id == dto.ExpensesTypeId); + if (!isExpensesTypeExist) + { + _logger.LogWarning("Expenses type not for ID: {ExpensesTypeId} when creating new expense", dto.ExpensesTypeId); + return NotFound(ApiResponse.ErrorResponse("Expanses Type not found", "Expanses Type not found", 404)); + } + var isPaymentModeExist = await _context.PaymentModeMatser.AnyAsync(et => et.Id == dto.PaymentModeId); + if (!isPaymentModeExist) + { + _logger.LogWarning("Payment Mode not for ID: {PaymentModeId} when creating new expense", dto.PaymentModeId); + return NotFound(ApiResponse.ErrorResponse("Payment Mode not found", "Payment Mode not found", 404)); + } + var isStatusExist = await _context.ExpensesStatusMaster.AnyAsync(et => et.Id == dto.StatusId); + if (!isStatusExist) + { + _logger.LogWarning("Status not for ID: {PaymentModeId} when creating new expense", dto.PaymentModeId); + return NotFound(ApiResponse.ErrorResponse("Status not found", "Status not found", 404)); + } + var expense = new Expenses + { + ProjectId = dto.ProjectId, + ExpensesTypeId = dto.ExpensesTypeId, + PaymentModeId = dto.PaymentModeId, + PaidById = dto.PaidById, + CreatedById = loggedInEmployee.Id, + TransactionDate = dto.TransactionDate, + CreatedAt = DateTime.UtcNow, + TransactionId = dto.TransactionId, + Description = dto.Description, + Location = dto.Location, + GSTNumber = dto.GSTNumber, + SupplerName = dto.SupplerName, + Amount = dto.Amount, + NoOfPersons = dto.NoOfPersons, + StatusId = dto.StatusId, + PreApproved = dto.PreApproved, + IsActive = true, + TenantId = tenantId + }; + _context.Expenses.Add(expense); + + + Guid batchId = Guid.NewGuid(); + foreach (var attachment in dto.BillAttachments) + { + //if (!_s3Service.IsBase64String(attachment.Base64Data)) + //{ + // _logger.LogWarning("Image upload failed: Base64 data is missing While creating new expense entity for project {ProjectId} by employee {EmployeeId}", expense.ProjectId, expense.PaidById); + // return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); + //} + var base64 = attachment.Base64Data!.Contains(',') + ? attachment.Base64Data[(attachment.Base64Data.IndexOf(",") + 1)..] + : attachment.Base64Data; + + var fileType = _s3Service.GetContentTypeFromBase64(base64); + var fileName = _s3Service.GenerateFileName(fileType, expense.Id, "Expense"); + var objectKey = $"tenant-{tenantId}/project-{expense.ProjectId}/Expenses/{fileName}"; + try + { + await _s3Service.UploadFileAsync(base64, fileType, objectKey); + _logger.LogInfo("Image uploaded to S3 with key: {ObjectKey}", objectKey); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occured while saving image to S3"); + //return BadRequest(ApiResponse.ErrorResponse("Cannot upload attachment to S3", new + //{ + // message = ex.Message, + // innerexcption = ex.InnerException?.Message, + // stackTrace = ex.StackTrace, + // source = ex.Source + //}, 400)); + } + + var document = new Document + { + BatchId = batchId, + UploadedById = loggedInEmployee.Id, + FileName = attachment.FileName ?? "", + ContentType = attachment.ContentType ?? "", + S3Key = objectKey, + //Base64Data = attachment.Base64Data, + FileSize = attachment.FileSize, + UploadedAt = DateTime.UtcNow, + TenantId = tenantId + }; + _context.Documents.Add(document); + + var billAttachement = new BillAttachments + { + DocumentId = document.Id, + ExpensesId = expense.Id, + TenantId = tenantId + }; + _context.BillAttachments.Add(billAttachement); + } + try + { + await _context.SaveChangesAsync(); + } + catch (DbUpdateException dbEx) + { + _logger.LogError(dbEx, "Error occured while saving Expense, Document and bill attachment entity"); + return BadRequest(ApiResponse.ErrorResponse("Databsae Exception", new + { + Message = dbEx.Message, + StackTrace = dbEx.StackTrace, + Source = dbEx.Source, + innerexcption = new + { + Message = dbEx.InnerException?.Message, + StackTrace = dbEx.InnerException?.StackTrace, + Source = dbEx.InnerException?.Source, + } + }, 400)); + } + _logger.LogInfo("Documents and attachments saved for Expense: {ExpenseId}", expense.Id); + + return StatusCode(201, ApiResponse.SuccessResponse(expense, "Expense created Successfully", 201)); + } + + + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/Marco.Pms.Services/Controllers/MasterController.cs b/Marco.Pms.Services/Controllers/MasterController.cs index f115bde..608caed 100644 --- a/Marco.Pms.Services/Controllers/MasterController.cs +++ b/Marco.Pms.Services/Controllers/MasterController.cs @@ -856,6 +856,7 @@ namespace Marco.Pms.Services.Controllers var response = await _masterService.GetExpenseTypeListAsync(); return StatusCode(response.StatusCode, response); } + [HttpPost("expenses-type")] public async Task CreateExpenseType(ExpensesTypeMasterDto dto) { var response = await _masterService.GetExpenseTypeListAsync();