221 lines
9.6 KiB
C#
221 lines
9.6 KiB
C#
using Management.Data;
|
|
using Management.Security;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Management.Controllers.Admin;
|
|
|
|
/// <summary>
|
|
/// Admin endpoints for template configuration metadata —
|
|
/// business categories and objective display properties.
|
|
///
|
|
/// Both are managed through a single stored procedure
|
|
/// (spAdminTemplateConfig) with action-based routing.
|
|
///
|
|
/// CHANNEL ENDPOINTS:
|
|
/// GET /api/admin/template-config/channels - List channels (with mapping/template counts)
|
|
/// GET /api/admin/template-config/channels/{id} - Get channel
|
|
/// POST /api/admin/template-config/channels - Create channel
|
|
/// PUT /api/admin/template-config/channels/{id} - Update channel (code rename cascades to mappings + templates)
|
|
/// DELETE /api/admin/template-config/channels/{id} - Delete channel (blocked if references exist)
|
|
///
|
|
/// CATEGORY ENDPOINTS:
|
|
/// GET /api/admin/template-config/categories - List categories (with template counts)
|
|
/// GET /api/admin/template-config/categories/{id} - Get category
|
|
/// POST /api/admin/template-config/categories - Create category
|
|
/// PUT /api/admin/template-config/categories/{id} - Update category (rename cascades to templates)
|
|
/// DELETE /api/admin/template-config/categories/{id} - Delete category (blocked if templates exist)
|
|
///
|
|
/// OBJECTIVE ENDPOINTS:
|
|
/// GET /api/admin/template-config/objectives - List objectives (with template counts + mapping status)
|
|
/// GET /api/admin/template-config/objectives/{id} - Get objective
|
|
/// PUT /api/admin/template-config/objectives/{id} - Update objective display properties (color, sort)
|
|
///
|
|
/// NOTE: Objectives are a controlled set — they cannot be created or deleted
|
|
/// because they must stay in sync with tbObjectiveMapping and spInitiative
|
|
/// validation. Only display properties (color, sortOrder, isActive) are editable.
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/admin/template-config")]
|
|
public sealed class AdminTemplateConfigController : AdminControllerBase
|
|
{
|
|
public AdminTemplateConfigController(SqlService sql, ClientContext client, ILogger<AdminTemplateConfigController> log)
|
|
: base(sql, client, log) { }
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// CATEGORIES
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
[HttpGet("categories")]
|
|
public Task<IActionResult> ListCategories(CancellationToken ct = default)
|
|
=> CallProc("spAdminTemplateConfig", "categories.list", new { }, ct);
|
|
|
|
[HttpGet("categories/{categoryId:int}")]
|
|
public Task<IActionResult> GetCategory(int categoryId, CancellationToken ct)
|
|
=> CallProc("spAdminTemplateConfig", "categories.get", new { categoryId }, ct);
|
|
|
|
[HttpPost("categories")]
|
|
public Task<IActionResult> CreateCategory([FromBody] CreateCategoryRequest request, CancellationToken ct)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request?.Name))
|
|
return Task.FromResult(ValidationError("name is required"));
|
|
|
|
Logger.LogWarning("[Admin] CreateCategory | Name={Name} | By={User}",
|
|
request.Name, Client.Email);
|
|
|
|
return CallProc("spAdminTemplateConfig", "categories.create", new
|
|
{
|
|
name = request.Name.Trim(),
|
|
icon = request.Icon?.Trim(),
|
|
sortOrder = request.SortOrder
|
|
}, ct);
|
|
}
|
|
|
|
[HttpPut("categories/{categoryId:int}")]
|
|
public Task<IActionResult> UpdateCategory(int categoryId, [FromBody] UpdateCategoryRequest request, CancellationToken ct)
|
|
{
|
|
Logger.LogWarning("[Admin] UpdateCategory | Id={Id} | By={User}", categoryId, Client.Email);
|
|
|
|
return CallProc("spAdminTemplateConfig", "categories.update", new
|
|
{
|
|
categoryId,
|
|
name = request?.Name?.Trim(),
|
|
icon = request?.Icon?.Trim(),
|
|
sortOrder = request?.SortOrder,
|
|
isActive = request?.IsActive
|
|
}, ct);
|
|
}
|
|
|
|
[HttpDelete("categories/{categoryId:int}")]
|
|
public Task<IActionResult> DeleteCategory(int categoryId, CancellationToken ct)
|
|
{
|
|
Logger.LogWarning("[Admin] DeleteCategory | Id={Id} | By={User}", categoryId, Client.Email);
|
|
return CallProc("spAdminTemplateConfig", "categories.delete", new { categoryId }, ct);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// OBJECTIVES
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
[HttpGet("objectives")]
|
|
public Task<IActionResult> ListObjectives(CancellationToken ct = default)
|
|
=> CallProc("spAdminTemplateConfig", "objectives.list", new { }, ct);
|
|
|
|
[HttpGet("objectives/{objectiveId:int}")]
|
|
public Task<IActionResult> GetObjective(int objectiveId, CancellationToken ct)
|
|
=> CallProc("spAdminTemplateConfig", "objectives.get", new { objectiveId }, ct);
|
|
|
|
[HttpPut("objectives/{objectiveId:int}")]
|
|
public Task<IActionResult> UpdateObjective(int objectiveId, [FromBody] UpdateObjectiveRequest request, CancellationToken ct)
|
|
{
|
|
Logger.LogWarning("[Admin] UpdateObjective | Id={Id} | By={User}", objectiveId, Client.Email);
|
|
|
|
return CallProc("spAdminTemplateConfig", "objectives.update", new
|
|
{
|
|
objectiveId,
|
|
color = request?.Color?.Trim(),
|
|
sortOrder = request?.SortOrder,
|
|
isActive = request?.IsActive
|
|
}, ct);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// CHANNELS
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
[HttpGet("channels")]
|
|
public Task<IActionResult> ListChannels(CancellationToken ct = default)
|
|
=> CallProc("spAdminTemplateConfig", "channels.list", new { }, ct);
|
|
|
|
[HttpGet("channels/{channelId:int}")]
|
|
public Task<IActionResult> GetChannel(int channelId, CancellationToken ct)
|
|
=> CallProc("spAdminTemplateConfig", "channels.get", new { channelId }, ct);
|
|
|
|
[HttpPost("channels")]
|
|
public Task<IActionResult> CreateChannel([FromBody] CreateChannelRequest request, CancellationToken ct)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request?.Code))
|
|
return Task.FromResult(ValidationError("code is required"));
|
|
if (string.IsNullOrWhiteSpace(request?.Label))
|
|
return Task.FromResult(ValidationError("label is required"));
|
|
|
|
Logger.LogWarning("[Admin] CreateChannel | Code={Code} | By={User}",
|
|
request.Code, Client.Email);
|
|
|
|
return CallProc("spAdminTemplateConfig", "channels.create", new
|
|
{
|
|
code = request.Code.Trim().ToLowerInvariant(),
|
|
label = request.Label.Trim(),
|
|
color = request.Color?.Trim(),
|
|
icon = request.Icon?.Trim(),
|
|
sortOrder = request.SortOrder
|
|
}, ct);
|
|
}
|
|
|
|
[HttpPut("channels/{channelId:int}")]
|
|
public Task<IActionResult> UpdateChannel(int channelId, [FromBody] UpdateChannelRequest request, CancellationToken ct)
|
|
{
|
|
Logger.LogWarning("[Admin] UpdateChannel | Id={Id} | By={User}", channelId, Client.Email);
|
|
|
|
return CallProc("spAdminTemplateConfig", "channels.update", new
|
|
{
|
|
channelId,
|
|
code = request?.Code?.Trim()?.ToLowerInvariant(),
|
|
label = request?.Label?.Trim(),
|
|
color = request?.Color?.Trim(),
|
|
icon = request?.Icon?.Trim(),
|
|
sortOrder = request?.SortOrder,
|
|
isActive = request?.IsActive
|
|
}, ct);
|
|
}
|
|
|
|
[HttpDelete("channels/{channelId:int}")]
|
|
public Task<IActionResult> DeleteChannel(int channelId, CancellationToken ct)
|
|
{
|
|
Logger.LogWarning("[Admin] DeleteChannel | Id={Id} | By={User}", channelId, Client.Email);
|
|
return CallProc("spAdminTemplateConfig", "channels.delete", new { channelId }, ct);
|
|
}
|
|
}
|
|
|
|
// ─── DTOs ───────────────────────────────────────────────────
|
|
|
|
public sealed class CreateCategoryRequest
|
|
{
|
|
public string? Name { get; set; }
|
|
public string? Icon { get; set; }
|
|
public int? SortOrder { get; set; }
|
|
}
|
|
|
|
public sealed class UpdateCategoryRequest
|
|
{
|
|
public string? Name { get; set; }
|
|
public string? Icon { get; set; }
|
|
public int? SortOrder { get; set; }
|
|
public bool? IsActive { get; set; }
|
|
}
|
|
|
|
public sealed class UpdateObjectiveRequest
|
|
{
|
|
public string? Color { get; set; }
|
|
public int? SortOrder { get; set; }
|
|
public bool? IsActive { get; set; }
|
|
}
|
|
|
|
public sealed class CreateChannelRequest
|
|
{
|
|
public string? Code { get; set; }
|
|
public string? Label { get; set; }
|
|
public string? Color { get; set; }
|
|
public string? Icon { get; set; }
|
|
public int? SortOrder { get; set; }
|
|
}
|
|
|
|
public sealed class UpdateChannelRequest
|
|
{
|
|
public string? Code { get; set; }
|
|
public string? Label { get; set; }
|
|
public string? Color { get; set; }
|
|
public string? Icon { get; set; }
|
|
public int? SortOrder { get; set; }
|
|
public bool? IsActive { get; set; }
|
|
}
|