330 lines
12 KiB
C#
330 lines
12 KiB
C#
using System.Text.Json;
|
|
using License.Api.Data;
|
|
using License.Api.DTOs;
|
|
using License.Api.Models;
|
|
using License.Api.Services;
|
|
using License.Api.Utils;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace License.Api.Controllers;
|
|
|
|
[ApiController]
|
|
[Authorize(Policy = "SuperAdmin")]
|
|
[Route("api/admin/agents")]
|
|
public class AdminAgentsController : ControllerBase
|
|
{
|
|
private readonly AppDbContext _db;
|
|
private readonly ConfigService _config;
|
|
|
|
public AdminAgentsController(AppDbContext db, ConfigService config)
|
|
{
|
|
_db = db;
|
|
_config = config;
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<IActionResult> Create([FromBody] AgentCreateRequest request)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
var agent = new Agent
|
|
{
|
|
AdminId = request.AdminId,
|
|
AgentCode = request.AgentCode,
|
|
CompanyName = request.CompanyName,
|
|
ContactPerson = request.ContactPerson,
|
|
ContactPhone = request.ContactPhone,
|
|
ContactEmail = request.ContactEmail,
|
|
PasswordHash = PasswordHasher.Hash(request.Password),
|
|
Balance = request.InitialBalance,
|
|
Discount = request.Discount,
|
|
CreditLimit = request.CreditLimit,
|
|
MaxProjects = request.AllowedProjects?.Count ?? 0,
|
|
AllowedProjects = request.AllowedProjects == null ? null : JsonSerializer.Serialize(request.AllowedProjects),
|
|
Status = "active",
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_db.Agents.Add(agent);
|
|
await _db.SaveChangesAsync();
|
|
|
|
var data = new AgentDetailResponse
|
|
{
|
|
Id = agent.Id,
|
|
AgentCode = agent.AgentCode,
|
|
CompanyName = agent.CompanyName,
|
|
ContactPerson = agent.ContactPerson,
|
|
ContactPhone = agent.ContactPhone,
|
|
ContactEmail = agent.ContactEmail,
|
|
Balance = agent.Balance,
|
|
Discount = agent.Discount,
|
|
CreditLimit = agent.CreditLimit,
|
|
Status = agent.Status,
|
|
CreatedAt = agent.CreatedAt
|
|
};
|
|
|
|
return Ok(ApiResponse<AgentDetailResponse>.Ok(data));
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<IActionResult> List([FromQuery] string? status, [FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
page = Math.Max(1, page);
|
|
pageSize = Math.Clamp(pageSize, 1, 100);
|
|
|
|
var query = _db.Agents.AsQueryable();
|
|
if (!string.IsNullOrWhiteSpace(status))
|
|
query = query.Where(a => a.Status == status);
|
|
|
|
var total = await query.CountAsync();
|
|
var items = await query.OrderByDescending(a => a.CreatedAt)
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.Select(a => new AgentListItem
|
|
{
|
|
Id = a.Id,
|
|
AgentCode = a.AgentCode,
|
|
CompanyName = a.CompanyName,
|
|
ContactPerson = a.ContactPerson,
|
|
ContactPhone = a.ContactPhone,
|
|
Balance = a.Balance,
|
|
Discount = a.Discount,
|
|
Status = a.Status,
|
|
CreatedAt = a.CreatedAt
|
|
})
|
|
.ToListAsync();
|
|
|
|
var result = new PagedResult<AgentListItem>
|
|
{
|
|
Items = items,
|
|
Pagination = new PaginationInfo
|
|
{
|
|
Page = page,
|
|
PageSize = pageSize,
|
|
Total = total,
|
|
TotalPages = (int)Math.Ceiling(total / (double)pageSize)
|
|
}
|
|
};
|
|
|
|
return Ok(ApiResponse<PagedResult<AgentListItem>>.Ok(result));
|
|
}
|
|
|
|
[HttpGet("{id:int}")]
|
|
public async Task<IActionResult> Get(int id)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
var agent = await _db.Agents.FindAsync(id);
|
|
if (agent == null)
|
|
return NotFound(ApiResponse.Fail(404, "not_found"));
|
|
|
|
var transactions = await _db.AgentTransactions
|
|
.Where(t => t.AgentId == id)
|
|
.OrderByDescending(t => t.CreatedAt)
|
|
.Take(50)
|
|
.ToListAsync();
|
|
|
|
var stats = await _db.CardKeys
|
|
.Where(c => c.AgentId == id && c.DeletedAt == null)
|
|
.GroupBy(c => c.AgentId)
|
|
.Select(g => new
|
|
{
|
|
totalCards = g.Count(),
|
|
activeCards = g.Count(x => x.Status == "active"),
|
|
totalRevenue = g.Sum(x => x.SoldPrice ?? 0)
|
|
})
|
|
.SingleOrDefaultAsync();
|
|
|
|
return Ok(ApiResponse<object>.Ok(new
|
|
{
|
|
agent = new AgentDetailResponse
|
|
{
|
|
Id = agent.Id,
|
|
AgentCode = agent.AgentCode,
|
|
CompanyName = agent.CompanyName,
|
|
ContactPerson = agent.ContactPerson,
|
|
ContactPhone = agent.ContactPhone,
|
|
ContactEmail = agent.ContactEmail,
|
|
Balance = agent.Balance,
|
|
Discount = agent.Discount,
|
|
CreditLimit = agent.CreditLimit,
|
|
Status = agent.Status,
|
|
CreatedAt = agent.CreatedAt
|
|
},
|
|
stats,
|
|
transactions
|
|
}));
|
|
}
|
|
|
|
[HttpPut("{id:int}")]
|
|
public async Task<IActionResult> Update(int id, [FromBody] AgentUpdateRequest request)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
var agent = await _db.Agents.FindAsync(id);
|
|
if (agent == null)
|
|
return NotFound(ApiResponse.Fail(404, "not_found"));
|
|
|
|
if (!string.IsNullOrWhiteSpace(request.CompanyName))
|
|
agent.CompanyName = request.CompanyName;
|
|
if (!string.IsNullOrWhiteSpace(request.ContactPerson))
|
|
agent.ContactPerson = request.ContactPerson;
|
|
if (!string.IsNullOrWhiteSpace(request.ContactPhone))
|
|
agent.ContactPhone = request.ContactPhone;
|
|
if (!string.IsNullOrWhiteSpace(request.ContactEmail))
|
|
agent.ContactEmail = request.ContactEmail;
|
|
if (request.Discount.HasValue)
|
|
agent.Discount = request.Discount.Value;
|
|
if (request.CreditLimit.HasValue)
|
|
agent.CreditLimit = request.CreditLimit.Value;
|
|
if (request.AllowedProjects != null)
|
|
{
|
|
agent.AllowedProjects = JsonSerializer.Serialize(request.AllowedProjects);
|
|
agent.MaxProjects = request.AllowedProjects.Count;
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(request.Status))
|
|
agent.Status = request.Status;
|
|
|
|
agent.UpdatedAt = DateTime.UtcNow;
|
|
await _db.SaveChangesAsync();
|
|
|
|
return Ok(ApiResponse.Ok());
|
|
}
|
|
|
|
[HttpPost("{id:int}/disable")]
|
|
public async Task<IActionResult> Disable(int id)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
var agent = await _db.Agents.FindAsync(id);
|
|
if (agent == null)
|
|
return NotFound(ApiResponse.Fail(404, "not_found"));
|
|
|
|
agent.Status = "disabled";
|
|
agent.UpdatedAt = DateTime.UtcNow;
|
|
await _db.SaveChangesAsync();
|
|
return Ok(ApiResponse.Ok());
|
|
}
|
|
|
|
[HttpPost("{id:int}/enable")]
|
|
public async Task<IActionResult> Enable(int id)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
var agent = await _db.Agents.FindAsync(id);
|
|
if (agent == null)
|
|
return NotFound(ApiResponse.Fail(404, "not_found"));
|
|
|
|
agent.Status = "active";
|
|
agent.UpdatedAt = DateTime.UtcNow;
|
|
await _db.SaveChangesAsync();
|
|
return Ok(ApiResponse.Ok());
|
|
}
|
|
|
|
[HttpDelete("{id:int}")]
|
|
public async Task<IActionResult> Delete(int id)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
var agent = await _db.Agents.FindAsync(id);
|
|
if (agent == null)
|
|
return NotFound(ApiResponse.Fail(404, "not_found"));
|
|
|
|
_db.Agents.Remove(agent);
|
|
await _db.SaveChangesAsync();
|
|
return Ok(ApiResponse.Ok());
|
|
}
|
|
|
|
[HttpPost("{id:int}/recharge")]
|
|
public async Task<IActionResult> Recharge(int id, [FromBody] AgentBalanceRequest request)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
return await AdjustBalance(id, request.Amount, "recharge", request.Remark);
|
|
}
|
|
|
|
[HttpPost("{id:int}/deduct")]
|
|
public async Task<IActionResult> Deduct(int id, [FromBody] AgentBalanceRequest request)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
return await AdjustBalance(id, -Math.Abs(request.Amount), "consume", request.Remark);
|
|
}
|
|
|
|
[HttpGet("{id:int}/transactions")]
|
|
public async Task<IActionResult> Transactions(int id)
|
|
{
|
|
var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true);
|
|
if (!agentSystemEnabled)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
var items = await _db.AgentTransactions
|
|
.Where(t => t.AgentId == id)
|
|
.OrderByDescending(t => t.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
return Ok(ApiResponse<List<AgentTransaction>>.Ok(items));
|
|
}
|
|
|
|
private async Task<IActionResult> AdjustBalance(int id, decimal amount, string type, string? remark)
|
|
{
|
|
if (!User.TryGetUserId(out var adminId))
|
|
return Unauthorized(ApiResponse.Fail(401, "unauthorized"));
|
|
|
|
await using var tx = await _db.Database.BeginTransactionAsync();
|
|
var agent = await _db.Agents
|
|
.FromSqlRaw("SELECT * FROM \"Agents\" WHERE \"Id\" = {0} FOR UPDATE", id)
|
|
.FirstOrDefaultAsync();
|
|
if (agent == null)
|
|
return NotFound(ApiResponse.Fail(404, "not_found"));
|
|
|
|
var balanceBefore = agent.Balance;
|
|
var balanceAfter = balanceBefore + amount;
|
|
if (balanceAfter < -agent.CreditLimit)
|
|
return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden"));
|
|
|
|
agent.Balance = balanceAfter;
|
|
agent.UpdatedAt = DateTime.UtcNow;
|
|
_db.AgentTransactions.Add(new AgentTransaction
|
|
{
|
|
AgentId = agent.Id,
|
|
Type = type,
|
|
Amount = amount,
|
|
BalanceBefore = balanceBefore,
|
|
BalanceAfter = balanceAfter,
|
|
Remark = remark,
|
|
CreatedBy = adminId,
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
|
|
await _db.SaveChangesAsync();
|
|
await tx.CommitAsync();
|
|
|
|
return Ok(ApiResponse.Ok());
|
|
}
|
|
}
|