using System.Linq.Expressions;
using Fake.Application;
using Fake.Domain.Exceptions;
using Fake.Rbac.Application.Dtos.Role;
using Fake.Rbac.Application.Dtos.User;
using Fake.Rbac.Application.Services;
using Fake.Rbac.Infrastructure;
using Fake.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Oms.Application.Dtos.Agents;
using Oms.Application.Dtos.Shared;
using Oms.Application.Shared;
using Oms.Domain.AgentsAggregate.Consts;
using Oms.Domain.AgentsAggregate.Entities;
using Oms.Domain.AgentsAggregate.Repositories;
using Oms.Domain.CustomersAggregate.Repositories;
using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.Shared.Enums;
namespace Oms.Application.Services.Agents;
///
/// 渠道代理管理端 API(开户、规则、改绑、返佣明细)。
///
[ApiExplorerSettings(GroupName = "OMS-Agent")]
public class AgentAdminService : ApplicationService
{
public const string AgentRoleCode = "Agent";
private IAgentRepository AgentRepository => LazyServiceProvider.GetRequiredService();
private IAgentCommissionDetailRepository DetailRepository =>
LazyServiceProvider.GetRequiredService();
private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService();
private IOrderRepository OrderRepository => LazyServiceProvider.GetRequiredService();
private UserService UserService => LazyServiceProvider.GetRequiredService();
private RoleService RoleService => LazyServiceProvider.GetRequiredService();
private FakeRbacDbContext RbacDbContext => LazyServiceProvider.GetRequiredService();
/// 分页查询代理。
[HttpPost("/agent/admin/list")]
public async Task> GetListAsync(AgentListInput input)
{
var predicate = BuildAgentListPredicate(input);
var page = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var entities = await AgentRepository.GetPagedListAsync(
predicate,
page,
pageSize,
new Dictionary { [nameof(Agent.CreateTime)] = false, [nameof(Agent.Id)] = false });
var total = await AgentRepository.CountAsync(predicate);
return new OmsPageResult
{
Items = entities.Select(MapAgent).ToList(),
Total = total
};
}
/// 创建代理(同步 RBAC 用户 + Agent 角色)。
[HttpPost("/agent/admin/create")]
public async Task CreateAsync(AgentCreateInput input)
{
var code = input.AgentCode.Trim();
if (await AgentRepository.ExistsAgentCodeAsync(code))
{
throw new BusinessException("代理编号已存在");
}
EnsureBaseType(input.BaseType);
var roleId = await GetAgentRoleIdOrThrowAsync();
var user = await UserService.CreateAsync(new UserCreateDto
{
Name = input.AgentName.Trim(),
Account = code,
Password = input.Password,
Email = string.IsNullOrWhiteSpace(input.Email)
? $"{code}@agent.local"
: input.Email.Trim(),
RoleIds = []
});
try
{
await UserService.AssignRolesAsync(user.Id, [roleId]);
var userId = await GetRbacUserMapIdOrThrowAsync(user.Id);
await AgentRepository.InsertAsync(new Agent
{
UserId = userId,
AgentCode = code,
AgentName = input.AgentName.Trim(),
Phone = NormalizeOptional(input.Phone),
Email = NormalizeOptional(input.Email),
Password = OmsPasswordHelper.ToMd5(input.Password),
Status = EnableStatus.Enabled,
BaseType = input.BaseType.Trim().ToLowerInvariant(),
CommissionRate = input.CommissionRate,
SalesProductIds = NormalizeOptional(input.SalesProductIds),
Remark = NormalizeOptional(input.Remark)
});
}
catch
{
try
{
await UserService.DeleteAsync(user.Id);
}
catch
{
// ignore rollback failure
}
throw;
}
}
/// 更新代理资料与返佣规则。
[HttpPost("/agent/admin/update")]
public async Task UpdateAsync(AgentUpdateInput input)
{
var entity = await AgentRepository.FirstOrDefaultAsync(x => x.Id == input.Id)
?? throw new BusinessException("代理不存在");
EnsureBaseType(input.BaseType);
entity.AgentName = input.AgentName.Trim();
entity.Phone = NormalizeOptional(input.Phone);
entity.Email = NormalizeOptional(input.Email);
entity.Status = input.Status == EnableStatus.None ? EnableStatus.Enabled : input.Status;
entity.BaseType = input.BaseType.Trim().ToLowerInvariant();
entity.CommissionRate = input.CommissionRate;
entity.SalesProductIds = NormalizeOptional(input.SalesProductIds);
entity.Remark = NormalizeOptional(input.Remark);
if (!string.IsNullOrWhiteSpace(input.Password))
{
entity.Password = OmsPasswordHelper.ToMd5(input.Password);
}
await AgentRepository.UpdateAsync(entity);
}
/// 改绑主客户所属代理(仅影响之后新单)。
[HttpPost("/agent/admin/rebind")]
public async Task RebindCustomerAsync(AgentRebindCustomerInput input)
{
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == input.CustomerId)
?? throw new BusinessException("客户不存在");
if (customer.ParentCustomerId.HasValue)
{
throw new BusinessException("请改绑主客户");
}
if (input.AgentId.HasValue && input.AgentId.Value > 0)
{
var agent = await AgentRepository.FirstOrDefaultAsync(x => x.Id == input.AgentId.Value)
?? throw new BusinessException("代理不存在");
if (agent.Status != EnableStatus.Enabled)
{
throw new BusinessException("代理已停用");
}
customer.AgentId = agent.Id;
customer.AgentBindTime = DateTime.Now;
}
else
{
customer.AgentId = null;
customer.AgentBindTime = null;
}
await CustomerRepository.UpdateAsync(customer);
}
/// 分页查询返佣明细。
[HttpPost("/agent/admin/commission/list")]
public async Task> GetCommissionListAsync(AgentCommissionListInput input)
{
var predicate = BuildCommissionPredicate(input);
var page = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var entities = await DetailRepository.GetPagedListAsync(
predicate,
page,
pageSize,
new Dictionary
{
[nameof(AgentCommissionDetail.CreateTime)] = false,
[nameof(AgentCommissionDetail.Id)] = false
});
var total = await DetailRepository.CountAsync(predicate);
return new OmsPageResult
{
Items = await MapCommissionOutputsAsync(entities),
Total = total
};
}
/// 标记返佣明细为已打款。
[HttpPost("/agent/admin/commission/mark-paid")]
public async Task MarkCommissionPaidAsync(AgentCommissionMarkPaidInput input)
{
if (input.Ids == null || input.Ids.Count == 0)
{
throw new BusinessException("请选择明细");
}
var ids = input.Ids.Distinct().ToList();
var list = await DetailRepository.GetListAsync(x => ids.Contains(x.Id));
var now = DateTime.Now;
var remark = NormalizeOptional(input.Remark);
foreach (var item in list)
{
if (string.Equals(item.Status, AgentCommissionStatusCodes.Paid, StringComparison.OrdinalIgnoreCase))
{
continue;
}
item.Status = AgentCommissionStatusCodes.Paid;
item.PaidTime = now;
if (!string.IsNullOrWhiteSpace(remark))
{
item.Remark = remark;
}
await DetailRepository.UpdateAsync(item);
}
}
private static Expression> BuildAgentListPredicate(AgentListInput input)
{
var keyword = input.Keyword?.Trim();
var hasStatus = input.Status.HasValue && input.Status.Value != EnableStatus.None;
if (hasStatus && !string.IsNullOrWhiteSpace(keyword))
{
var status = input.Status!.Value;
return x => x.Status == status
&& (x.AgentCode.Contains(keyword!)
|| x.AgentName.Contains(keyword!)
|| (x.Phone != null && x.Phone.Contains(keyword!)));
}
if (hasStatus)
{
var status = input.Status!.Value;
return x => x.Status == status;
}
if (!string.IsNullOrWhiteSpace(keyword))
{
return x => x.AgentCode.Contains(keyword!)
|| x.AgentName.Contains(keyword!)
|| (x.Phone != null && x.Phone.Contains(keyword!));
}
return x => true;
}
private static Expression> BuildCommissionPredicate(AgentCommissionListInput input)
{
var status = input.Status?.Trim();
if (input.AgentId is > 0 && input.OrderId is > 0 && !string.IsNullOrWhiteSpace(status))
{
var agentId = input.AgentId.Value;
var orderId = input.OrderId.Value;
return x => x.AgentId == agentId && x.OrderId == orderId && x.Status == status;
}
if (input.AgentId is > 0 && input.OrderId is > 0)
{
var agentId = input.AgentId.Value;
var orderId = input.OrderId.Value;
return x => x.AgentId == agentId && x.OrderId == orderId;
}
if (input.AgentId is > 0 && !string.IsNullOrWhiteSpace(status))
{
var agentId = input.AgentId.Value;
return x => x.AgentId == agentId && x.Status == status;
}
if (input.OrderId is > 0 && !string.IsNullOrWhiteSpace(status))
{
var orderId = input.OrderId.Value;
return x => x.OrderId == orderId && x.Status == status;
}
if (input.AgentId is > 0)
{
var agentId = input.AgentId.Value;
return x => x.AgentId == agentId;
}
if (input.OrderId is > 0)
{
var orderId = input.OrderId.Value;
return x => x.OrderId == orderId;
}
if (!string.IsNullOrWhiteSpace(status))
{
return x => x.Status == status;
}
return x => true;
}
private async Task> MapCommissionOutputsAsync(
List entities)
{
if (entities.Count == 0)
{
return [];
}
var agentIds = entities.Select(x => x.AgentId).Distinct().ToList();
var customerIds = entities.Select(x => x.CustomerId).Distinct().ToList();
var orderIds = entities.Select(x => x.OrderId).Distinct().ToList();
var agents = await AgentRepository.GetListAsync(x => agentIds.Contains(x.Id));
var customers = await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
var orders = await OrderRepository.GetListAsync(x => orderIds.Contains(x.Id));
var agentMap = agents.ToDictionary(x => x.Id);
var customerMap = customers.ToDictionary(x => x.Id);
var orderMap = orders.ToDictionary(x => x.Id);
return entities.Select(x =>
{
agentMap.TryGetValue(x.AgentId, out var agent);
customerMap.TryGetValue(x.CustomerId, out var customer);
orderMap.TryGetValue(x.OrderId, out var order);
return new AgentCommissionDetailOutput
{
Id = x.Id,
AgentId = x.AgentId,
AgentCode = agent?.AgentCode,
AgentName = agent?.AgentName,
OrderId = x.OrderId,
ServiceNo = order?.ServiceNo,
CustomerId = x.CustomerId,
CustomerCode = customer?.CustomerCode,
CustomerName = customer?.CustomerName,
SalesProductId = x.SalesProductId,
BaseType = x.BaseType,
BaseAmount = x.BaseAmount,
CommissionRate = x.CommissionRate,
CommissionAmount = x.CommissionAmount,
Status = x.Status,
TriggerOrderStatus = x.TriggerOrderStatus,
PaidTime = x.PaidTime,
Remark = x.Remark,
CreateTime = x.CreateTime
};
}).ToList();
}
private static AgentOutput MapAgent(Agent x) => new()
{
Id = x.Id,
AgentCode = x.AgentCode,
AgentName = x.AgentName,
Phone = x.Phone,
Email = x.Email,
Status = x.Status,
BaseType = x.BaseType,
CommissionRate = x.CommissionRate,
SalesProductIds = x.SalesProductIds,
Remark = x.Remark,
CreateTime = x.CreateTime
};
private static void EnsureBaseType(string? baseType)
{
if (!AgentCommissionBaseTypes.IsSupported(baseType))
{
throw new BusinessException("基数类型仅支持 freight(运费)或 receivable(应收)");
}
}
private static string? NormalizeOptional(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private async Task GetAgentRoleIdOrThrowAsync()
{
var roles = await RoleService.GetAllRolesAsync();
var role = roles.FirstOrDefault(x =>
string.Equals(x.Code, AgentRoleCode, StringComparison.OrdinalIgnoreCase));
if (role == null)
{
throw new BusinessException("未找到 Agent 角色,请先执行种子或手动创建");
}
return role.Id;
}
private async Task GetRbacUserMapIdOrThrowAsync(Guid userId)
{
var sql = $"select `userid` as `Value` from `{FakeTables.User}` where `Id` = {{0}}";
var mapId = await RbacDbContext.Database.SqlQueryRaw(sql, userId).FirstOrDefaultAsync();
if (!mapId.HasValue || mapId.Value <= 0)
{
throw new BusinessException("RBAC 用户映射失败");
}
return mapId.Value;
}
}