diff --git a/sql/migrate_dict_employee_position.sql b/sql/migrate_dict_employee_position.sql
new file mode 100644
index 0000000..c0c2846
--- /dev/null
+++ b/sql/migrate_dict_employee_position.sql
@@ -0,0 +1,80 @@
+-- 员工档案「岗位 / 职级」字典初始数据(幂等:已存在同 DictType+DictCode 则跳过)
+-- dictType: employee_position / employee_job_level
+-- MySQL 5.7+ / 8.0
+
+SET NAMES utf8mb4;
+
+-- ========== 岗位 employee_position ==========
+INSERT INTO `t_dict_type` (`DictType`, `Remark`, `TypeSort`, `IsDeleted`, `CreateUserId`)
+SELECT 'employee_position', '员工岗位', 0, 0, 0
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `t_dict_type` WHERE `DictType` = 'employee_position' AND `IsDeleted` = 0
+);
+
+INSERT INTO `t_dict` (`DictType`, `DictSort`, `DictCode`, `DictName`, `IsDeleted`, `CreateUserId`)
+SELECT v.`DictType`, v.`DictSort`, v.`DictCode`, v.`DictName`, 0, 0
+FROM (
+ SELECT 'employee_position' AS DictType, 1 AS DictSort, 'generalManager' AS DictCode, '总经理' AS DictName
+ UNION ALL SELECT 'employee_position', 2, 'deputyGeneralManager', '副总经理'
+ UNION ALL SELECT 'employee_position', 3, 'logisticsAdvisor', '物流顾问'
+ UNION ALL SELECT 'employee_position', 4, 'csManager', '客服主管'
+ UNION ALL SELECT 'employee_position', 5, 'csSpecialist', '客服专员'
+ UNION ALL SELECT 'employee_position', 6, 'opsManager', '运营经理'
+ UNION ALL SELECT 'employee_position', 7, 'opsSpecialist', '运营专员'
+ UNION ALL SELECT 'employee_position', 8, 'financeManager', '财务经理'
+ UNION ALL SELECT 'employee_position', 9, 'financeSpecialist', '财务专员'
+ UNION ALL SELECT 'employee_position', 10, 'warehouseManager', '仓储主管'
+ UNION ALL SELECT 'employee_position', 11, 'warehouseClerk', '仓管员'
+ UNION ALL SELECT 'employee_position', 12, 'customsSpecialist', '关务专员'
+ UNION ALL SELECT 'employee_position', 13, 'productManager', '产品经理'
+ UNION ALL SELECT 'employee_position', 14, 'techEngineer', '技术工程师'
+ UNION ALL SELECT 'employee_position', 15, 'hrAdmin', '人事行政'
+ UNION ALL SELECT 'employee_position', 16, 'marketingSpecialist', '市场专员'
+ UNION ALL SELECT 'employee_position', 17, 'dispatcher', '调度员'
+) AS v
+WHERE NOT EXISTS (
+ SELECT 1
+ FROM `t_dict` d
+ WHERE d.`DictType` = v.`DictType`
+ AND d.`DictCode` = v.`DictCode`
+ AND d.`IsDeleted` = 0
+);
+
+UPDATE `t_dict`
+SET `IsDeleted` = 1,
+ `UpdateTime` = NOW()
+WHERE `DictType` = 'employee_position'
+ AND `DictCode` IN (
+ 'salesDirector',
+ 'salesManager',
+ 'salesSpecialist',
+ 'logisticsAdvisorDirector',
+ 'logisticsAdvisorManager'
+ )
+ AND `IsDeleted` = 0;
+
+-- ========== 职级 employee_job_level(一级~五级) ==========
+INSERT INTO `t_dict_type` (`DictType`, `Remark`, `TypeSort`, `IsDeleted`, `CreateUserId`)
+SELECT 'employee_job_level', '员工职级', 0, 0, 0
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM `t_dict_type` WHERE `DictType` = 'employee_job_level' AND `IsDeleted` = 0
+);
+
+INSERT INTO `t_dict` (`DictType`, `DictSort`, `DictCode`, `DictName`, `IsDeleted`, `CreateUserId`)
+SELECT v.`DictType`, v.`DictSort`, v.`DictCode`, v.`DictName`, 0, 0
+FROM (
+ SELECT 'employee_job_level' AS DictType, 1 AS DictSort, '1' AS DictCode, '一级' AS DictName
+ UNION ALL SELECT 'employee_job_level', 2, '2', '二级'
+ UNION ALL SELECT 'employee_job_level', 3, '3', '三级'
+ UNION ALL SELECT 'employee_job_level', 4, '4', '四级'
+ UNION ALL SELECT 'employee_job_level', 5, '5', '五级'
+) AS v
+WHERE NOT EXISTS (
+ SELECT 1
+ FROM `t_dict` d
+ WHERE d.`DictType` = v.`DictType`
+ AND d.`DictCode` = v.`DictCode`
+ AND d.`IsDeleted` = 0
+);
diff --git a/src/Oms.Application/Dtos/Customers/CreateCustomerInput.cs b/src/Oms.Application/Dtos/Customers/CreateCustomerInput.cs
index cfbef33..1c1ac13 100644
--- a/src/Oms.Application/Dtos/Customers/CreateCustomerInput.cs
+++ b/src/Oms.Application/Dtos/Customers/CreateCustomerInput.cs
@@ -118,4 +118,10 @@ public class CreateCustomerInput
[StringLength(16)]
public string? InviteCode { get; set; }
+ ///
+ /// 顾问邀请码(所属顾问绑定,值为员工 Account;仅 CustomerService 为空时生效)。
+ ///
+ [StringLength(64)]
+ public string? AdvisorCode { get; set; }
+
}
diff --git a/src/Oms.Application/Services/Customers/CustomerService.cs b/src/Oms.Application/Services/Customers/CustomerService.cs
index 07ddc94..21550cd 100644
--- a/src/Oms.Application/Services/Customers/CustomerService.cs
+++ b/src/Oms.Application/Services/Customers/CustomerService.cs
@@ -233,6 +233,10 @@ public class CustomerService : ApplicationService
input.InviteCode,
ReferralCodeRepository,
CustomerRepository);
+ await StaffAdvisorBinder.BindAdvisorOnCreateAsync(
+ entity,
+ input.AdvisorCode,
+ RbacDbContext);
await CustomerRepository.InsertAsync(entity);
try
{
diff --git a/src/Oms.Application/Shared/StaffAdvisorBinder.cs b/src/Oms.Application/Shared/StaffAdvisorBinder.cs
new file mode 100644
index 0000000..48c9719
--- /dev/null
+++ b/src/Oms.Application/Shared/StaffAdvisorBinder.cs
@@ -0,0 +1,67 @@
+using Fake.Rbac.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Oms.Domain.CustomersAggregate.Entities;
+
+namespace Oms.Application.Shared;
+
+///
+/// 顾问邀请绑定:注册时把所属顾问(CustomerService)写成员工登录账号。
+///
+public static class StaffAdvisorBinder
+{
+ ///
+ /// 仅在 CustomerService 为空时绑定;advisorCode 为员工 Account(来自 ?advisor=)。
+ /// 无效账号静默跳过,不阻断注册。
+ ///
+ public static async Task BindAdvisorOnCreateAsync(
+ Customer entity,
+ string? advisorCode,
+ FakeRbacDbContext rbacDbContext,
+ CancellationToken cancellationToken = default)
+ {
+ if (!string.IsNullOrWhiteSpace(entity.CustomerService))
+ {
+ return;
+ }
+
+ var key = advisorCode?.Trim();
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ return;
+ }
+
+ var account = await ResolveStaffAccountAsync(rbacDbContext, key, cancellationToken);
+ if (string.IsNullOrWhiteSpace(account))
+ {
+ return;
+ }
+
+ entity.CustomerService = account;
+ }
+
+ private static async Task ResolveStaffAccountAsync(
+ FakeRbacDbContext rbacDbContext,
+ string key,
+ CancellationToken cancellationToken)
+ {
+ const string sql = """
+ SELECT u.`Account` AS `Account`
+ FROM `user` u
+ WHERE u.`IsDeleted` = 0
+ AND u.`Account` = {0}
+ ORDER BY u.`CreateTime` ASC
+ LIMIT 1
+ """;
+
+ var row = await rbacDbContext.Database
+ .SqlQueryRaw(sql, key)
+ .FirstOrDefaultAsync(cancellationToken);
+
+ return string.IsNullOrWhiteSpace(row?.Account) ? null : row.Account.Trim();
+ }
+
+ private sealed class AccountRow
+ {
+ public string? Account { get; set; }
+ }
+}
diff --git a/src/SimpleAdmin.Api/WeChat/Dtos/WeChatDtos.cs b/src/SimpleAdmin.Api/WeChat/Dtos/WeChatDtos.cs
index ea4808c..f5c09ba 100644
--- a/src/SimpleAdmin.Api/WeChat/Dtos/WeChatDtos.cs
+++ b/src/SimpleAdmin.Api/WeChat/Dtos/WeChatDtos.cs
@@ -96,6 +96,11 @@ public class WeChatCustomerRegisterInput
///
public string? InviteCode { get; set; }
+ ///
+ /// 顾问邀请码(可选)。来自 ?advisor=,值为员工登录账号;注册时写入所属顾问 CustomerService。
+ ///
+ public string? AdvisorCode { get; set; }
+
[Required]
public WeChatProfileInput Wechat { get; set; } = new();
}
diff --git a/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs b/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs
index 21f239a..36b5ce9 100644
--- a/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs
+++ b/src/SimpleAdmin.Api/WeChat/WeChatCustomerService.cs
@@ -164,6 +164,11 @@ public class WeChatCustomerService(
{
entity.ReferralChannel = "wechat";
}
+ await StaffAdvisorBinder.BindAdvisorOnCreateAsync(
+ entity,
+ input.AdvisorCode,
+ rbacDbContext,
+ cancellationToken);
await customerRepository.InsertAsync(entity);
try