using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Security.Claims; namespace Api.SeaHavenIndustries.Controllers { [Authorize] [ApiController] [Route("api/[controller]")] [Route("api/uplifts")] public class UpliftController : Controller { private readonly ApplicationDbContext _db; private readonly IConfiguration _config; public UpliftController(ApplicationDbContext db, IConfiguration config) { _db = db; _config = config; } [HttpGet] public async Task List([FromQuery] string? status = "Pending", [FromQuery] int? tier = null, [FromQuery] int page = 1, [FromQuery] int pageSize = 25) { var query = from u in _db.DispatchUpliftRequests join d in _db.Dispatches on u.DispatchId equals d.Id join v in _db.Vendors on d.VendorId equals v.Id into vendors from v in vendors.DefaultIfEmpty() where (u.IsDeleted == null || u.IsDeleted == false) select new { u, d, v }; if (!string.IsNullOrWhiteSpace(status)) query = query.Where(x => x.u.Status == status); if (tier.HasValue) query = query.Where(x => x.u.RequiredTier == tier.Value); var total = await query.CountAsync(); var raw = await query .OrderByDescending(x => x.u.CreatedDate) .Skip((Math.Max(page, 1) - 1) * pageSize) .Take(pageSize) .Select(x => new { x.u.Id, DispatchId = x.d.Id, x.d.DispatchNumber, x.d.PONumber, WorkOrderId = x.d.WorkOrderId, VendorCompanyName = x.v != null ? x.v.CompanyName : x.u.RequestedByVendorName, x.u.CurrentNTE, x.u.RequestedNTE, x.u.VendorReason, x.u.RequiredTier, x.u.Status, RequestedAt = x.u.CreatedDate, x.u.DecidedAt, x.u.DecisionNote }) .ToListAsync(); var items = raw.Select(r => new { r.Id, r.DispatchId, r.DispatchNumber, r.PONumber, r.WorkOrderId, r.VendorCompanyName, r.CurrentNTE, r.RequestedNTE, Delta = r.RequestedNTE - (r.CurrentNTE ?? 0m), r.VendorReason, r.RequiredTier, r.Status, r.RequestedAt, r.DecidedAt, r.DecisionNote, CanDecide = UserCanApprove(r.RequiredTier) }).ToList(); return Ok(new DataResponse { Status = "Success", Data = new { total, page, pageSize, items } }); } [HttpGet("dispatch/{dispatchId:int}")] public async Task ListForDispatch(int dispatchId) { var rows = await (from u in _db.DispatchUpliftRequests where u.DispatchId == dispatchId && (u.IsDeleted == null || u.IsDeleted == false) join dec in _db.Users on u.DecidedByUserId equals dec.Id into decs from dec in decs.DefaultIfEmpty() orderby u.CreatedDate descending select new { u.Id, u.DispatchId, u.CurrentNTE, u.RequestedNTE, Delta = u.RequestedNTE - (u.CurrentNTE ?? 0m), u.VendorReason, u.Status, u.RequiredTier, u.RequestedByVendorName, RequestedAt = u.CreatedDate, u.DecidedAt, u.DecisionNote, DecidedByFirstName = dec != null ? dec.FirstName : null, DecidedByLastName = dec != null ? dec.LastName : null }).ToListAsync(); var items = rows.Select(r => new { r.Id, r.DispatchId, r.CurrentNTE, r.RequestedNTE, r.Delta, r.VendorReason, r.Status, r.RequiredTier, r.RequestedByVendorName, r.RequestedAt, r.DecidedAt, r.DecisionNote, DecidedByName = string.Join(" ", new[] { r.DecidedByFirstName, r.DecidedByLastName } .Where(s => !string.IsNullOrWhiteSpace(s))).Trim(), CanDecide = UserCanApprove(r.RequiredTier) }).ToList(); return Ok(new DataResponse { Status = "Success", Data = items }); } [HttpPost("{id:int}/approve")] public async Task Approve(int id, [FromBody] DecisionRequest? body) { var req = await _db.DispatchUpliftRequests.FirstOrDefaultAsync(u => u.Id == id); if (req == null) return NotFound(new Response { Status = "Error", Message = "Uplift request not found" }); if (req.Status != "Pending") return BadRequest(new Response { Status = "Error", Message = $"Cannot approve a '{req.Status}' request" }); if (!UserCanApprove(req.RequiredTier)) return StatusCode(403, new Response { Status = "Error", Message = $"Approval requires a Tier {req.RequiredTier} role" }); var dispatch = await _db.Dispatches.FirstOrDefaultAsync(d => d.Id == req.DispatchId); if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" }); if (dispatch.Status == "Verified" || dispatch.Status == "Cancelled") return BadRequest(new Response { Status = "Error", Message = $"Cannot approve uplift on a '{dispatch.Status}' dispatch" }); var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; var now = DateTime.UtcNow; var oldNTE = dispatch.NTEAmount ?? 0m; dispatch.NTEAmount = req.RequestedNTE; dispatch.LastModificationTime = now; req.Status = "Approved"; req.DecidedAt = now; req.DecidedByUserId = userId; req.DecisionNote = string.IsNullOrWhiteSpace(body?.Note) ? null : body!.Note!.Trim(); req.LastModificationTime = now; _db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog { WorkOrderId = dispatch.WorkOrderId ?? 0, UserId = userId, FieldName = $"Dispatch {dispatch.DispatchNumber} NTE", OldValue = $"${oldNTE:F2}", NewValue = $"${req.RequestedNTE:F2}", Action = "uplift_approved", CreatedAt = now }); await _db.SaveChangesAsync(); return Ok(new DataResponse { Status = "Success", Data = new { req.Id, req.Status, dispatch.NTEAmount } }); } [HttpPost("{id:int}/deny")] public async Task Deny(int id, [FromBody] DecisionRequest body) { if (body == null || string.IsNullOrWhiteSpace(body.Note)) return BadRequest(new Response { Status = "Error", Message = "A note is required when denying" }); var req = await _db.DispatchUpliftRequests.FirstOrDefaultAsync(u => u.Id == id); if (req == null) return NotFound(new Response { Status = "Error", Message = "Uplift request not found" }); if (req.Status != "Pending") return BadRequest(new Response { Status = "Error", Message = $"Cannot deny a '{req.Status}' request" }); if (!UserCanApprove(req.RequiredTier)) return StatusCode(403, new Response { Status = "Error", Message = $"Decision requires a Tier {req.RequiredTier} role" }); var dispatch = await _db.Dispatches.FirstOrDefaultAsync(d => d.Id == req.DispatchId); var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; var now = DateTime.UtcNow; req.Status = "Denied"; req.DecidedAt = now; req.DecidedByUserId = userId; req.DecisionNote = body.Note!.Trim(); req.LastModificationTime = now; _db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog { WorkOrderId = dispatch?.WorkOrderId ?? 0, UserId = userId, FieldName = $"Dispatch {dispatch?.DispatchNumber} Uplift", OldValue = "Pending", NewValue = "Denied", Action = "uplift_denied", CreatedAt = now }); await _db.SaveChangesAsync(); return Ok(new DataResponse { Status = "Success", Data = new { req.Id, req.Status } }); } [HttpGet("can-approve")] public IActionResult CanApprove([FromQuery] int tier) { return Ok(new DataResponse { Status = "Success", Data = new { canApprove = UserCanApprove(tier) } }); } private bool UserCanApprove(int requiredTier) { var key = $"Approvals:Tier{requiredTier}Roles"; var roles = _config.GetSection(key).Get() ?? Array.Empty(); foreach (var r in roles) { if (!string.IsNullOrWhiteSpace(r) && User.IsInRole(r)) return true; } return false; } public class DecisionRequest { public string? Note { get; set; } } } }