using Management.Data; using Management.Security; using Microsoft.AspNetCore.Mvc; namespace Management.Controllers.Admin; /// /// Admin endpoints for audience-based allocation modifiers. /// Table: dbo.tbAllocationModifier /// Proc: dbo.spAllocationRecommend (list / update actions) /// /// ENDPOINTS: /// GET /api/admin/modifiers - List all modifiers /// PUT /api/admin/modifiers/{id} - Update a modifier /// POST /api/admin/modifiers/preview - Preview recommendation with factors /// [ApiController] [Route("api/admin/modifiers")] public sealed class AdminModifiersController : AdminControllerBase { public AdminModifiersController(SqlService sql, ClientContext client, ILogger log) : base(sql, client, log) { } [HttpGet] public Task List(CancellationToken ct) => CallProc("spAllocationRecommend", "list", new { }, ct); [HttpPut("{id:int}")] public Task Update(int id, [FromBody] UpdateModifierRequest request, CancellationToken ct) { if (request?.PctAdjustment is < -50 or > 50) return Task.FromResult(ValidationError("pctAdjustment must be between -50 and 50")); Logger.LogWarning("[Admin] UpdateModifier | Id={Id} | By={User}", id, Client.Email); return CallProc("spAllocationRecommend", "update", new { id, pctAdjustment = request?.PctAdjustment, minBudgetAdj = request?.MinBudgetAdj, rationale = request?.Rationale?.Trim(), isActive = request?.IsActive }, ct); } /// /// Preview a recommendation with given factors — same proc, recommend action. /// Lets admins test how modifiers affect channel mix without going through the wizard. /// [HttpPost("preview")] public Task Preview([FromBody] PreviewRequest request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request?.BusinessCategory)) return Task.FromResult(ValidationError("businessCategory is required")); if (string.IsNullOrWhiteSpace(request?.Objective)) return Task.FromResult(ValidationError("objective is required")); return CallProc("spAllocationRecommend", "recommend", new { businessCategory = request.BusinessCategory.Trim(), objective = request.Objective.Trim(), ageSkew = request.AgeSkew?.Trim(), marketScope = request.MarketScope?.Trim() }, ct); } } // DTOs public sealed class UpdateModifierRequest { public int? PctAdjustment { get; set; } public int? MinBudgetAdj { get; set; } public string? Rationale { get; set; } public bool? IsActive { get; set; } } public sealed class PreviewRequest { public string? BusinessCategory { get; set; } public string? Objective { get; set; } public string? AgeSkew { get; set; } public string? MarketScope { get; set; } }