using AutoMapper; 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.Model.ViewModels.Expanses; using Marco.Pms.Model.ViewModels.Master; 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 ExpenseController : ControllerBase { private readonly ApplicationDbContext _context; private readonly UserHelper _userHelper; private readonly PermissionServices _permission; private readonly ILoggingService _logger; private readonly S3UploadService _s3Service; private readonly IMapper _mapper; private readonly Guid tenantId; public ExpenseController( ApplicationDbContext context, UserHelper userHelper, PermissionServices permission, ILoggingService logger, S3UploadService s3Service, IMapper mapper) { _context = context; _userHelper = userHelper; _permission = permission; _logger = logger; _s3Service = s3Service; _mapper = mapper; tenantId = userHelper.GetTenantId(); } [HttpGet("list")] public async Task GetExpensesList() { var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var loggedInEmployeeId = loggedInEmployee.Id; List? expensesList = null; var expensesListQuery = _context.Expenses .Include(e => e.ExpensesType) .Include(e => e.Project) .Include(e => e.PaidBy) .ThenInclude(e => e!.JobRole) .Include(e => e.PaymentMode) .Include(e => e.Status) .Include(e => e.CreatedBy) .Where(e => e.TenantId == tenantId); var HasViewAllPermission = await _permission.HasPermission(PermissionsMaster.ExpenseViewAll, loggedInEmployeeId); var HasViewSelfPermission = await _permission.HasPermission(PermissionsMaster.ExpenseViewSelf, loggedInEmployeeId); if (HasViewAllPermission) { expensesList = await expensesListQuery.ToListAsync(); } else if (HasViewSelfPermission) { expensesList = await expensesListQuery.Where(e => e.CreatedById == loggedInEmployeeId).ToListAsync(); } if (expensesList == null) { _logger.LogInfo("No Expense found for employee {EmployeeId}", loggedInEmployeeId); return Ok(ApiResponse.SuccessResponse(new List(), "No Expense found for current user", 200)); } //ImageFilter? imageFilter = null; //if (!string.IsNullOrWhiteSpace(filter)) //{ // try // { // var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; // //string unescapedJsonString = JsonSerializer.Deserialize(filter, options) ?? ""; // //imageFilter = JsonSerializer.Deserialize(unescapedJsonString, options); // imageFilter = JsonSerializer.Deserialize(filter, options); // } // catch (Exception ex) // { // _logger.LogWarning("[GetImageList] Failed to parse filter: {Message}", ex.Message); // } //} var response = _mapper.Map>(expensesList); var statusIds = expensesList.Select(e => e.StatusId).ToList(); var statusMappings = await _context.ExpensesStatusMapping .Include(sm => sm.NextStatus) .Where(sm => statusIds.Contains(sm.StatusId)) .ToListAsync(); foreach (var expense in response) { var statusMapping = statusMappings.Where(sm => sm.StatusId == expense.Status?.Id).Select(sm => _mapper.Map(sm.NextStatus)).ToList(); expense.NextStatus = statusMapping; } return StatusCode(200, ApiResponse.SuccessResponse(response, $"{response.Count} records of expenses for you fetched successfully", 200)); } [HttpGet("details/{id}")] public string Get(int id) { return "value"; } [HttpPost("create")] 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("edit/{id}")] public void Put(int id, [FromBody] string value) { } [HttpDelete("delete/{id}")] public void Delete(int id) { } } }