Initial commit
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user