feat: 注册绑定所属顾问与岗位职级字典种子

支持 AdvisorCode 写入 CustomerService=Account;补充 employee_position/employee_job_level 初始 SQL。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-09-03 12:45:13 +08:00
parent 7e9a5d4ae0
commit eba1fa4867
6 changed files with 167 additions and 0 deletions

View File

@ -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
);

View File

@ -118,4 +118,10 @@ public class CreateCustomerInput
[StringLength(16)]
public string? InviteCode { get; set; }
/// <summary>
/// 顾问邀请码(所属顾问绑定,值为员工 Account;仅 CustomerService 为空时生效)。
/// </summary>
[StringLength(64)]
public string? AdvisorCode { get; set; }
}

View File

@ -233,6 +233,10 @@ public class CustomerService : ApplicationService
input.InviteCode,
ReferralCodeRepository,
CustomerRepository);
await StaffAdvisorBinder.BindAdvisorOnCreateAsync(
entity,
input.AdvisorCode,
RbacDbContext);
await CustomerRepository.InsertAsync(entity);
try
{

View File

@ -0,0 +1,67 @@
using Fake.Rbac.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Oms.Domain.CustomersAggregate.Entities;
namespace Oms.Application.Shared;
/// <summary>
/// 顾问邀请绑定:注册时把所属顾问(CustomerService)写成员工登录账号。
/// </summary>
public static class StaffAdvisorBinder
{
/// <summary>
/// 仅在 CustomerService 为空时绑定;advisorCode 为员工 Account(来自 ?advisor=)。
/// 无效账号静默跳过,不阻断注册。
/// </summary>
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<string?> 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<AccountRow>(sql, key)
.FirstOrDefaultAsync(cancellationToken);
return string.IsNullOrWhiteSpace(row?.Account) ? null : row.Account.Trim();
}
private sealed class AccountRow
{
public string? Account { get; set; }
}
}

View File

@ -96,6 +96,11 @@ public class WeChatCustomerRegisterInput
/// </summary>
public string? InviteCode { get; set; }
/// <summary>
/// 顾问邀请码(可选)。来自 ?advisor=,值为员工登录账号;注册时写入所属顾问 CustomerService。
/// </summary>
public string? AdvisorCode { get; set; }
[Required]
public WeChatProfileInput Wechat { get; set; } = new();
}

View File

@ -164,6 +164,11 @@ public class WeChatCustomerService(
{
entity.ReferralChannel = "wechat";
}
await StaffAdvisorBinder.BindAdvisorOnCreateAsync(
entity,
input.AdvisorCode,
rbacDbContext,
cancellationToken);
await customerRepository.InsertAsync(entity);
try