using System.Linq.Expressions;
using Fake.Application;
using Fake.Rbac.Application.Dtos.Role;
using Fake.Rbac.Application.Dtos.User;
using Fake.Rbac.Application.Services;
using Fake.Rbac.Infrastructure;
using Fake.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Oms.Application.Dtos.Customers;
using Oms.Application.Dtos.Shared;
using Oms.Application.Shared;
using Oms.Domain.CustomersAggregate.Entities;
using Oms.Domain.CustomersAggregate.Enums;
using Oms.Domain.CustomersAggregate.Repositories;
namespace Oms.Application.Services.Customers;
///
/// 客户 API 服务。
///
[ApiExplorerSettings(GroupName = "OMS-Customer")]
public class CustomerService : ApplicationService
{
private const string CustomerRoleCode = "Customer";
private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService();
private UserService UserService => LazyServiceProvider.GetRequiredService();
private RoleService RoleService => LazyServiceProvider.GetRequiredService();
private FakeRbacDbContext RbacDbContext => LazyServiceProvider.GetRequiredService();
private OmsRbacUserDisplayNameResolver UserDisplayNameResolver =>
LazyServiceProvider.GetRequiredService();
///
/// 查询客户列表。
///
/// 入参:客户分页查询条件。
/// 客户分页结果,包含列表数据和总记录数。
///
/// 核心业务逻辑:
/// 1. 根据查询条件构造筛选规则。
/// 2. 查询目标数据并补齐关联展示字段。
/// 3. 按接口约定返回列表结果。
///
[HttpGet("/customer/list")]
public async Task> GetPaginatedListAsync([FromQuery] CustomerPaginatedInput input)
{
var predicate = BuildPaginatedPredicate(input);
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var entities = await CustomerRepository.GetPagedListAsync(
predicate,
pageIndex,
pageSize,
new Dictionary { [nameof(Customer.UpdateTime)] = false, [nameof(Customer.Id)] = false });
var totalCount = await CustomerRepository.CountAsync(predicate);
var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
entities.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId }));
var items = entities.Select(entity =>
{
var output = ObjectMapper.Map(entity);
output.CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, auditUserNameMap);
output.UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.UpdateUserId, auditUserNameMap);
output.UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime);
return output;
}).ToList();
return new OmsPageResult
{
Items = items,
Total = totalCount
};
}
///
/// 查询客户详情。
///
/// 入参:目标主键。
/// 客户详情。
///
/// 核心业务逻辑:
/// 1. 根据标识查询目标对象。
/// 2. 目标不存在时抛出业务异常。
/// 3. 映射并返回详情结果。
///
[HttpGet("/customer/get")]
public async Task GetByIdAsync([FromQuery] int id)
{
var entity = await GetCustomerOrThrowAsync(id);
var output = ObjectMapper.Map(entity);
var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
[entity.CreateUserId, entity.UpdateUserId]);
output.CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, auditUserNameMap);
output.UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.UpdateUserId, auditUserNameMap);
output.UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime);
return output;
}
///
/// 新增客户。
///
/// 入参:客户创建参数。
/// 无返回体,仅完成新增客户。
///
/// 核心业务逻辑:
/// 1. 校验入参与关联对象、唯一性等前置条件。
/// 2. 组装目标实体及相关明细数据。
/// 3. 写入审计字段并持久化结果。
///
[HttpPost("/customer/create")]
public async Task CreateAsync(CreateCustomerInput input)
{
var normalized = await NormalizeCreateCustomerInputAsync(input, "客户编号", "客户名称");
await EnsureCustomerIdentityUniqueAsync(normalized.CustomerCode, normalized.Phone, normalized.Email);
var customerRoleId = await GetCustomerRoleIdOrThrowAsync();
var user = await CreateRbacUserAsync(input, normalized);
try
{
await UserService.AssignRolesAsync(user.Id, [customerRoleId]);
var userId = await GetRbacUserMapIdOrThrowAsync(user.Id);
var entity = BuildCustomerEntity(input, normalized, null, userId);
await CustomerRepository.InsertAsync(entity);
}
catch
{
await TryRollbackRbacUserAsync(user.Id);
throw;
}
}
///
/// 创建子客户。
///
/// 入参:子客户基础资料、登录密码和授权权限编码列表。
/// 无返回体,仅完成子客户创建和RBAC用户、角色绑定。
[HttpPost("/customer/sub/create")]
public async Task CreateSubCustomerAsync([FromBody] CreateSubCustomerInput input)
{
var mainCustomer = await GetCurrentMainCustomerOrThrowAsync();
ValidateCustomerStatus(mainCustomer);
var normalized = await NormalizeCreateCustomerInputAsync(input, "子客户编号", "子客户名称");
var permissions = await NormalizeAndValidateSubCustomerPermissionsAsync(input.Permissions);
await EnsureCustomerIdentityUniqueAsync(normalized.CustomerCode, normalized.Phone, normalized.Email);
var user = await CreateRbacUserAsync(input, normalized);
Guid? roleId = null;
try
{
var userId = await GetRbacUserMapIdOrThrowAsync(user.Id);
roleId = await CreateSubCustomerRoleAsync(userId, normalized.CustomerName, permissions);
await UserService.AssignRolesAsync(user.Id, [roleId.Value]);
var entity = BuildCustomerEntity(input, normalized, mainCustomer.Id, userId);
await CustomerRepository.InsertAsync(entity);
}
catch
{
if (roleId.HasValue)
{
await TryRollbackRbacRoleAsync(roleId.Value);
}
await TryRollbackRbacUserAsync(user.Id);
throw;
}
}
///
/// 更新客户。
///
/// 入参:客户更新参数。
/// 无返回体,仅完成更新客户。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象。
/// 2. 按入参更新主体数据和关联配置。
/// 3. 写入更新审计字段并保存结果。
///
[HttpPost("/customer/update")]
public async Task UpdateAsync(UpdateCustomerInput input)
{
var entity = await GetCustomerOrThrowAsync(input.Id);
var customerCode = NormalizeRequiredText(input.CustomerCode, "客户编号");
var customerName = NormalizeRequiredText(input.CustomerName, "客户名称");
var phone = NormalizeRequiredText(input.Phone, "手机号");
var email = NormalizeOptionalText(input.Email);
await EnsureCustomerIdentityUniqueForUpdateAsync(entity.Id, customerCode, phone, email);
entity.CustomerCode = customerCode;
entity.CustomerName = customerName;
entity.Phone = phone;
entity.Email = email;
entity.WechatAvatar = NormalizeOptionalText(input.WechatAvatar);
entity.WechatNickname = NormalizeOptionalText(input.WechatNickname);
entity.Balance = input.Balance;
entity.TotalQuota = input.TotalQuota;
entity.UsedQuota = input.UsedQuota;
entity.Status = input.Status;
entity.Remark = NormalizeOptionalText(input.Remark);
entity.Salesman = NormalizeOptionalText(input.Salesman);
entity.CustomerService = NormalizeOptionalText(input.CustomerService);
entity.CustomerGroup = input.CustomerGroup;
await CustomerRepository.UpdateAsync(entity);
}
///
/// 删除客户。
///
/// 入参:目标主键。
/// 无返回体,仅完成删除客户。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象。
/// 2. 执行软删除或删除关联明细。
/// 3. 持久化删除结果。
///
[HttpPost("/customer/delete")]
public async Task DeleteAsync(IdInput input)
{
var entity = await GetCustomerOrThrowAsync(input.Id);
await CustomerRepository.DeleteAsync(entity);
}
///
/// 重置客户密码。
///
/// 入参:客户密码重置参数。
/// 无返回体,仅完成重置客户密码。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象及当前状态。
/// 2. 执行业务状态流转或敏感字段更新。
/// 3. 写入审计字段并保存结果。
///
[HttpPost("/customer/reset-password")]
public async Task ResetPasswordAsync(ResetCustomerPasswordInput input)
{
var entity = await GetCustomerOrThrowAsync(input.Id);
entity.Password = OmsPasswordHelper.ToMd5(input.NewPassword);
await CustomerRepository.UpdateAsync(entity);
}
private Expression> BuildPaginatedPredicate(CustomerPaginatedInput input)
{
var keyword = input.Keyword?.Trim();
var salesman = input.Salesman?.Trim();
var customerService = input.CustomerService?.Trim();
var hasStatusFilter = input.Status.HasValue;
var statusValue = input.Status.GetValueOrDefault();
var hasCustomerGroupFilter = input.CustomerGroup.HasValue;
var customerGroupValue = input.CustomerGroup.GetValueOrDefault();
return customer =>
(string.IsNullOrWhiteSpace(keyword) ||
customer.CustomerCode.Contains(keyword) ||
customer.CustomerName.Contains(keyword) ||
customer.Phone.Contains(keyword) ||
(customer.Email ?? string.Empty).Contains(keyword)) &&
(!hasStatusFilter || customer.Status == statusValue) &&
(!hasCustomerGroupFilter || customer.CustomerGroup == customerGroupValue) &&
(string.IsNullOrWhiteSpace(salesman) || customer.Salesman == salesman) &&
(string.IsNullOrWhiteSpace(customerService) || customer.CustomerService == customerService);
}
private async Task GetCustomerOrThrowAsync(int id)
{
var entity = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == id);
if (entity == null)
{
throw new BusinessException("客户不存在");
}
return entity;
}
private async Task GetRbacUserMapIdOrThrowAsync(Guid id)
{
var userId = await RbacDbContext.Database.SqlQuery(
$"select `userid` as `Value` from `user` where `Id` = {id}")
.FirstOrDefaultAsync();
if (!userId.HasValue || userId.Value <= 0)
{
throw new BusinessException("用户编号创建失败");
}
return userId.Value;
}
private async Task GetCustomerRoleIdOrThrowAsync()
{
var roles = await RoleService.GetAllRolesAsync();
var role = roles.FirstOrDefault(x => string.Equals(x.Code, CustomerRoleCode, StringComparison.OrdinalIgnoreCase));
if (role == null)
{
throw new BusinessException("客户角色未初始化");
}
return role.Id;
}
private async Task GetCurrentMainCustomerOrThrowAsync()
{
var userId = CurrentUser.GetId();
if (userId <= 0)
{
throw new BusinessException("客户未登录");
}
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.UserId == userId && x.ParentCustomerId == null);
if (customer == null)
{
throw new BusinessException("仅主客户可以管理子客户");
}
return customer;
}
private async Task EnsureCustomerIdentityUniqueAsync(string customerCode, string phone, string email)
{
if (await CustomerRepository.GetByCustomerCodeAsync(customerCode) != null)
{
throw new BusinessException("客户编号已存在");
}
if (await CustomerRepository.GetByPhoneAsync(phone) != null)
{
throw new BusinessException("手机号已存在");
}
if (await CustomerRepository.GetByEmailAsync(email) != null)
{
throw new BusinessException("邮箱已存在");
}
}
private async Task EnsureCustomerIdentityUniqueForUpdateAsync(int id, string customerCode, string phone, string? email)
{
var duplicatedCode = await CustomerRepository.GetByCustomerCodeAsync(customerCode);
if (duplicatedCode != null && duplicatedCode.Id != id)
{
throw new BusinessException("客户编号已存在");
}
var duplicatedPhone = await CustomerRepository.GetByPhoneAsync(phone);
if (duplicatedPhone != null && duplicatedPhone.Id != id)
{
throw new BusinessException("手机号已存在");
}
if (email == null)
{
return;
}
var duplicatedEmail = await CustomerRepository.GetByEmailAsync(email);
if (duplicatedEmail != null && duplicatedEmail.Id != id)
{
throw new BusinessException("邮箱已存在");
}
}
private async Task CreateRbacUserAsync(CreateCustomerInput input, NormalizedCustomerInput normalized)
{
return await UserService.CreateAsync(new UserCreateDto
{
Name = normalized.CustomerName,
Account = normalized.Phone,
Password = input.Password,
Email = normalized.Email,
Avatar = NormalizeOptionalText(input.WechatAvatar),
RoleIds = []
});
}
private static Customer BuildCustomerEntity(
CreateCustomerInput input,
NormalizedCustomerInput normalized,
int? parentCustomerId,
int userId)
{
return new Customer
{
ParentCustomerId = parentCustomerId,
UserId = userId,
CustomerCode = normalized.CustomerCode,
CustomerName = normalized.CustomerName,
WechatAvatar = NormalizeOptionalText(input.WechatAvatar),
WechatNickname = NormalizeOptionalText(input.WechatNickname),
Phone = normalized.Phone,
Email = normalized.Email,
Password = OmsPasswordHelper.ToMd5(input.Password),
Balance = input.Balance,
TotalQuota = input.TotalQuota,
UsedQuota = input.UsedQuota,
Status = input.Status,
Remark = NormalizeOptionalText(input.Remark),
Salesman = NormalizeOptionalText(input.Salesman),
CustomerService = NormalizeOptionalText(input.CustomerService),
CustomerGroup = input.CustomerGroup
};
}
private async Task NormalizeCreateCustomerInputAsync(
CreateCustomerInput input,
string codeFieldName,
string nameFieldName)
{
var customerCode = string.IsNullOrWhiteSpace(input.CustomerCode)
? await CustomerCodeGenerator.GenerateNextAsync(CustomerRepository)
: NormalizeRequiredText(input.CustomerCode, codeFieldName);
return new NormalizedCustomerInput(
customerCode,
NormalizeRequiredText(input.CustomerName, nameFieldName),
NormalizeRequiredText(input.Phone, "手机号"),
NormalizeRequiredText(input.Email, "邮箱"));
}
private async Task> NormalizeAndValidateSubCustomerPermissionsAsync(IEnumerable? permissions)
{
var requestedPermissions = (permissions ?? [])
.Select(NormalizeOptionalText)
.Where(x => x != null)
.Select(x => x!)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var currentUserId = CurrentUser.Id ?? throw new BusinessException("客户未登录");
var currentPermissions = await UserService.GetUserPermissionsAsync(currentUserId);
var currentPermissionSet = currentPermissions.ToHashSet(StringComparer.OrdinalIgnoreCase);
if (requestedPermissions.Any(x => !currentPermissionSet.Contains(x)))
{
throw new BusinessException("子客户权限不能超出当前客户权限");
}
return requestedPermissions;
}
private async Task CreateSubCustomerRoleAsync(int userId, string customerName, List permissions)
{
var role = await RoleService.CreateAsync(new RoleCreateDto
{
Code = $"CustomerSub_{userId}",
Name = $"子客户-{customerName}",
Permissions = permissions
});
return role.Id;
}
private async Task TryRollbackRbacUserAsync(Guid userId)
{
try
{
await UserService.DeleteAsync(userId);
}
catch
{
}
}
private async Task TryRollbackRbacRoleAsync(Guid roleId)
{
try
{
await RoleService.DeleteAsync(roleId);
}
catch
{
}
}
private static void ValidateCustomerStatus(Customer customer)
{
if (customer.Status == CustomerStatus.Disabled)
{
throw new BusinessException("客户已禁用");
}
if (customer.Status == CustomerStatus.Frozen)
{
throw new BusinessException("客户已冻结");
}
}
private static string NormalizeRequiredText(string? value, string fieldName)
{
var normalized = value?.Trim();
if (string.IsNullOrWhiteSpace(normalized))
{
throw new BusinessException($"{fieldName}不能为空");
}
return normalized;
}
private static string? NormalizeOptionalText(string? value)
{
var normalized = value?.Trim();
return string.IsNullOrWhiteSpace(normalized) ? null : normalized;
}
private sealed record NormalizedCustomerInput(
string CustomerCode,
string CustomerName,
string Phone,
string Email);
}