mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
178 lines
7.7 KiB
C#
178 lines
7.7 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Microsoft.Extensions.Options;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
using SeaHaven.Services.Configuration;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Interfaces;
|
|
using System.Security.Claims;
|
|
|
|
namespace SeaHaven.Services.Implementation
|
|
{
|
|
public class UpliftService : IUpliftService
|
|
{
|
|
private readonly IUpliftDataService _upliftData;
|
|
private readonly IDispatchDataService _dispatchData;
|
|
private readonly ApprovalsOptions _approvalsOptions;
|
|
|
|
public UpliftService(IUpliftDataService upliftData, IDispatchDataService dispatchData, IOptions<ApprovalsOptions> approvalsOptions)
|
|
{
|
|
_upliftData = upliftData;
|
|
_dispatchData = dispatchData;
|
|
_approvalsOptions = approvalsOptions.Value;
|
|
}
|
|
|
|
public async Task<UpliftListResultDTO> ListAsync(ClaimsPrincipal user, string? status, int? tier, int page, int pageSize, CancellationToken cancellationToken)
|
|
{
|
|
var (total, items) = await _upliftData.GetPagedAsync(status, tier, page, pageSize, cancellationToken);
|
|
|
|
var mapped = items.Select(r => new UpliftListItemDTO
|
|
{
|
|
Id = r.Id,
|
|
DispatchId = r.DispatchId,
|
|
DispatchNumber = r.DispatchNumber,
|
|
PONumber = r.PONumber,
|
|
WorkOrderId = r.WorkOrderId,
|
|
VendorCompanyName = r.VendorCompanyName,
|
|
CurrentNTE = r.CurrentNTE,
|
|
RequestedNTE = r.RequestedNTE,
|
|
Delta = r.RequestedNTE - (r.CurrentNTE ?? 0m),
|
|
VendorReason = r.VendorReason,
|
|
RequiredTier = r.RequiredTier,
|
|
Status = r.Status,
|
|
RequestedAt = r.CreatedDate,
|
|
DecidedAt = r.DecidedAt,
|
|
DecisionNote = r.DecisionNote,
|
|
CanDecide = UserCanApprove(user, r.RequiredTier)
|
|
}).ToList();
|
|
|
|
return new UpliftListResultDTO
|
|
{
|
|
Total = total,
|
|
Page = page,
|
|
PageSize = pageSize,
|
|
Items = mapped
|
|
};
|
|
}
|
|
|
|
public async Task<IEnumerable<UpliftForDispatchDTO>> ListForDispatchAsync(ClaimsPrincipal user, int dispatchId, CancellationToken cancellationToken)
|
|
{
|
|
var rows = await _upliftData.GetForDispatchAsync(dispatchId, cancellationToken);
|
|
|
|
return rows.Select(r => new UpliftForDispatchDTO
|
|
{
|
|
Id = r.Id,
|
|
DispatchId = r.DispatchId,
|
|
CurrentNTE = r.CurrentNTE,
|
|
RequestedNTE = r.RequestedNTE,
|
|
Delta = r.RequestedNTE - (r.CurrentNTE ?? 0m),
|
|
VendorReason = r.VendorReason,
|
|
Status = r.Status,
|
|
RequiredTier = r.RequiredTier,
|
|
RequestedByVendorName = r.RequestedByVendorName,
|
|
RequestedAt = r.CreatedDate,
|
|
DecidedAt = r.DecidedAt,
|
|
DecisionNote = r.DecisionNote,
|
|
DecidedByName = string.Join(" ", new[] { r.DecidedByFirstName, r.DecidedByLastName }
|
|
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim(),
|
|
CanDecide = UserCanApprove(user, r.RequiredTier)
|
|
}).ToList();
|
|
}
|
|
|
|
public async Task<UpliftApproveResultDTO> ApproveAsync(ClaimsPrincipal user, int id, string? note, CancellationToken cancellationToken)
|
|
{
|
|
var req = await _upliftData.GetByIdAsync(id, cancellationToken);
|
|
if (req == null) throw new KeyNotFoundException("Uplift request not found");
|
|
if (req.Status != "Pending") throw new InvalidOperationException($"Cannot approve a '{req.Status}' request");
|
|
if (!UserCanApprove(user, req.RequiredTier))
|
|
throw new UpliftForbiddenException($"Approval requires a Tier {req.RequiredTier} role");
|
|
|
|
var dispatch = await _dispatchData.GetByIdAsync(req.DispatchId);
|
|
if (dispatch == null) throw new KeyNotFoundException("Dispatch not found");
|
|
if (dispatch.Status == "Verified" || dispatch.Status == "Cancelled" || dispatch.Status == "Refused")
|
|
throw new InvalidOperationException($"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(note) ? null : note!.Trim();
|
|
req.LastModificationTime = now;
|
|
|
|
await _dispatchData.StageAuditLogAsync(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
|
|
}, cancellationToken);
|
|
|
|
await _upliftData.SaveChangesAsync(cancellationToken);
|
|
return new UpliftApproveResultDTO { Id = req.Id, Status = req.Status, NTEAmount = dispatch.NTEAmount };
|
|
}
|
|
|
|
public async Task<UpliftDenyResultDTO> DenyAsync(ClaimsPrincipal user, int id, string? note, CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(note))
|
|
throw new InvalidOperationException("A note is required when denying");
|
|
|
|
var req = await _upliftData.GetByIdAsync(id, cancellationToken);
|
|
if (req == null) throw new KeyNotFoundException("Uplift request not found");
|
|
if (req.Status != "Pending") throw new InvalidOperationException($"Cannot deny a '{req.Status}' request");
|
|
if (!UserCanApprove(user, req.RequiredTier))
|
|
throw new UpliftForbiddenException($"Decision requires a Tier {req.RequiredTier} role");
|
|
|
|
var dispatch = await _dispatchData.GetByIdAsync(req.DispatchId);
|
|
|
|
var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
var now = DateTime.UtcNow;
|
|
|
|
req.Status = "Denied";
|
|
req.DecidedAt = now;
|
|
req.DecidedByUserId = userId;
|
|
req.DecisionNote = note!.Trim();
|
|
req.LastModificationTime = now;
|
|
|
|
await _dispatchData.StageAuditLogAsync(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = dispatch?.WorkOrderId ?? 0,
|
|
UserId = userId,
|
|
FieldName = $"Dispatch {dispatch?.DispatchNumber} Uplift",
|
|
OldValue = "Pending",
|
|
NewValue = "Denied",
|
|
Action = "uplift_denied",
|
|
CreatedAt = now
|
|
}, cancellationToken);
|
|
|
|
await _upliftData.SaveChangesAsync(cancellationToken);
|
|
return new UpliftDenyResultDTO { Id = req.Id, Status = req.Status };
|
|
}
|
|
|
|
public bool CanApprove(ClaimsPrincipal user, int tier) => UserCanApprove(user, tier);
|
|
|
|
private bool UserCanApprove(ClaimsPrincipal user, int requiredTier)
|
|
{
|
|
var roles = RolesForTier(requiredTier);
|
|
foreach (var r in roles)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(r) && user.IsInRole(r)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private string[] RolesForTier(int requiredTier) => requiredTier switch
|
|
{
|
|
1 => _approvalsOptions.Tier1Roles,
|
|
2 => _approvalsOptions.Tier2Roles,
|
|
_ => Array.Empty<string>()
|
|
};
|
|
}
|
|
}
|