feat: 代理门户能力与客户绑定代理改绑
All checks were successful
Build and Deploy / deploy (push) Successful in 1m14s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m14s
补齐工作台/名下客户订单返佣、注册校验与运营端客户 AgentId 改绑,订单列表按代理快照过滤。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0c89ec6ce5
commit
b6831bad1d
@ -30,10 +30,16 @@ public class AgentOutput
|
|||||||
|
|
||||||
public string BaseType { get; set; } = string.Empty;
|
public string BaseType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>基数类型展示名(运费/应收)。</summary>
|
||||||
|
public string? BaseTypeName { get; set; }
|
||||||
|
|
||||||
public decimal CommissionRate { get; set; }
|
public decimal CommissionRate { get; set; }
|
||||||
|
|
||||||
public string? SalesProductIds { get; set; }
|
public string? SalesProductIds { get; set; }
|
||||||
|
|
||||||
|
/// <summary>适用销售产品名称;空表示不限产品。</summary>
|
||||||
|
public List<string> SalesProductNames { get; set; } = [];
|
||||||
|
|
||||||
public string? Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
public DateTime CreateTime { get; set; }
|
public DateTime CreateTime { get; set; }
|
||||||
@ -195,6 +201,33 @@ public class AgentPortalCustomerOutput
|
|||||||
public DateTime? AgentBindTime { get; set; }
|
public DateTime? AgentBindTime { get; set; }
|
||||||
|
|
||||||
public DateTime CreateTime { get; set; }
|
public DateTime CreateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>最近下单时间(含名下子客户)。</summary>
|
||||||
|
public DateTime? LastOrderTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>近 30 天运单数(含名下子客户,不含已删除)。</summary>
|
||||||
|
public int OrderCount30d { get; set; }
|
||||||
|
|
||||||
|
/// <summary>待付款运单数(含名下子客户)。</summary>
|
||||||
|
public int PendingPaymentCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>代理门户返佣明细分页筛选。</summary>
|
||||||
|
public class AgentPortalCommissionListInput
|
||||||
|
{
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
|
||||||
|
public int PageSize { get; set; } = 50;
|
||||||
|
|
||||||
|
/// <summary>pending / paid;空=全部。</summary>
|
||||||
|
public string? Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>运单号关键字。</summary>
|
||||||
|
public string? Keyword { get; set; }
|
||||||
|
|
||||||
|
public DateTime? CreateTimeStart { get; set; }
|
||||||
|
|
||||||
|
public DateTime? CreateTimeEnd { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AgentPortalOrderOutput
|
public class AgentPortalOrderOutput
|
||||||
@ -232,3 +265,92 @@ public class AgentPortalPageInput
|
|||||||
|
|
||||||
public string? Keyword { get; set; }
|
public string? Keyword { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>匿名校验代理编号(客户注册归因)。</summary>
|
||||||
|
public class AgentValidateCodeOutput
|
||||||
|
{
|
||||||
|
public bool Valid { get; set; }
|
||||||
|
|
||||||
|
public string? AgentCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>脱敏代理名称,供注册页展示。</summary>
|
||||||
|
public string? AgentNameMasked { get; set; }
|
||||||
|
|
||||||
|
public string? Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 代理工作台统计概览。
|
||||||
|
/// </summary>
|
||||||
|
public class AgentWorkplaceStatsOutput
|
||||||
|
{
|
||||||
|
/// <summary>名下主客户数。</summary>
|
||||||
|
public int CustomerCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>今日代客下单数。</summary>
|
||||||
|
public int TodayCreated { get; set; }
|
||||||
|
|
||||||
|
/// <summary>昨日代客下单数。</summary>
|
||||||
|
public int YesterdayCreated { get; set; }
|
||||||
|
|
||||||
|
/// <summary>较昨日新增百分比。</summary>
|
||||||
|
public decimal? DayOverDayGrowthPercent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>待付款运单数。</summary>
|
||||||
|
public int PendingPayment { get; set; }
|
||||||
|
|
||||||
|
/// <summary>运输中运单数。</summary>
|
||||||
|
public int Shipping { get; set; }
|
||||||
|
|
||||||
|
/// <summary>问题件运单数。</summary>
|
||||||
|
public int Problematic { get; set; }
|
||||||
|
|
||||||
|
/// <summary>派送成功运单数(全量状态桶)。</summary>
|
||||||
|
public int DeliverySuccess { get; set; }
|
||||||
|
|
||||||
|
/// <summary>待结算返佣笔数。</summary>
|
||||||
|
public int PendingCommissionCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>待结算返佣金额。</summary>
|
||||||
|
public decimal PendingCommissionAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>已打款返佣金额(累计)。</summary>
|
||||||
|
public decimal PaidCommissionAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>本周新增绑定主客户数(按 AgentBindTime)。</summary>
|
||||||
|
public int WeekNewCustomers { get; set; }
|
||||||
|
|
||||||
|
/// <summary>派送失败运单数。</summary>
|
||||||
|
public int DeliveryFailed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>按 OrderStatus 分组数量。</summary>
|
||||||
|
public Dictionary<string, int> Counts { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 代理工作台趋势点。
|
||||||
|
/// </summary>
|
||||||
|
public class AgentWorkplaceTrendPoint
|
||||||
|
{
|
||||||
|
public string Label { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public DateTime Start { get; set; }
|
||||||
|
|
||||||
|
public DateTime End { get; set; }
|
||||||
|
|
||||||
|
/// <summary>区间内代客下单数。</summary>
|
||||||
|
public int OrderCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>区间内计提返佣金额。</summary>
|
||||||
|
public decimal CommissionAmount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 代理工作台趋势。
|
||||||
|
/// </summary>
|
||||||
|
public class AgentWorkplaceTrendOutput
|
||||||
|
{
|
||||||
|
public string Dimension { get; set; } = "day";
|
||||||
|
|
||||||
|
public List<AgentWorkplaceTrendPoint> Points { get; set; } = [];
|
||||||
|
}
|
||||||
|
|||||||
@ -78,7 +78,7 @@ public class CustomerOutput
|
|||||||
public string? Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 销售人员。
|
/// 销售人员(遗留字段,管理端已改用 AgentId)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Salesman { get; set; }
|
public string? Salesman { get; set; }
|
||||||
|
|
||||||
@ -87,6 +87,15 @@ public class CustomerOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? CustomerService { get; set; }
|
public string? CustomerService { get; set; }
|
||||||
|
|
||||||
|
/// <summary>绑定渠道代理 Id。</summary>
|
||||||
|
public int? AgentId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>绑定代理编号。</summary>
|
||||||
|
public string? AgentCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>绑定代理名称。</summary>
|
||||||
|
public string? AgentName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 客户分组。
|
/// 客户分组。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -33,7 +33,7 @@ public class CustomerPaginatedInput
|
|||||||
public CustomerGroup? CustomerGroup { get; set; }
|
public CustomerGroup? CustomerGroup { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 销售人员。
|
/// 销售人员(遗留筛选)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Salesman { get; set; }
|
public string? Salesman { get; set; }
|
||||||
|
|
||||||
@ -42,6 +42,9 @@ public class CustomerPaginatedInput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? CustomerService { get; set; }
|
public string? CustomerService { get; set; }
|
||||||
|
|
||||||
|
/// <summary>按绑定代理 Id 筛选。</summary>
|
||||||
|
public int? AgentId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 排序字段:createTime / updateTime / customerCode / customerName / balance。
|
/// 排序字段:createTime / updateTime / customerCode / customerName / balance。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -78,7 +78,7 @@ public class CustomerPaginatedOutput
|
|||||||
public string? Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 销售人员。
|
/// 销售人员(遗留字段,管理端已改用 AgentId)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Salesman { get; set; }
|
public string? Salesman { get; set; }
|
||||||
|
|
||||||
@ -87,6 +87,15 @@ public class CustomerPaginatedOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? CustomerService { get; set; }
|
public string? CustomerService { get; set; }
|
||||||
|
|
||||||
|
/// <summary>绑定渠道代理 Id。</summary>
|
||||||
|
public int? AgentId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>绑定代理编号。</summary>
|
||||||
|
public string? AgentCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>绑定代理名称。</summary>
|
||||||
|
public string? AgentName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 客户分组。
|
/// 客户分组。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -81,7 +81,7 @@ public class UpdateCustomerInput
|
|||||||
public string? Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 销售人员。
|
/// 销售人员(遗留;新流程请用 AgentCode)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StringLength(32)]
|
[StringLength(32)]
|
||||||
public string? Salesman { get; set; }
|
public string? Salesman { get; set; }
|
||||||
@ -92,6 +92,12 @@ public class UpdateCustomerInput
|
|||||||
[StringLength(32)]
|
[StringLength(32)]
|
||||||
public string? CustomerService { get; set; }
|
public string? CustomerService { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渠道代理编号;传空串解绑;不传则保留原绑定。仅主客户可改。
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(32)]
|
||||||
|
public string? AgentCode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 客户分组(遗留字段,计价优先 MemberLevelId)。
|
/// 客户分组(遗留字段,计价优先 MemberLevelId)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ using Fake.Rbac.Application.Dtos.User;
|
|||||||
using Fake.Rbac.Application.Services;
|
using Fake.Rbac.Application.Services;
|
||||||
using Fake.Rbac.Infrastructure;
|
using Fake.Rbac.Infrastructure;
|
||||||
using Fake.EntityFrameworkCore;
|
using Fake.EntityFrameworkCore;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Oms.Application.Dtos.Agents;
|
using Oms.Application.Dtos.Agents;
|
||||||
@ -43,6 +44,42 @@ public class AgentAdminService : ApplicationService
|
|||||||
|
|
||||||
private FakeRbacDbContext RbacDbContext => LazyServiceProvider.GetRequiredService<FakeRbacDbContext>();
|
private FakeRbacDbContext RbacDbContext => LazyServiceProvider.GetRequiredService<FakeRbacDbContext>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 匿名校验代理编号(客户注册页归因 / 手填兜底)。
|
||||||
|
/// </summary>
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpGet("/agent/public/validate-code")]
|
||||||
|
public async Task<AgentValidateCodeOutput> ValidateCodeAsync([FromQuery] string code)
|
||||||
|
{
|
||||||
|
var trimmed = code?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(trimmed))
|
||||||
|
{
|
||||||
|
return new AgentValidateCodeOutput
|
||||||
|
{
|
||||||
|
Valid = false,
|
||||||
|
Message = "代理编号不能为空"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = await AgentRepository.GetByAgentCodeAsync(trimmed);
|
||||||
|
if (agent == null || agent.Status != EnableStatus.Enabled)
|
||||||
|
{
|
||||||
|
return new AgentValidateCodeOutput
|
||||||
|
{
|
||||||
|
Valid = false,
|
||||||
|
Message = "代理编号无效或已停用"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AgentValidateCodeOutput
|
||||||
|
{
|
||||||
|
Valid = true,
|
||||||
|
AgentCode = agent.AgentCode,
|
||||||
|
AgentNameMasked = MaskAgentName(agent.AgentName),
|
||||||
|
Message = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>分页查询代理。</summary>
|
/// <summary>分页查询代理。</summary>
|
||||||
[HttpPost("/agent/admin/list")]
|
[HttpPost("/agent/admin/list")]
|
||||||
public async Task<OmsPageResult<AgentOutput>> GetListAsync(AgentListInput input)
|
public async Task<OmsPageResult<AgentOutput>> GetListAsync(AgentListInput input)
|
||||||
@ -411,4 +448,15 @@ public class AgentAdminService : ApplicationService
|
|||||||
|
|
||||||
return mapId.Value;
|
return mapId.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string MaskAgentName(string? name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return "渠道代理";
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = name.Trim();
|
||||||
|
return text.Length <= 1 ? "*" : text[0] + new string('*', Math.Min(2, text.Length - 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,26 @@
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Fake.Domain.Exceptions;
|
using Fake.Domain.Exceptions;
|
||||||
using Fake.Security.Claims;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MiniExcelLibs;
|
||||||
using Oms.Application.Dtos.Agents;
|
using Oms.Application.Dtos.Agents;
|
||||||
using Oms.Application.Dtos.Shared;
|
using Oms.Application.Dtos.Shared;
|
||||||
using Oms.Application.Shared;
|
using Oms.Application.Shared;
|
||||||
|
using Oms.Domain.AgentsAggregate.Consts;
|
||||||
using Oms.Domain.AgentsAggregate.Entities;
|
using Oms.Domain.AgentsAggregate.Entities;
|
||||||
using Oms.Domain.AgentsAggregate.Repositories;
|
using Oms.Domain.AgentsAggregate.Repositories;
|
||||||
|
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||||
using Oms.Domain.CustomersAggregate.Entities;
|
using Oms.Domain.CustomersAggregate.Entities;
|
||||||
using Oms.Domain.CustomersAggregate.Repositories;
|
using Oms.Domain.CustomersAggregate.Repositories;
|
||||||
|
using Oms.Domain.OrdersAggregate;
|
||||||
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using Oms.Domain.OrdersAggregate.Entities;
|
using Oms.Domain.OrdersAggregate.Entities;
|
||||||
using Oms.Domain.OrdersAggregate.Repositories;
|
using Oms.Domain.OrdersAggregate.Repositories;
|
||||||
|
|
||||||
namespace Oms.Application.Services.Agents;
|
namespace Oms.Application.Services.Agents;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 渠道代理门户只读 API(名下客户 / 订单 / 返佣)。
|
/// 渠道代理门户 API(名下客户 / 订单 / 返佣 / 工作台)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiExplorerSettings(GroupName = "OMS-Agent")]
|
[ApiExplorerSettings(GroupName = "OMS-Agent")]
|
||||||
public class AgentPortalService : ApplicationService
|
public class AgentPortalService : ApplicationService
|
||||||
@ -30,11 +34,15 @@ public class AgentPortalService : ApplicationService
|
|||||||
|
|
||||||
private IOrderRepository OrderRepository => LazyServiceProvider.GetRequiredService<IOrderRepository>();
|
private IOrderRepository OrderRepository => LazyServiceProvider.GetRequiredService<IOrderRepository>();
|
||||||
|
|
||||||
/// <summary>当前代理资料。</summary>
|
private ISalesProductRepository SalesProductRepository =>
|
||||||
|
LazyServiceProvider.GetRequiredService<ISalesProductRepository>();
|
||||||
|
|
||||||
|
/// <summary>当前代理资料(含返佣规则展示字段)。</summary>
|
||||||
[HttpGet("/agent/portal/profile")]
|
[HttpGet("/agent/portal/profile")]
|
||||||
public async Task<AgentOutput> GetProfileAsync()
|
public async Task<AgentOutput> GetProfileAsync()
|
||||||
{
|
{
|
||||||
var agent = await GetCurrentAgentOrThrowAsync();
|
var agent = await GetCurrentAgentOrThrowAsync();
|
||||||
|
var productNames = await ResolveSalesProductNamesAsync(agent.SalesProductIds);
|
||||||
return new AgentOutput
|
return new AgentOutput
|
||||||
{
|
{
|
||||||
Id = agent.Id,
|
Id = agent.Id,
|
||||||
@ -44,14 +52,16 @@ public class AgentPortalService : ApplicationService
|
|||||||
Email = agent.Email,
|
Email = agent.Email,
|
||||||
Status = agent.Status,
|
Status = agent.Status,
|
||||||
BaseType = agent.BaseType,
|
BaseType = agent.BaseType,
|
||||||
|
BaseTypeName = ResolveBaseTypeName(agent.BaseType),
|
||||||
CommissionRate = agent.CommissionRate,
|
CommissionRate = agent.CommissionRate,
|
||||||
SalesProductIds = agent.SalesProductIds,
|
SalesProductIds = agent.SalesProductIds,
|
||||||
|
SalesProductNames = productNames,
|
||||||
Remark = agent.Remark,
|
Remark = agent.Remark,
|
||||||
CreateTime = agent.CreateTime
|
CreateTime = agent.CreateTime
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>名下客户分页(含主客户 AgentId=我)。</summary>
|
/// <summary>名下客户分页(含活跃度:最近下单 / 近30天票数 / 待付款数)。</summary>
|
||||||
[HttpPost("/agent/portal/customers/list")]
|
[HttpPost("/agent/portal/customers/list")]
|
||||||
public async Task<OmsPageResult<AgentPortalCustomerOutput>> GetCustomersAsync(AgentPortalPageInput input)
|
public async Task<OmsPageResult<AgentPortalCustomerOutput>> GetCustomersAsync(AgentPortalPageInput input)
|
||||||
{
|
{
|
||||||
@ -77,16 +87,25 @@ public class AgentPortalService : ApplicationService
|
|||||||
[nameof(Customer.Id)] = false
|
[nameof(Customer.Id)] = false
|
||||||
});
|
});
|
||||||
var total = await CustomerRepository.CountAsync(predicate);
|
var total = await CustomerRepository.CountAsync(predicate);
|
||||||
|
var activityMap = await BuildCustomerActivityMapAsync(agent.Id, entities.Select(x => x.Id).ToList());
|
||||||
|
|
||||||
return new OmsPageResult<AgentPortalCustomerOutput>
|
return new OmsPageResult<AgentPortalCustomerOutput>
|
||||||
{
|
{
|
||||||
Items = entities.Select(x => new AgentPortalCustomerOutput
|
Items = entities.Select(x =>
|
||||||
{
|
{
|
||||||
Id = x.Id,
|
activityMap.TryGetValue(x.Id, out var activity);
|
||||||
CustomerCode = x.CustomerCode,
|
return new AgentPortalCustomerOutput
|
||||||
CustomerName = MaskName(x.CustomerName) ?? string.Empty,
|
{
|
||||||
Phone = MaskPhone(x.Phone),
|
Id = x.Id,
|
||||||
AgentBindTime = x.AgentBindTime,
|
CustomerCode = x.CustomerCode,
|
||||||
CreateTime = x.CreateTime
|
CustomerName = MaskName(x.CustomerName) ?? string.Empty,
|
||||||
|
Phone = MaskPhone(x.Phone),
|
||||||
|
AgentBindTime = x.AgentBindTime,
|
||||||
|
CreateTime = x.CreateTime,
|
||||||
|
LastOrderTime = activity.LastOrderTime,
|
||||||
|
OrderCount30d = activity.OrderCount30d,
|
||||||
|
PendingPaymentCount = activity.PendingPaymentCount
|
||||||
|
};
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
Total = total
|
Total = total
|
||||||
};
|
};
|
||||||
@ -141,7 +160,6 @@ public class AgentPortalService : ApplicationService
|
|||||||
OrderStatus = x.OrderStatus,
|
OrderStatus = x.OrderStatus,
|
||||||
ConsigneeCountryName = x.ConsigneeCountryName,
|
ConsigneeCountryName = x.ConsigneeCountryName,
|
||||||
ReceivableSubtotal = x.ReceivableSubtotal,
|
ReceivableSubtotal = x.ReceivableSubtotal,
|
||||||
// 门户不返回应付:固定 null
|
|
||||||
PayableAmount = null,
|
PayableAmount = null,
|
||||||
CreateTime = x.CreateTime
|
CreateTime = x.CreateTime
|
||||||
};
|
};
|
||||||
@ -150,72 +168,384 @@ public class AgentPortalService : ApplicationService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>名下返佣明细。</summary>
|
/// <summary>
|
||||||
[HttpPost("/agent/portal/commission/list")]
|
/// 代理工作台统计:名下客户、运单状态桶、待结算/已打款返佣、本周新增绑定。
|
||||||
public async Task<OmsPageResult<AgentCommissionDetailOutput>> GetCommissionListAsync(AgentPortalPageInput input)
|
/// </summary>
|
||||||
|
[HttpGet("/agent/portal/workplace/stats")]
|
||||||
|
public async Task<AgentWorkplaceStatsOutput> GetWorkplaceStatsAsync()
|
||||||
{
|
{
|
||||||
var agent = await GetCurrentAgentOrThrowAsync();
|
var agent = await GetCurrentAgentOrThrowAsync();
|
||||||
Expression<Func<AgentCommissionDetail, bool>> predicate = x => x.AgentId == agent.Id;
|
var filter = new OrderListFilter { AgentId = agent.Id };
|
||||||
|
var rows = await OrderRepository.GetStatusCountsByFilterAsync(filter);
|
||||||
|
var counts = rows
|
||||||
|
.Where(x => !string.Equals(x.Status, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToDictionary(x => x.Status, x => x.Count, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
int GetCount(string status) =>
|
||||||
|
counts.TryGetValue(status, out var value) ? value : 0;
|
||||||
|
|
||||||
|
var today = DateTime.Today;
|
||||||
|
var tomorrow = today.AddDays(1);
|
||||||
|
var yesterday = today.AddDays(-1);
|
||||||
|
var todayCreated = await OrderRepository.CountByFilterAndCreateTimeRangeAsync(filter, today, tomorrow);
|
||||||
|
var yesterdayCreated = await OrderRepository.CountByFilterAndCreateTimeRangeAsync(filter, yesterday, today);
|
||||||
|
|
||||||
|
decimal? growth = null;
|
||||||
|
if (yesterdayCreated > 0)
|
||||||
|
{
|
||||||
|
growth = Math.Round((todayCreated - yesterdayCreated) * 100m / yesterdayCreated, 1);
|
||||||
|
}
|
||||||
|
else if (todayCreated > 0)
|
||||||
|
{
|
||||||
|
growth = 100m;
|
||||||
|
}
|
||||||
|
|
||||||
|
var customerCount = await CustomerRepository.CountAsync(x =>
|
||||||
|
x.AgentId == agent.Id && x.ParentCustomerId == null);
|
||||||
|
|
||||||
|
var weekStart = today.AddDays(-(((int)today.DayOfWeek + 6) % 7));
|
||||||
|
var weekNewCustomers = await CustomerRepository.CountAsync(x =>
|
||||||
|
x.AgentId == agent.Id
|
||||||
|
&& x.ParentCustomerId == null
|
||||||
|
&& x.AgentBindTime != null
|
||||||
|
&& x.AgentBindTime >= weekStart
|
||||||
|
&& x.AgentBindTime < tomorrow);
|
||||||
|
|
||||||
|
var pendingDetails = await DetailRepository.GetListAsync(x =>
|
||||||
|
x.AgentId == agent.Id && x.Status == AgentCommissionStatusCodes.Pending);
|
||||||
|
var paidDetails = await DetailRepository.GetListAsync(x =>
|
||||||
|
x.AgentId == agent.Id && x.Status == AgentCommissionStatusCodes.Paid);
|
||||||
|
|
||||||
|
return new AgentWorkplaceStatsOutput
|
||||||
|
{
|
||||||
|
CustomerCount = customerCount,
|
||||||
|
TodayCreated = todayCreated,
|
||||||
|
YesterdayCreated = yesterdayCreated,
|
||||||
|
DayOverDayGrowthPercent = growth,
|
||||||
|
PendingPayment = GetCount(OrderStatusCodes.PendingPayment),
|
||||||
|
Shipping = GetCount(OrderStatusCodes.Shipping) + GetCount(OrderStatusCodes.PendingOnline),
|
||||||
|
Problematic = GetCount(OrderStatusCodes.Problematic),
|
||||||
|
DeliverySuccess = GetCount(OrderStatusCodes.DeliverySuccess),
|
||||||
|
DeliveryFailed = GetCount(OrderStatusCodes.DeliveryFailed),
|
||||||
|
PendingCommissionCount = pendingDetails.Count,
|
||||||
|
PendingCommissionAmount = pendingDetails.Sum(x => x.CommissionAmount),
|
||||||
|
PaidCommissionAmount = paidDetails.Sum(x => x.CommissionAmount),
|
||||||
|
WeekNewCustomers = weekNewCustomers,
|
||||||
|
Counts = counts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 代理工作台趋势:按日统计代客下单数与计提返佣金额。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="days">天数,默认 7,最大 31。</param>
|
||||||
|
[HttpGet("/agent/portal/workplace/trend")]
|
||||||
|
public async Task<AgentWorkplaceTrendOutput> GetWorkplaceTrendAsync([FromQuery] int days = 7)
|
||||||
|
{
|
||||||
|
var agent = await GetCurrentAgentOrThrowAsync();
|
||||||
|
days = Math.Clamp(days, 1, 31);
|
||||||
|
var filter = new OrderListFilter { AgentId = agent.Id };
|
||||||
|
var today = DateTime.Today;
|
||||||
|
var rangeStart = today.AddDays(-(days - 1));
|
||||||
|
var rangeEnd = today.AddDays(1);
|
||||||
|
|
||||||
|
var commissions = await DetailRepository.GetListAsync(x =>
|
||||||
|
x.AgentId == agent.Id
|
||||||
|
&& x.CreateTime >= rangeStart
|
||||||
|
&& x.CreateTime < rangeEnd);
|
||||||
|
|
||||||
|
var points = new List<AgentWorkplaceTrendPoint>(days);
|
||||||
|
for (var i = 0; i < days; i++)
|
||||||
|
{
|
||||||
|
var start = rangeStart.AddDays(i);
|
||||||
|
var end = start.AddDays(1);
|
||||||
|
var orderCount = await OrderRepository.CountByFilterAndCreateTimeRangeAsync(filter, start, end);
|
||||||
|
var commissionAmount = commissions
|
||||||
|
.Where(x => x.CreateTime >= start && x.CreateTime < end)
|
||||||
|
.Sum(x => x.CommissionAmount);
|
||||||
|
points.Add(new AgentWorkplaceTrendPoint
|
||||||
|
{
|
||||||
|
Label = start.ToString("MM-dd"),
|
||||||
|
Start = start,
|
||||||
|
End = end,
|
||||||
|
OrderCount = orderCount,
|
||||||
|
CommissionAmount = commissionAmount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AgentWorkplaceTrendOutput
|
||||||
|
{
|
||||||
|
Dimension = "day",
|
||||||
|
Points = points,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>名下返佣明细(支持状态 / 运单号 / 计提时间筛选)。</summary>
|
||||||
|
[HttpPost("/agent/portal/commission/list")]
|
||||||
|
public async Task<OmsPageResult<AgentCommissionDetailOutput>> GetCommissionListAsync(
|
||||||
|
AgentPortalCommissionListInput input)
|
||||||
|
{
|
||||||
|
var agent = await GetCurrentAgentOrThrowAsync();
|
||||||
|
var details = await QueryCommissionDetailsAsync(agent, input);
|
||||||
var page = OmsDocHelper.NormalizePage(input.Page);
|
var page = OmsDocHelper.NormalizePage(input.Page);
|
||||||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||||||
var entities = await DetailRepository.GetPagedListAsync(
|
var total = details.Count;
|
||||||
predicate,
|
var pageItems = details
|
||||||
page,
|
.Skip((page - 1) * pageSize)
|
||||||
pageSize,
|
.Take(pageSize)
|
||||||
new Dictionary<string, bool>
|
.ToList();
|
||||||
{
|
|
||||||
[nameof(AgentCommissionDetail.CreateTime)] = false,
|
|
||||||
[nameof(AgentCommissionDetail.Id)] = false
|
|
||||||
});
|
|
||||||
var total = await DetailRepository.CountAsync(predicate);
|
|
||||||
|
|
||||||
var customerIds = entities.Select(x => x.CustomerId).Distinct().ToList();
|
|
||||||
var orderIds = entities.Select(x => x.OrderId).Distinct().ToList();
|
|
||||||
var customers = customerIds.Count == 0
|
|
||||||
? []
|
|
||||||
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
|
||||||
var orders = orderIds.Count == 0
|
|
||||||
? []
|
|
||||||
: await OrderRepository.GetListAsync(x => orderIds.Contains(x.Id));
|
|
||||||
var customerMap = customers.ToDictionary(x => x.Id);
|
|
||||||
var orderMap = orders.ToDictionary(x => x.Id);
|
|
||||||
|
|
||||||
return new OmsPageResult<AgentCommissionDetailOutput>
|
return new OmsPageResult<AgentCommissionDetailOutput>
|
||||||
{
|
{
|
||||||
Items = entities.Select(x =>
|
Items = await MapCommissionOutputsAsync(agent, pageItems),
|
||||||
{
|
|
||||||
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 = MaskName(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(),
|
|
||||||
Total = total
|
Total = total
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>导出名下返佣明细(Excel,最多 5000 条)。</summary>
|
||||||
|
[HttpPost("/agent/portal/commission/export")]
|
||||||
|
public async Task<FileContentResult> ExportCommissionAsync(AgentPortalCommissionListInput input)
|
||||||
|
{
|
||||||
|
var agent = await GetCurrentAgentOrThrowAsync();
|
||||||
|
var details = await QueryCommissionDetailsAsync(agent, input);
|
||||||
|
const int maxRows = 5000;
|
||||||
|
if (details.Count > maxRows)
|
||||||
|
{
|
||||||
|
throw new BusinessException($"导出结果超过 {maxRows} 条(当前 {details.Count}),请缩小筛选条件后再导出");
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputs = await MapCommissionOutputsAsync(agent, details);
|
||||||
|
var rows = outputs.Select(x => new
|
||||||
|
{
|
||||||
|
运单号 = x.ServiceNo,
|
||||||
|
客户编号 = x.CustomerCode,
|
||||||
|
客户名称 = x.CustomerName,
|
||||||
|
基数类型 = ResolveBaseTypeName(x.BaseType),
|
||||||
|
基数金额 = x.BaseAmount,
|
||||||
|
返佣比例 = x.CommissionRate,
|
||||||
|
返佣金额 = x.CommissionAmount,
|
||||||
|
状态 = string.Equals(x.Status, AgentCommissionStatusCodes.Paid, StringComparison.OrdinalIgnoreCase)
|
||||||
|
? "已打款"
|
||||||
|
: "待结算",
|
||||||
|
触发状态 = x.TriggerOrderStatus,
|
||||||
|
计提时间 = x.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
打款时间 = x.PaidTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "",
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
await using var stream = new MemoryStream();
|
||||||
|
await MiniExcel.SaveAsAsync(stream, rows);
|
||||||
|
return new FileContentResult(
|
||||||
|
stream.ToArray(),
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
{
|
||||||
|
FileDownloadName = $"agent-commission-{DateTime.Now:yyyyMMddHHmmss}.xlsx"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<AgentCommissionDetail>> QueryCommissionDetailsAsync(
|
||||||
|
Agent agent,
|
||||||
|
AgentPortalCommissionListInput input)
|
||||||
|
{
|
||||||
|
var status = input.Status?.Trim();
|
||||||
|
var keyword = input.Keyword?.Trim();
|
||||||
|
var start = input.CreateTimeStart;
|
||||||
|
var end = input.CreateTimeEnd;
|
||||||
|
|
||||||
|
Expression<Func<AgentCommissionDetail, bool>> predicate = x => x.AgentId == agent.Id;
|
||||||
|
if (!string.IsNullOrWhiteSpace(status))
|
||||||
|
{
|
||||||
|
predicate = x => x.AgentId == agent.Id && x.Status == status;
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = await DetailRepository.GetListAsync(predicate);
|
||||||
|
if (start.HasValue)
|
||||||
|
{
|
||||||
|
list = list.Where(x => x.CreateTime >= start.Value).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end.HasValue)
|
||||||
|
{
|
||||||
|
var endExclusive = end.Value.Date.AddDays(1);
|
||||||
|
list = list.Where(x => x.CreateTime < endExclusive).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
var orderIds = list.Select(x => x.OrderId).Distinct().ToList();
|
||||||
|
var orders = orderIds.Count == 0
|
||||||
|
? []
|
||||||
|
: await OrderRepository.GetListAsync(x => orderIds.Contains(x.Id));
|
||||||
|
var matchedOrderIds = orders
|
||||||
|
.Where(o => o.ServiceNo.Contains(keyword, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| (o.TrackNo != null && o.TrackNo.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
.Select(o => o.Id)
|
||||||
|
.ToHashSet();
|
||||||
|
list = list.Where(x => matchedOrderIds.Contains(x.OrderId)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
.OrderByDescending(x => x.CreateTime)
|
||||||
|
.ThenByDescending(x => x.Id)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<AgentCommissionDetailOutput>> MapCommissionOutputsAsync(
|
||||||
|
Agent agent,
|
||||||
|
IReadOnlyCollection<AgentCommissionDetail> entities)
|
||||||
|
{
|
||||||
|
if (entities.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var customerIds = entities.Select(x => x.CustomerId).Distinct().ToList();
|
||||||
|
var orderIds = entities.Select(x => x.OrderId).Distinct().ToList();
|
||||||
|
var customers = await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||||||
|
var orders = await OrderRepository.GetListAsync(x => orderIds.Contains(x.Id));
|
||||||
|
var customerMap = customers.ToDictionary(x => x.Id);
|
||||||
|
var orderMap = orders.ToDictionary(x => x.Id);
|
||||||
|
|
||||||
|
return entities.Select(x =>
|
||||||
|
{
|
||||||
|
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 = MaskName(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 async Task<Dictionary<int, (DateTime? LastOrderTime, int OrderCount30d, int PendingPaymentCount)>>
|
||||||
|
BuildCustomerActivityMapAsync(int agentId, List<int> mainCustomerIds)
|
||||||
|
{
|
||||||
|
var result = mainCustomerIds.ToDictionary(
|
||||||
|
id => id,
|
||||||
|
_ => ((DateTime?)null, 0, 0));
|
||||||
|
if (mainCustomerIds.Count == 0)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = await CustomerRepository.GetListAsync(x =>
|
||||||
|
x.ParentCustomerId != null && mainCustomerIds.Contains(x.ParentCustomerId.Value));
|
||||||
|
var mainByCustomerId = new Dictionary<int, int>();
|
||||||
|
foreach (var id in mainCustomerIds)
|
||||||
|
{
|
||||||
|
mainByCustomerId[id] = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in children)
|
||||||
|
{
|
||||||
|
if (child.ParentCustomerId is > 0)
|
||||||
|
{
|
||||||
|
mainByCustomerId[child.Id] = child.ParentCustomerId.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var allIds = mainByCustomerId.Keys.ToList();
|
||||||
|
var since30 = DateTime.Today.AddDays(-30);
|
||||||
|
var orders = await OrderRepository.GetListAsync(x =>
|
||||||
|
x.AgentId == agentId
|
||||||
|
&& x.CustomerId != null
|
||||||
|
&& allIds.Contains(x.CustomerId.Value)
|
||||||
|
&& x.OrderStatus != OrderStatusCodes.Deleted);
|
||||||
|
|
||||||
|
foreach (var group in orders.GroupBy(o => mainByCustomerId[o.CustomerId!.Value]))
|
||||||
|
{
|
||||||
|
var list = group.ToList();
|
||||||
|
result[group.Key] = (
|
||||||
|
list.Max(o => (DateTime?)o.CreateTime),
|
||||||
|
list.Count(o => o.CreateTime >= since30),
|
||||||
|
list.Count(o => string.Equals(
|
||||||
|
o.OrderStatus,
|
||||||
|
OrderStatusCodes.PendingPayment,
|
||||||
|
StringComparison.OrdinalIgnoreCase)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<string>> ResolveSalesProductNamesAsync(string? salesProductIds)
|
||||||
|
{
|
||||||
|
var ids = ParseSalesProductIds(salesProductIds);
|
||||||
|
if (ids.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var products = await SalesProductRepository.GetListAsync(x => ids.Contains(x.Id));
|
||||||
|
var map = products.ToDictionary(x => x.Id);
|
||||||
|
return ids
|
||||||
|
.Select(id => map.TryGetValue(id, out var p) ? $"{p.ProductName}({p.ProductCode})" : $"#{id}")
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<int> ParseSalesProductIds(string? raw)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(raw))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = raw.Trim();
|
||||||
|
if (text.StartsWith('['))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var arr = global::System.Text.Json.JsonSerializer.Deserialize<List<int>>(text);
|
||||||
|
return arr?.Where(x => x > 0).Distinct().ToList() ?? [];
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
|
.Split([',', ';', '|', ' '], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||||
|
.Select(x => int.TryParse(x, out var id) ? id : 0)
|
||||||
|
.Where(x => x > 0)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveBaseTypeName(string? baseType)
|
||||||
|
{
|
||||||
|
if (string.Equals(baseType, "receivable", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "应收";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(baseType, "freight", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "运费";
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(baseType) ? "运费" : baseType;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Agent> GetCurrentAgentOrThrowAsync()
|
private async Task<Agent> GetCurrentAgentOrThrowAsync()
|
||||||
{
|
{
|
||||||
var mapId = CurrentUser.GetId<int>();
|
var mapId = OmsSessionHelper.ResolveMapUserId(CurrentUser);
|
||||||
if (mapId <= 0)
|
if (mapId <= 0)
|
||||||
{
|
{
|
||||||
throw new BusinessException("代理未登录");
|
throw new BusinessException("代理未登录");
|
||||||
|
|||||||
@ -78,6 +78,8 @@ public class CustomerService : ApplicationService
|
|||||||
entities.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId }));
|
entities.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId }));
|
||||||
var memberLevelNameMap = await ResolveMemberLevelNameMapAsync(
|
var memberLevelNameMap = await ResolveMemberLevelNameMapAsync(
|
||||||
entities.Where(x => x.MemberLevelId.HasValue).Select(x => x.MemberLevelId!.Value));
|
entities.Where(x => x.MemberLevelId.HasValue).Select(x => x.MemberLevelId!.Value));
|
||||||
|
var agentMap = await ResolveAgentDisplayMapAsync(
|
||||||
|
entities.Where(x => x.AgentId.HasValue).Select(x => x.AgentId!.Value));
|
||||||
var items = entities.Select(entity =>
|
var items = entities.Select(entity =>
|
||||||
{
|
{
|
||||||
var output = ObjectMapper.Map<Customer, CustomerPaginatedOutput>(entity);
|
var output = ObjectMapper.Map<Customer, CustomerPaginatedOutput>(entity);
|
||||||
@ -90,6 +92,7 @@ public class CustomerService : ApplicationService
|
|||||||
output.MemberLevelName = levelName;
|
output.MemberLevelName = levelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FillAgentDisplay(output, entity.AgentId, agentMap);
|
||||||
return output;
|
return output;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
@ -121,6 +124,12 @@ public class CustomerService : ApplicationService
|
|||||||
output.MemberLevelName = level?.LevelName;
|
output.MemberLevelName = level?.LevelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entity.AgentId.HasValue)
|
||||||
|
{
|
||||||
|
var agentMap = await ResolveAgentDisplayMapAsync([entity.AgentId.Value]);
|
||||||
|
FillAgentDisplay(output, entity.AgentId, agentMap);
|
||||||
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,6 +410,11 @@ public class CustomerService : ApplicationService
|
|||||||
entity.CustomerService = NormalizeOptionalText(input.CustomerService);
|
entity.CustomerService = NormalizeOptionalText(input.CustomerService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.AgentCode != null)
|
||||||
|
{
|
||||||
|
await AgentBinder.ApplyAgentBindingAsync(entity, input.AgentCode, AgentRepository);
|
||||||
|
}
|
||||||
|
|
||||||
if (input.CustomerGroup.HasValue)
|
if (input.CustomerGroup.HasValue)
|
||||||
{
|
{
|
||||||
entity.CustomerGroup = input.CustomerGroup;
|
entity.CustomerGroup = input.CustomerGroup;
|
||||||
@ -500,6 +514,8 @@ public class CustomerService : ApplicationService
|
|||||||
var statusValue = input.Status.GetValueOrDefault();
|
var statusValue = input.Status.GetValueOrDefault();
|
||||||
var hasCustomerGroupFilter = input.CustomerGroup.HasValue;
|
var hasCustomerGroupFilter = input.CustomerGroup.HasValue;
|
||||||
var customerGroupValue = input.CustomerGroup.GetValueOrDefault();
|
var customerGroupValue = input.CustomerGroup.GetValueOrDefault();
|
||||||
|
var hasAgentFilter = input.AgentId is > 0;
|
||||||
|
var agentId = input.AgentId.GetValueOrDefault();
|
||||||
|
|
||||||
return customer =>
|
return customer =>
|
||||||
(string.IsNullOrWhiteSpace(keyword) ||
|
(string.IsNullOrWhiteSpace(keyword) ||
|
||||||
@ -510,7 +526,47 @@ public class CustomerService : ApplicationService
|
|||||||
(!hasStatusFilter || customer.Status == statusValue) &&
|
(!hasStatusFilter || customer.Status == statusValue) &&
|
||||||
(!hasCustomerGroupFilter || customer.CustomerGroup == customerGroupValue) &&
|
(!hasCustomerGroupFilter || customer.CustomerGroup == customerGroupValue) &&
|
||||||
(string.IsNullOrWhiteSpace(salesman) || customer.Salesman == salesman) &&
|
(string.IsNullOrWhiteSpace(salesman) || customer.Salesman == salesman) &&
|
||||||
(string.IsNullOrWhiteSpace(customerService) || customer.CustomerService == customerService);
|
(string.IsNullOrWhiteSpace(customerService) || customer.CustomerService == customerService) &&
|
||||||
|
(!hasAgentFilter || customer.AgentId == agentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Dictionary<int, (string Code, string Name)>> ResolveAgentDisplayMapAsync(
|
||||||
|
IEnumerable<int> agentIds)
|
||||||
|
{
|
||||||
|
var ids = agentIds.Distinct().ToList();
|
||||||
|
if (ids.Count == 0)
|
||||||
|
{
|
||||||
|
return new Dictionary<int, (string Code, string Name)>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var agents = await AgentRepository.GetListAsync(x => ids.Contains(x.Id));
|
||||||
|
return agents.ToDictionary(x => x.Id, x => (x.AgentCode, x.AgentName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillAgentDisplay(
|
||||||
|
CustomerPaginatedOutput output,
|
||||||
|
int? agentId,
|
||||||
|
IReadOnlyDictionary<int, (string Code, string Name)> agentMap)
|
||||||
|
{
|
||||||
|
output.AgentId = agentId;
|
||||||
|
if (agentId.HasValue && agentMap.TryGetValue(agentId.Value, out var agent))
|
||||||
|
{
|
||||||
|
output.AgentCode = agent.Code;
|
||||||
|
output.AgentName = agent.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillAgentDisplay(
|
||||||
|
CustomerOutput output,
|
||||||
|
int? agentId,
|
||||||
|
IReadOnlyDictionary<int, (string Code, string Name)> agentMap)
|
||||||
|
{
|
||||||
|
output.AgentId = agentId;
|
||||||
|
if (agentId.HasValue && agentMap.TryGetValue(agentId.Value, out var agent))
|
||||||
|
{
|
||||||
|
output.AgentCode = agent.Code;
|
||||||
|
output.AgentName = agent.Name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, bool> BuildCustomerSortDictionary(CustomerPaginatedInput input)
|
private static Dictionary<string, bool> BuildCustomerSortDictionary(CustomerPaginatedInput input)
|
||||||
|
|||||||
@ -1208,6 +1208,12 @@ public partial class OrderService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (OmsSessionHelper.IsAgentLogin(CurrentUser))
|
||||||
|
{
|
||||||
|
await ApplyAgentOrderScopeAsync(filter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var userGuidRaw = CurrentUser.FindClaimValueOrNull(global::System.Security.Claims.ClaimTypes.NameIdentifier);
|
var userGuidRaw = CurrentUser.FindClaimValueOrNull(global::System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||||
if (string.IsNullOrWhiteSpace(userGuidRaw) || !Guid.TryParse(userGuidRaw, out var userGuid))
|
if (string.IsNullOrWhiteSpace(userGuidRaw) || !Guid.TryParse(userGuidRaw, out var userGuid))
|
||||||
{
|
{
|
||||||
@ -1217,4 +1223,41 @@ public partial class OrderService
|
|||||||
var context = await DataScopeBuilder.BuildAsync(userGuid, DataScopeApplier.OrderResourceCode);
|
var context = await DataScopeBuilder.BuildAsync(userGuid, DataScopeApplier.OrderResourceCode);
|
||||||
DataScopeApplier.ApplyToOrderListFilter(filter, context);
|
DataScopeApplier.ApplyToOrderListFilter(filter, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渠道代理门户:列表仅可见下单快照 AgentId=本代理的运单。
|
||||||
|
/// </summary>
|
||||||
|
private async Task ApplyAgentOrderScopeAsync(OrderListFilter filter)
|
||||||
|
{
|
||||||
|
var agentRepository =
|
||||||
|
LazyServiceProvider.GetRequiredService<Domain.AgentsAggregate.Repositories.IAgentRepository>();
|
||||||
|
var userId = OmsSessionHelper.ResolveMapUserId(CurrentUser);
|
||||||
|
if (userId <= 0)
|
||||||
|
{
|
||||||
|
throw new BusinessException("代理未登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = await agentRepository.GetByUserIdAsync(userId);
|
||||||
|
if (agent == null)
|
||||||
|
{
|
||||||
|
throw new BusinessException("代理不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.AgentId = agent.Id;
|
||||||
|
|
||||||
|
if (filter.CustomerId is > 0)
|
||||||
|
{
|
||||||
|
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == filter.CustomerId.Value);
|
||||||
|
if (customer == null)
|
||||||
|
{
|
||||||
|
throw new BusinessException("客户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
await OmsSessionHelper.EnsureAgentOwnsCustomerAsync(
|
||||||
|
CustomerRepository,
|
||||||
|
agentRepository,
|
||||||
|
CurrentUser,
|
||||||
|
customer);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3508,13 +3508,19 @@ public partial class OrderService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从会话解析有效客户(客户登录自动补全;管理员须传 customerId)。
|
/// 从会话解析有效客户(客户登录自动补全;管理员/代理须传 customerId;代理仅限名下客户)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Task<Customer> ResolveEffectiveCustomerAsync(int? requestedCustomerId = null) =>
|
private Task<Customer> ResolveEffectiveCustomerAsync(int? requestedCustomerId = null)
|
||||||
OmsSessionHelper.ResolveSessionCustomerAsync(
|
{
|
||||||
|
var agentRepository = OmsSessionHelper.IsAgentLogin(CurrentUser)
|
||||||
|
? LazyServiceProvider.GetRequiredService<Domain.AgentsAggregate.Repositories.IAgentRepository>()
|
||||||
|
: null;
|
||||||
|
return OmsSessionHelper.ResolveSessionCustomerAsync(
|
||||||
CustomerRepository,
|
CustomerRepository,
|
||||||
CurrentUser,
|
CurrentUser,
|
||||||
requestedCustomerId);
|
requestedCustomerId,
|
||||||
|
agentRepository);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ApplyMarketingSnapshotOnCreateAsync(Order order, int? couponInstanceId)
|
private async Task ApplyMarketingSnapshotOnCreateAsync(Order order, int? couponInstanceId)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -704,6 +704,25 @@ public partial class OrderService : ApplicationService
|
|||||||
{
|
{
|
||||||
input.CustomerId = await ResolvePriceCheckCustomerIdAsync();
|
input.CustomerId = await ResolvePriceCheckCustomerIdAsync();
|
||||||
}
|
}
|
||||||
|
else if (OmsSessionHelper.IsAgentLogin(CurrentUser))
|
||||||
|
{
|
||||||
|
// 代理代客查价:仅允许名下绑定客户
|
||||||
|
var agentRepository =
|
||||||
|
LazyServiceProvider.GetRequiredService<Domain.AgentsAggregate.Repositories.IAgentRepository>();
|
||||||
|
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == input.CustomerId.Value);
|
||||||
|
if (customer == null)
|
||||||
|
{
|
||||||
|
throw new BusinessException("客户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
await OmsSessionHelper.EnsureAgentOwnsCustomerAsync(
|
||||||
|
CustomerRepository,
|
||||||
|
agentRepository,
|
||||||
|
CurrentUser,
|
||||||
|
customer);
|
||||||
|
input.CustomerId =
|
||||||
|
await MarketingCustomerHelper.ResolveMarketingMainCustomerIdAsync(CustomerRepository, customer);
|
||||||
|
}
|
||||||
|
|
||||||
return await BuildPriceCheckResultAsync(input);
|
return await BuildPriceCheckResultAsync(input);
|
||||||
}
|
}
|
||||||
@ -1335,6 +1354,20 @@ public partial class OrderService : ApplicationService
|
|||||||
|
|
||||||
private async Task EnsureCustomerCanAccessOrderAsync(Order order)
|
private async Task EnsureCustomerCanAccessOrderAsync(Order order)
|
||||||
{
|
{
|
||||||
|
if (OmsSessionHelper.IsAgentLogin(CurrentUser))
|
||||||
|
{
|
||||||
|
var agentRepository =
|
||||||
|
LazyServiceProvider.GetRequiredService<Domain.AgentsAggregate.Repositories.IAgentRepository>();
|
||||||
|
var userId = OmsSessionHelper.ResolveMapUserId(CurrentUser);
|
||||||
|
var agent = userId > 0 ? await agentRepository.GetByUserIdAsync(userId) : null;
|
||||||
|
if (agent == null || order.AgentId != agent.Id)
|
||||||
|
{
|
||||||
|
throw new BusinessException("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||||
CustomerRepository,
|
CustomerRepository,
|
||||||
CurrentUser,
|
CurrentUser,
|
||||||
|
|||||||
@ -37,4 +37,36 @@ public static class AgentBinder
|
|||||||
entity.AgentId = agent.Id;
|
entity.AgentId = agent.Id;
|
||||||
entity.AgentBindTime = DateTime.Now;
|
entity.AgentBindTime = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 运营改绑:按代理编号覆盖;空串解绑。子客户不可改(跟主客户)。
|
||||||
|
/// </summary>
|
||||||
|
public static async Task ApplyAgentBindingAsync(
|
||||||
|
Customer entity,
|
||||||
|
string? agentCode,
|
||||||
|
IAgentRepository agentRepository,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (entity.ParentCustomerId.HasValue)
|
||||||
|
{
|
||||||
|
throw new BusinessException("请改绑主客户");
|
||||||
|
}
|
||||||
|
|
||||||
|
var code = agentCode?.Trim();
|
||||||
|
if (string.IsNullOrEmpty(code))
|
||||||
|
{
|
||||||
|
entity.AgentId = null;
|
||||||
|
entity.AgentBindTime = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = await agentRepository.GetByAgentCodeAsync(code, cancellationToken);
|
||||||
|
if (agent == null || agent.Status != EnableStatus.Enabled)
|
||||||
|
{
|
||||||
|
throw new BusinessException("代理编号无效或已停用");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.AgentId = agent.Id;
|
||||||
|
entity.AgentBindTime = DateTime.Now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Fake.Users;
|
using Fake.Users;
|
||||||
|
using Oms.Domain.AgentsAggregate.Repositories;
|
||||||
using Oms.Domain.CustomersAggregate.Entities;
|
using Oms.Domain.CustomersAggregate.Entities;
|
||||||
using Oms.Domain.CustomersAggregate.Repositories;
|
using Oms.Domain.CustomersAggregate.Repositories;
|
||||||
|
|
||||||
namespace Oms.Application.Shared;
|
namespace Oms.Application.Shared;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从 JWT Claims 解析当前会话类型(管理员 / 客户)。
|
/// 从 JWT Claims 解析当前会话类型(管理员 / 客户 / 渠道代理)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class OmsSessionHelper
|
public static class OmsSessionHelper
|
||||||
{
|
{
|
||||||
@ -22,7 +23,9 @@ public static class OmsSessionHelper
|
|||||||
public const string MapIdClaim = "mapId";
|
public const string MapIdClaim = "mapId";
|
||||||
|
|
||||||
private const string CustomerLoginType = "1";
|
private const string CustomerLoginType = "1";
|
||||||
|
private const string AgentLoginType = "2";
|
||||||
private const string CustomerRoleName = "Customer";
|
private const string CustomerRoleName = "Customer";
|
||||||
|
private const string AgentRoleName = "Agent";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前会话是否为客户门户登录。
|
/// 当前会话是否为客户门户登录。
|
||||||
@ -43,6 +46,24 @@ public static class OmsSessionHelper
|
|||||||
return string.Equals(role, CustomerRoleName, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(role, CustomerRoleName, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前会话是否为渠道代理门户登录。
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsAgentLogin(ICurrentUser currentUser)
|
||||||
|
{
|
||||||
|
if (string.Equals(
|
||||||
|
currentUser.FindClaimOrNull(LoginTypeClaim)?.Value,
|
||||||
|
AgentLoginType,
|
||||||
|
StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var role = currentUser.FindClaimOrNull(ClaimTypes.Role)?.Value
|
||||||
|
?? currentUser.FindClaimOrNull("role")?.Value;
|
||||||
|
return string.Equals(role, AgentRoleName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析当前会话的 RBAC mapId(user.userid)。
|
/// 解析当前会话的 RBAC mapId(user.userid)。
|
||||||
/// JWT nameidentifier 多为 Guid,GetId<int> 常为 0,需回退读 mapId claim。
|
/// JWT nameidentifier 多为 Guid,GetId<int> 常为 0,需回退读 mapId claim。
|
||||||
@ -85,12 +106,14 @@ public static class OmsSessionHelper
|
|||||||
TryGetCustomerMapId(currentUser);
|
TryGetCustomerMapId(currentUser);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析当前会话对应的 t_customer 记录(客户按 UserId=mapId;管理员可按 CustomerId 代提交)。
|
/// 解析当前会话对应的 t_customer 记录(客户按 UserId=mapId;管理员/代理可按 CustomerId 代提交)。
|
||||||
|
/// 代理须传入 <paramref name="agentRepository"/>,且仅允许操作名下绑定客户。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static async Task<Customer> ResolveSessionCustomerAsync(
|
public static async Task<Customer> ResolveSessionCustomerAsync(
|
||||||
ICustomerRepository customerRepository,
|
ICustomerRepository customerRepository,
|
||||||
ICurrentUser currentUser,
|
ICurrentUser currentUser,
|
||||||
int? requestedCustomerId = null)
|
int? requestedCustomerId = null,
|
||||||
|
IAgentRepository? agentRepository = null)
|
||||||
{
|
{
|
||||||
var mapId = TryGetCustomerMapId(currentUser);
|
var mapId = TryGetCustomerMapId(currentUser);
|
||||||
if (mapId.HasValue)
|
if (mapId.HasValue)
|
||||||
@ -121,9 +144,53 @@ public static class OmsSessionHelper
|
|||||||
throw new BusinessException("客户不存在");
|
throw new BusinessException("客户不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsAgentLogin(currentUser))
|
||||||
|
{
|
||||||
|
await EnsureAgentOwnsCustomerAsync(customerRepository, agentRepository, currentUser, customer);
|
||||||
|
}
|
||||||
|
|
||||||
return customer;
|
return customer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渠道代理仅可操作名下绑定客户(主客户 AgentId=本代理;子客户跟主客户)。
|
||||||
|
/// </summary>
|
||||||
|
public static async Task EnsureAgentOwnsCustomerAsync(
|
||||||
|
ICustomerRepository customerRepository,
|
||||||
|
IAgentRepository? agentRepository,
|
||||||
|
ICurrentUser currentUser,
|
||||||
|
Customer customer)
|
||||||
|
{
|
||||||
|
if (agentRepository == null)
|
||||||
|
{
|
||||||
|
throw new BusinessException("代理会话缺少代理仓储");
|
||||||
|
}
|
||||||
|
|
||||||
|
var userId = ResolveMapUserId(currentUser);
|
||||||
|
if (userId <= 0)
|
||||||
|
{
|
||||||
|
throw new BusinessException("代理未登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = await agentRepository.GetByUserIdAsync(userId);
|
||||||
|
if (agent == null)
|
||||||
|
{
|
||||||
|
throw new BusinessException("代理不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
int? bindAgentId = customer.AgentId;
|
||||||
|
if (customer.ParentCustomerId is > 0)
|
||||||
|
{
|
||||||
|
var main = await customerRepository.FirstOrDefaultAsync(x => x.Id == customer.ParentCustomerId.Value);
|
||||||
|
bindAgentId = main?.AgentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindAgentId != agent.Id)
|
||||||
|
{
|
||||||
|
throw new BusinessException("只能为名下绑定客户操作");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 客户登录时强制返回 t_customer.Id;管理员返回入参 customerId。
|
/// 客户登录时强制返回 t_customer.Id;管理员返回入参 customerId。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -34,6 +34,11 @@ public class OrderListFilter
|
|||||||
|
|
||||||
public int? CustomerId { get; set; }
|
public int? CustomerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渠道代理 Id(门户列表强制按下单快照 AgentId 过滤)。
|
||||||
|
/// </summary>
|
||||||
|
public int? AgentId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发货仓 ID(订单或绑定行所属仓)。
|
/// 发货仓 ID(订单或绑定行所属仓)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -47,6 +47,12 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
|||||||
query = query.Where(x => x.CustomerId == customerId);
|
query = query.Where(x => x.CustomerId == customerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filter.AgentId.HasValue)
|
||||||
|
{
|
||||||
|
var agentId = filter.AgentId.Value;
|
||||||
|
query = query.Where(x => x.AgentId == agentId);
|
||||||
|
}
|
||||||
|
|
||||||
var status = filter.Status?.Trim();
|
var status = filter.Status?.Trim();
|
||||||
var isDeletedTab = string.Equals(status, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase);
|
var isDeletedTab = string.Equals(status, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase);
|
||||||
if (isDeletedTab)
|
if (isDeletedTab)
|
||||||
@ -1503,6 +1509,7 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
|||||||
CreateTimeStart = filter.CreateTimeStart,
|
CreateTimeStart = filter.CreateTimeStart,
|
||||||
CreateTimeEnd = filter.CreateTimeEnd,
|
CreateTimeEnd = filter.CreateTimeEnd,
|
||||||
CustomerId = filter.CustomerId,
|
CustomerId = filter.CustomerId,
|
||||||
|
AgentId = filter.AgentId,
|
||||||
FieldFilters = filter.FieldFilters,
|
FieldFilters = filter.FieldFilters,
|
||||||
SortField = filter.SortField,
|
SortField = filter.SortField,
|
||||||
SortOrder = filter.SortOrder,
|
SortOrder = filter.SortOrder,
|
||||||
@ -1549,6 +1556,7 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
|||||||
CreateTimeStart = filter.CreateTimeStart,
|
CreateTimeStart = filter.CreateTimeStart,
|
||||||
CreateTimeEnd = filter.CreateTimeEnd,
|
CreateTimeEnd = filter.CreateTimeEnd,
|
||||||
CustomerId = filter.CustomerId,
|
CustomerId = filter.CustomerId,
|
||||||
|
AgentId = filter.AgentId,
|
||||||
WarehouseId = filter.WarehouseId,
|
WarehouseId = filter.WarehouseId,
|
||||||
FulfillmentType = filter.FulfillmentType,
|
FulfillmentType = filter.FulfillmentType,
|
||||||
StockBindStatus = filter.StockBindStatus,
|
StockBindStatus = filter.StockBindStatus,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user