Commit 10e0ac6c6aeade8c283300c5dbcf4e7c26c1dc56

Authored by “wangming”
1 parent 12d64309

优化工资查询和用户列表接口功能

1. 工资查询接口优化:
   - 修改所有9个薪酬服务的员工工资查询接口
   - 只能查询已锁定且未确认的工资(IsLocked=1 && EmployeeConfirmStatus!=1)
   - 员工确认工资后无法再次查看
   - 更新接口注释说明新的查询逻辑

2. 用户列表接口增强:
   - 添加门店ID(mdid)搜索功能
   - 添加岗位(gw)搜索功能
   - 添加启用状态(enabledMark)筛选功能
   - 支持组合搜索(门店ID+岗位+状态)

3. 文档和测试:
   - 新增员工工资查询接口逻辑梳理文档
   - 新增用户列表搜索功能测试脚本
docs/员工工资查询接口逻辑梳理.md 0 → 100644
  1 +# 员工工资查询接口逻辑梳理
  2 +
  3 +## 📋 概述
  4 +
  5 +所有薪酬服务都提供了根据员工ID和月份查询工资的接口,供员工查看自己的工资条。本文档梳理了所有薪酬服务中的查询逻辑。
  6 +
  7 +---
  8 +
  9 +## 🔍 接口列表
  10 +
  11 +### 1. 健康师工资查询
  12 +- **服务**: `LqSalaryService`
  13 +- **路由**: `GET /api/Extend/lqsalary/query-by-employee`
  14 +- **方法**: `GetSalaryByEmployee`
  15 +- **返回类型**: `HealthCoachSalaryOutput`
  16 +- **数据表**: `lq_salary_statistics`
  17 +
  18 +### 2. 店长工资查询
  19 +- **服务**: `LqStoreManagerSalaryService`
  20 +- **路由**: `GET /api/Extend/lqstoremanagersalary/query-by-employee`
  21 +- **方法**: `GetSalaryByEmployee`
  22 +- **返回类型**: `StoreManagerSalaryOutput`
  23 +- **数据表**: `lq_store_manager_salary_statistics`
  24 +
  25 +### 3. 主任工资查询
  26 +- **服务**: `LqDirectorSalaryService`
  27 +- **路由**: `GET /api/Extend/lqdirectorsalary/query-by-employee`
  28 +- **方法**: `GetSalaryByEmployee`
  29 +- **返回类型**: `DirectorSalaryOutput`
  30 +- **数据表**: `lq_director_salary_statistics`
  31 +
  32 +### 4. 店助工资查询
  33 +- **服务**: `LqAssistantSalaryService`
  34 +- **路由**: `GET /api/Extend/lqassistantsalary/query-by-employee`
  35 +- **方法**: `GetSalaryByEmployee`
  36 +- **返回类型**: `AssistantSalaryOutput`
  37 +- **数据表**: `lq_assistant_salary_statistics`
  38 +
  39 +### 5. 事业部总经理/经理工资查询
  40 +- **服务**: `LqBusinessUnitManagerSalaryService`
  41 +- **路由**: `GET /api/Extend/lqbusinessunitmanagersalary/query-by-employee`
  42 +- **方法**: `GetSalaryByEmployee`
  43 +- **返回类型**: `BusinessUnitManagerSalaryOutput`
  44 +- **数据表**: `lq_business_unit_manager_salary_statistics`
  45 +
  46 +### 6. 科技部老师工资查询
  47 +- **服务**: `LqTechTeacherSalaryService`
  48 +- **路由**: `GET /api/Extend/lqtechteachersalary/query-by-employee`
  49 +- **方法**: `GetSalaryByEmployee`
  50 +- **返回类型**: `TechTeacherSalaryOutput`
  51 +- **数据表**: `lq_tech_teacher_salary_statistics`
  52 +
  53 +### 7. 科技部总经理工资查询
  54 +- **服务**: `LqTechGeneralManagerSalaryService`
  55 +- **路由**: `GET /api/Extend/lqtechgeneralmanagersalary/query-by-employee`
  56 +- **方法**: `GetSalaryByEmployee`
  57 +- **返回类型**: `TechGeneralManagerSalaryOutput`
  58 +- **数据表**: `lq_tech_general_manager_salary_statistics`
  59 +
  60 +### 8. 大项目主管工资查询
  61 +- **服务**: `LqMajorProjectDirectorSalaryService`
  62 +- **路由**: `GET /api/Extend/lqmajorprojectdirectorsalary/query-by-employee`
  63 +- **方法**: `GetSalaryByEmployee`
  64 +- **返回类型**: `MajorProjectDirectorSalaryOutput`
  65 +- **数据表**: `lq_major_project_director_salary_statistics`
  66 +
  67 +### 9. 大项目部老师工资查询
  68 +- **服务**: `LqMajorProjectTeacherSalaryService`
  69 +- **路由**: `GET /api/Extend/lqmajorprojectteachersalary/query-by-employee`
  70 +- **方法**: `GetSalaryByEmployee`
  71 +- **返回类型**: `MajorProjectTeacherSalaryOutput`
  72 +- **数据表**: `lq_major_project_teacher_salary_statistics`
  73 +
  74 +---
  75 +
  76 +## 📝 统一查询逻辑
  77 +
  78 +### 输入参数
  79 +
  80 +所有接口都使用相同的输入参数类:`SalaryQueryByEmployeeInput`
  81 +
  82 +```csharp
  83 +public class SalaryQueryByEmployeeInput
  84 +{
  85 + /// <summary>
  86 + /// 年份
  87 + /// </summary>
  88 + public int Year { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 月份
  92 + /// </summary>
  93 + public int Month { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 员工ID
  97 + /// </summary>
  98 + public string EmployeeId { get; set; }
  99 +}
  100 +```
  101 +
  102 +### 查询条件
  103 +
  104 +所有接口的查询条件都相同:
  105 +
  106 +```csharp
  107 +.Where(x =>
  108 + x.StatisticsMonth == monthStr // 统计月份匹配
  109 + && x.EmployeeId == input.EmployeeId // 员工ID匹配
  110 + && x.IsLocked == 1 // 只查询已锁定的工资
  111 + && x.EmployeeConfirmStatus != 1 // 只查询未确认的工资
  112 +)
  113 +```
  114 +
  115 +**关键点**:
  116 +- ✅ **只查询已锁定的工资**:`IsLocked == 1`
  117 +- ✅ **只查询未确认的工资**:`EmployeeConfirmStatus != 1`(已确认的工资无法查询)
  118 +- ✅ **员工ID匹配**:`EmployeeId == input.EmployeeId`
  119 +- ✅ **月份匹配**:`StatisticsMonth == monthStr`(格式:YYYYMM)
  120 +
  121 +**重要说明**:
  122 +- 员工只能查看**已锁定但未确认**的工资记录
  123 +- 一旦员工确认工资后(`EmployeeConfirmStatus = 1`),该工资记录将无法通过此接口查询
  124 +- 这个设计确保员工在确认工资后,无法再次查看已确认的工资记录
  125 +
  126 +### 参数验证
  127 +
  128 +所有接口都进行相同的参数验证:
  129 +
  130 +```csharp
  131 +// 1. 验证年份和月份
  132 +if (input.Year <= 0 || input.Month <= 0 || input.Month > 12)
  133 +{
  134 + throw NCCException.Oh("年份和月份参数不正确");
  135 +}
  136 +
  137 +// 2. 验证员工ID
  138 +if (string.IsNullOrWhiteSpace(input.EmployeeId))
  139 +{
  140 + throw NCCException.Oh("员工ID不能为空");
  141 +}
  142 +
  143 +// 3. 格式化月份
  144 +var monthStr = $"{input.Year}{input.Month:D2}"; // 例如:202512
  145 +```
  146 +
  147 +### 查询结果处理
  148 +
  149 +```csharp
  150 +// 查询工资记录
  151 +var salary = await _db.Queryable<SalaryStatisticsEntity>()
  152 + .Where(x => x.StatisticsMonth == monthStr
  153 + && x.EmployeeId == input.EmployeeId
  154 + && x.IsLocked == 1)
  155 + .Select(x => new SalaryOutput { /* 字段映射 */ })
  156 + .FirstAsync();
  157 +
  158 +// 如果未找到,抛出异常
  159 +if (salary == null)
  160 +{
  161 + throw NCCException.Oh($"未找到员工{input.EmployeeId}在{input.Year}年{input.Month}月的工资记录");
  162 +}
  163 +
  164 +return salary;
  165 +```
  166 +
  167 +---
  168 +
  169 +## 🔐 安全机制
  170 +
  171 +### 1. 锁定机制
  172 +- **只查询已锁定的工资**:`IsLocked == 1`
  173 +- **目的**:确保员工只能查看已完成的工资数据,避免查看未完成计算的工资
  174 +- **业务逻辑**:工资计算完成后,管理员需要先锁定工资,员工才能查看
  175 +
  176 +### 1.1 确认状态限制
  177 +- **只查询未确认的工资**:`EmployeeConfirmStatus != 1`
  178 +- **目的**:员工确认工资后,该工资记录将无法再次查询
  179 +- **业务逻辑**:
  180 + 1. 工资计算完成 → 管理员锁定(`IsLocked = 1`)
  181 + 2. 员工可以查看工资(`IsLocked = 1` 且 `EmployeeConfirmStatus != 1`)
  182 + 3. 员工确认工资(`EmployeeConfirmStatus = 1`)
  183 + 4. 确认后无法再次查询(`EmployeeConfirmStatus = 1` 的记录被排除)
  184 +
  185 +### 2. 员工ID匹配
  186 +- **精确匹配**:`EmployeeId == input.EmployeeId`
  187 +- **目的**:确保员工只能查看自己的工资,不能查看其他员工的工资
  188 +- **实现方式**:
  189 + - 后端:通过SQL查询条件 `EmployeeId == input.EmployeeId` 实现精确匹配
  190 + - 前端:从本地存储获取当前登录用户的ID,自动填充到查询参数中
  191 +
  192 +### 3. 月份限制
  193 +- **格式验证**:月份必须在 1-12 之间
  194 +- **目的**:确保查询参数的有效性
  195 +
  196 +### 4. 权限验证说明
  197 +
  198 +#### 当前实现
  199 +- **后端**:接口**没有**验证当前登录用户,只通过 `EmployeeId` 参数查询
  200 +- **前端**:从 `uni.getStorageSync('userInfo')` 获取用户ID,自动填充到查询参数
  201 +- **安全依赖**:依赖前端确保传入的 `EmployeeId` 是当前登录用户的ID
  202 +
  203 +#### 潜在安全问题
  204 +- **风险**:如果前端被篡改,可能会查询到其他员工的工资
  205 +- **现状**:目前通过SQL查询条件 `EmployeeId == input.EmployeeId` 实现精确匹配,但**没有验证** `input.EmployeeId` 是否与当前登录用户ID一致
  206 +
  207 +#### 建议改进
  208 +可以在后端增加权限验证,确保员工只能查询自己的工资:
  209 +
  210 +```csharp
  211 +// 获取当前登录用户ID
  212 +var currentUserId = _userManager.UserId;
  213 +
  214 +// 验证:员工只能查询自己的工资
  215 +if (input.EmployeeId != currentUserId && !_userManager.IsAdministrator)
  216 +{
  217 + throw NCCException.Oh("您只能查询自己的工资记录");
  218 +}
  219 +```
  220 +
  221 +**注意**:管理员可能需要查询所有员工的工资,所以需要判断 `IsAdministrator`
  222 +
  223 +---
  224 +
  225 +## 📊 数据流程
  226 +
  227 +### 查询流程
  228 +
  229 +```
  230 +1. 接收请求参数(Year, Month, EmployeeId)
  231 + ↓
  232 +2. 参数验证
  233 + - 年份和月份有效性检查
  234 + - 员工ID非空检查
  235 + ↓
  236 +3. 格式化月份(YYYYMM格式)
  237 + ↓
  238 +4. 查询数据库
  239 + - 条件:StatisticsMonth == monthStr
  240 + - 条件:EmployeeId == input.EmployeeId
  241 + - 条件:IsLocked == 1(已锁定)
  242 + - 条件:EmployeeConfirmStatus != 1(未确认)
  243 + ↓
  244 +5. 数据映射(Entity → Output DTO)
  245 + ↓
  246 +6. 结果验证
  247 + - 如果未找到,抛出异常
  248 + ↓
  249 +7. 返回工资记录
  250 +```
  251 +
  252 +### 数据表结构
  253 +
  254 +每个薪酬服务对应一个工资统计表:
  255 +
  256 +| 服务 | 数据表 | 主键字段 | 员工ID字段 | 月份字段 | 锁定字段 |
  257 +|------|--------|---------|-----------|---------|---------|
  258 +| 健康师 | `lq_salary_statistics` | `F_Id` | `F_EmployeeId` | `F_StatisticsMonth` | `F_IsLocked` |
  259 +| 店长 | `lq_store_manager_salary_statistics` | `F_Id` | `F_EmployeeId` | `F_StatisticsMonth` | `F_IsLocked` |
  260 +| 主任 | `lq_director_salary_statistics` | `F_Id` | `F_EmployeeId` | `F_StatisticsMonth` | `F_IsLocked` |
  261 +| 店助 | `lq_assistant_salary_statistics` | `F_Id` | `F_EmployeeId` | `F_StatisticsMonth` | `F_IsLocked` |
  262 +| 事业部总经理/经理 | `lq_business_unit_manager_salary_statistics` | `F_Id` | `F_EmployeeId` | `F_StatisticsMonth` | `F_IsLocked` |
  263 +| 科技部老师 | `lq_tech_teacher_salary_statistics` | `F_Id` | `F_EmployeeId` | `F_StatisticsMonth` | `F_IsLocked` |
  264 +| 科技部总经理 | `lq_tech_general_manager_salary_statistics` | `F_Id` | `F_EmployeeId` | `F_StatisticsMonth` | `F_IsLocked` |
  265 +| 大项目主管 | `lq_major_project_director_salary_statistics` | `F_Id` | `F_EmployeeId` | `F_StatisticsMonth` | `F_IsLocked` |
  266 +| 大项目部老师 | `lq_major_project_teacher_salary_statistics` | `F_Id` | `F_EmployeeId` | `F_StatisticsMonth` | `F_IsLocked` |
  267 +
  268 +---
  269 +
  270 +## 🔄 与其他功能的关系
  271 +
  272 +### 1. 工资计算
  273 +- **关系**:查询接口依赖工资计算接口生成的数据
  274 +- **流程**:先执行计算接口(`calculate/*`),生成工资记录,然后才能查询
  275 +
  276 +### 2. 工资锁定
  277 +- **关系**:查询接口只返回已锁定的工资
  278 +- **流程**:工资计算完成后,需要锁定(`IsLocked = 1`),员工才能查看
  279 +
  280 +### 3. 员工确认
  281 +- **关系**:查询接口返回的数据包含确认状态(`EmployeeConfirmStatus`)
  282 +- **流程**:员工查看工资后,可以确认工资条
  283 +
  284 +---
  285 +
  286 +## ⚠️ 注意事项
  287 +
  288 +### 1. 锁定状态
  289 +- **必须锁定**:只有已锁定的工资才能被员工查询
  290 +- **未锁定处理**:如果工资未锁定,查询接口会返回404错误
  291 +
  292 +### 1.1 确认状态
  293 +- **必须未确认**:只有未确认的工资才能被员工查询
  294 +- **已确认处理**:如果工资已确认(`EmployeeConfirmStatus = 1`),查询接口会返回404错误
  295 +- **业务含义**:员工确认工资后,该工资记录将无法再次查询,确保数据安全
  296 +
  297 +### 2. 员工ID匹配
  298 +- **精确匹配**:必须使用正确的员工ID
  299 +- **安全考虑**:接口不验证当前登录用户,需要前端或中间件确保员工只能查询自己的工资
  300 +
  301 +### 3. 月份格式
  302 +- **格式要求**:月份必须格式化为 YYYYMM(如:202512)
  303 +- **验证**:月份必须在 1-12 之间
  304 +
  305 +### 4. 数据完整性
  306 +- **字段映射**:每个服务的Output DTO字段可能不同
  307 +- **空值处理**:如果未找到记录,返回404错误
  308 +
  309 +---
  310 +
  311 +## 📋 接口调用示例
  312 +
  313 +### 健康师工资查询
  314 +```http
  315 +GET /api/Extend/lqsalary/query-by-employee?Year=2025&Month=12&EmployeeId=员工ID
  316 +```
  317 +
  318 +### 店长工资查询
  319 +```http
  320 +GET /api/Extend/lqstoremanagersalary/query-by-employee?Year=2025&Month=12&EmployeeId=员工ID
  321 +```
  322 +
  323 +### 主任工资查询
  324 +```http
  325 +GET /api/Extend/lqdirectorsalary/query-by-employee?Year=2025&Month=12&EmployeeId=员工ID
  326 +```
  327 +
  328 +### 店助工资查询
  329 +```http
  330 +GET /api/Extend/lqassistantsalary/query-by-employee?Year=2025&Month=12&EmployeeId=员工ID
  331 +```
  332 +
  333 +### 事业部总经理/经理工资查询
  334 +```http
  335 +GET /api/Extend/lqbusinessunitmanagersalary/query-by-employee?Year=2025&Month=12&EmployeeId=员工ID
  336 +```
  337 +
  338 +### 科技部老师工资查询
  339 +```http
  340 +GET /api/Extend/lqtechteachersalary/query-by-employee?Year=2025&Month=12&EmployeeId=员工ID
  341 +```
  342 +
  343 +### 科技部总经理工资查询
  344 +```http
  345 +GET /api/Extend/lqtechgeneralmanagersalary/query-by-employee?Year=2025&Month=12&EmployeeId=员工ID
  346 +```
  347 +
  348 +### 大项目主管工资查询
  349 +```http
  350 +GET /api/Extend/lqmajorprojectdirectorsalary/query-by-employee?Year=2025&Month=12&EmployeeId=员工ID
  351 +```
  352 +
  353 +### 大项目部老师工资查询
  354 +```http
  355 +GET /api/Extend/lqmajorprojectteachersalary/query-by-employee?Year=2025&Month=12&EmployeeId=员工ID
  356 +```
  357 +
  358 +---
  359 +
  360 +## 🔍 代码实现对比
  361 +
  362 +### 共同点
  363 +
  364 +所有服务的查询逻辑都相同:
  365 +
  366 +1. **参数验证**:年份、月份、员工ID验证
  367 +2. **月份格式化**:`$"{input.Year}{input.Month:D2}"`
  368 +3. **查询条件**:`StatisticsMonth == monthStr && EmployeeId == input.EmployeeId && IsLocked == 1`
  369 +4. **异常处理**:未找到记录时抛出异常
  370 +5. **返回类型**:返回对应的Output DTO
  371 +
  372 +### 差异点
  373 +
  374 +1. **数据表不同**:每个服务查询不同的工资统计表
  375 +2. **Output DTO不同**:每个服务返回的字段可能不同
  376 +3. **字段映射不同**:根据岗位不同,返回的工资字段不同
  377 +
  378 +---
  379 +
  380 +## 📝 总结
  381 +
  382 +### 核心逻辑
  383 +
  384 +1. **统一接口**:所有薪酬服务都提供 `query-by-employee` 接口
  385 +2. **统一参数**:都使用 `SalaryQueryByEmployeeInput` 作为输入参数
  386 +3. **统一条件**:都查询已锁定(`IsLocked == 1`)的工资记录
  387 +4. **统一验证**:都进行相同的参数验证和异常处理
  388 +
  389 +### 安全机制
  390 +
  391 +1. **锁定检查**:只查询已锁定的工资
  392 +2. **员工匹配**:精确匹配员工ID
  393 +3. **参数验证**:验证年份、月份、员工ID的有效性
  394 +
  395 +### 使用场景
  396 +
  397 +1. **员工查看工资条**:员工通过小程序或PC端查看自己的工资
  398 +2. **工资确认**:员工查看工资后,可以确认工资条
  399 +3. **历史查询**:员工可以查询历史月份的工资记录
  400 +
  401 +---
  402 +
  403 +**文档版本**: v1.0
  404 +**创建日期**: 2026-01-09
  405 +**适用范围**: 所有薪酬服务的员工工资查询接口
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs
... ... @@ -124,8 +124,12 @@ namespace NCC.Extend
124 124 }
125 125  
126 126 /// <summary>
127   - /// 通过月份和员工ID查询工资(仅查询已锁定的工资)
  127 + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资)
128 128 /// </summary>
  129 + /// <remarks>
  130 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录
  131 + /// **注意**:已确认的工资记录无法通过此接口查询
  132 + /// </remarks>
129 133 [HttpGet("query-by-employee")]
130 134 public async Task<AssistantSalaryOutput> GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input)
131 135 {
... ... @@ -135,7 +139,7 @@ namespace NCC.Extend
135 139 throw NCCException.Oh("员工ID不能为空");
136 140 var monthStr = $"{input.Year}{input.Month:D2}";
137 141 var salary = await _db.Queryable<LqAssistantSalaryStatisticsEntity>()
138   - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1)
  142 + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1)
139 143 .Select(x => new AssistantSalaryOutput
140 144 {
141 145 Id = x.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqBusinessUnitManagerSalaryService.cs
... ... @@ -132,8 +132,12 @@ namespace NCC.Extend
132 132 }
133 133  
134 134 /// <summary>
135   - /// 通过月份和员工ID查询工资(仅查询已锁定的工资)
  135 + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资)
136 136 /// </summary>
  137 + /// <remarks>
  138 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录
  139 + /// **注意**:已确认的工资记录无法通过此接口查询
  140 + /// </remarks>
137 141 [HttpGet("query-by-employee")]
138 142 public async Task<BusinessUnitManagerSalaryOutput> GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input)
139 143 {
... ... @@ -143,7 +147,7 @@ namespace NCC.Extend
143 147 throw NCCException.Oh("员工ID不能为空");
144 148 var monthStr = $"{input.Year}{input.Month:D2}";
145 149 var salary = await _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>()
146   - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1)
  150 + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1)
147 151 .Select(x => new BusinessUnitManagerSalaryOutput
148 152 {
149 153 Id = x.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs
... ... @@ -154,8 +154,12 @@ namespace NCC.Extend
154 154 }
155 155  
156 156 /// <summary>
157   - /// 通过月份和员工ID查询工资(仅查询已锁定的工资)
  157 + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资)
158 158 /// </summary>
  159 + /// <remarks>
  160 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录
  161 + /// **注意**:已确认的工资记录无法通过此接口查询
  162 + /// </remarks>
159 163 [HttpGet("query-by-employee")]
160 164 public async Task<DirectorSalaryOutput> GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input)
161 165 {
... ... @@ -165,7 +169,7 @@ namespace NCC.Extend
165 169 throw NCCException.Oh("员工ID不能为空");
166 170 var monthStr = $"{input.Year}{input.Month:D2}";
167 171 var salary = await _db.Queryable<LqDirectorSalaryStatisticsEntity>()
168   - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1)
  172 + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1)
169 173 .Select(x => new DirectorSalaryOutput
170 174 {
171 175 Id = x.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs
... ... @@ -127,8 +127,12 @@ namespace NCC.Extend
127 127 }
128 128  
129 129 /// <summary>
130   - /// 通过月份和员工ID查询工资(仅查询已锁定的工资)
  130 + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资)
131 131 /// </summary>
  132 + /// <remarks>
  133 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录
  134 + /// **注意**:已确认的工资记录无法通过此接口查询
  135 + /// </remarks>
132 136 [HttpGet("query-by-employee")]
133 137 public async Task<MajorProjectDirectorSalaryOutput> GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input)
134 138 {
... ... @@ -138,7 +142,7 @@ namespace NCC.Extend
138 142 throw NCCException.Oh("员工ID不能为空");
139 143 var monthStr = $"{input.Year}{input.Month:D2}";
140 144 var salary = await _db.Queryable<LqMajorProjectDirectorSalaryStatisticsEntity>()
141   - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1)
  145 + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1)
142 146 .Select(x => new MajorProjectDirectorSalaryOutput
143 147 {
144 148 Id = x.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs
... ... @@ -142,8 +142,12 @@ namespace NCC.Extend
142 142 }
143 143  
144 144 /// <summary>
145   - /// 通过月份和员工ID查询工资(仅查询已锁定的工资)
  145 + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资)
146 146 /// </summary>
  147 + /// <remarks>
  148 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录
  149 + /// **注意**:已确认的工资记录无法通过此接口查询
  150 + /// </remarks>
147 151 [HttpGet("query-by-employee")]
148 152 public async Task<MajorProjectTeacherSalaryOutput> GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input)
149 153 {
... ... @@ -153,7 +157,7 @@ namespace NCC.Extend
153 157 throw NCCException.Oh("员工ID不能为空");
154 158 var monthStr = $"{input.Year}{input.Month:D2}";
155 159 var salary = await _db.Queryable<LqMajorProjectTeacherSalaryStatisticsEntity>()
156   - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1)
  160 + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1)
157 161 .Select(x => new MajorProjectTeacherSalaryOutput
158 162 {
159 163 Id = x.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
... ... @@ -175,12 +175,13 @@ namespace NCC.Extend
175 175 }
176 176  
177 177 /// <summary>
178   - /// 通过月份和员工ID查询工资(仅查询已锁定的工资)
  178 + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资)
179 179 /// </summary>
180 180 /// <remarks>
181 181 /// 根据年份、月份和员工ID查询对应员工的工资记录
182 182 ///
183   - /// **重要**:此接口只能查询已锁定(IsLocked=1)的工资记录
  183 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录
  184 + /// **注意**:已确认的工资记录无法通过此接口查询
184 185 ///
185 186 /// 示例请求:
186 187 /// ```
... ... @@ -212,7 +213,7 @@ namespace NCC.Extend
212 213 var monthStr = $"{input.Year}{input.Month:D2}";
213 214  
214 215 var salary = await _db.Queryable<LqSalaryStatisticsEntity>()
215   - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1)
  216 + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1)
216 217 .Select(x => new HealthCoachSalaryOutput
217 218 {
218 219 Id = x.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs
... ... @@ -133,8 +133,12 @@ namespace NCC.Extend
133 133 }
134 134  
135 135 /// <summary>
136   - /// 通过月份和员工ID查询工资(仅查询已锁定的工资)
  136 + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资)
137 137 /// </summary>
  138 + /// <remarks>
  139 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录
  140 + /// **注意**:已确认的工资记录无法通过此接口查询
  141 + /// </remarks>
138 142 [HttpGet("query-by-employee")]
139 143 public async Task<StoreManagerSalaryOutput> GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input)
140 144 {
... ... @@ -144,7 +148,7 @@ namespace NCC.Extend
144 148 throw NCCException.Oh("员工ID不能为空");
145 149 var monthStr = $"{input.Year}{input.Month:D2}";
146 150 var salary = await _db.Queryable<LqStoreManagerSalaryStatisticsEntity>()
147   - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1)
  151 + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1)
148 152 .Select(x => new StoreManagerSalaryOutput
149 153 {
150 154 Id = x.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.cs
... ... @@ -131,8 +131,12 @@ namespace NCC.Extend
131 131 }
132 132  
133 133 /// <summary>
134   - /// 通过月份和员工ID查询工资(仅查询已锁定的工资)
  134 + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资)
135 135 /// </summary>
  136 + /// <remarks>
  137 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录
  138 + /// **注意**:已确认的工资记录无法通过此接口查询
  139 + /// </remarks>
136 140 [HttpGet("query-by-employee")]
137 141 public async Task<TechGeneralManagerSalaryOutput> GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input)
138 142 {
... ... @@ -142,7 +146,7 @@ namespace NCC.Extend
142 146 throw NCCException.Oh("员工ID不能为空");
143 147 var monthStr = $"{input.Year}{input.Month:D2}";
144 148 var salary = await _db.Queryable<LqTechGeneralManagerSalaryStatisticsEntity>()
145   - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1)
  149 + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1)
146 150 .Select(x => new TechGeneralManagerSalaryOutput
147 151 {
148 152 Id = x.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs
... ... @@ -146,8 +146,12 @@ namespace NCC.Extend
146 146 }
147 147  
148 148 /// <summary>
149   - /// 通过月份和员工ID查询工资(仅查询已锁定的工资)
  149 + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资)
150 150 /// </summary>
  151 + /// <remarks>
  152 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录
  153 + /// **注意**:已确认的工资记录无法通过此接口查询
  154 + /// </remarks>
151 155 [HttpGet("query-by-employee")]
152 156 public async Task<TechTeacherSalaryOutput> GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input)
153 157 {
... ... @@ -157,7 +161,7 @@ namespace NCC.Extend
157 161 throw NCCException.Oh("员工ID不能为空");
158 162 var monthStr = $"{input.Year}{input.Month:D2}";
159 163 var salary = await _db.Queryable<LqTechTeacherSalaryStatisticsEntity>()
160   - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1)
  164 + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1)
161 165 .Select(x => new TechTeacherSalaryOutput
162 166 {
163 167 Id = x.Id,
... ...
netcore/src/Modularity/System/NCC.System.Entitys/Dto/Permission/User/UserListQuery.cs
... ... @@ -13,5 +13,20 @@ namespace NCC.System.Entitys.Dto.Permission.User
13 13 /// 机构ID
14 14 /// </summary>
15 15 public string organizeId { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 门店ID(F_MDID)
  19 + /// </summary>
  20 + public string mdid { get; set; }
  21 +
  22 + /// <summary>
  23 + /// 岗位(F_GW)
  24 + /// </summary>
  25 + public string gw { get; set; }
  26 +
  27 + /// <summary>
  28 + /// 启用状态(0=禁用,1=启用,null=全部)
  29 + /// </summary>
  30 + public int? enabledMark { get; set; }
16 31 }
17 32 }
... ...
netcore/src/Modularity/System/NCC.System/Service/Permission/UsersService.cs
... ... @@ -136,6 +136,9 @@ namespace NCC.System.Service.Permission
136 136 //组织机构
137 137 .WhereIF(organizeIds.Any(), u => organizeIds.Contains(SqlFunc.ToString(u.organizeId)))
138 138 .WhereIF(!pageInput.keyword.IsNullOrEmpty(), u => u.account.Contains(pageInput.keyword) || u.realName.Contains(pageInput.keyword))
  139 + .WhereIF(!input.mdid.IsNullOrEmpty(), u => u.mdid == input.mdid)
  140 + .WhereIF(!input.gw.IsNullOrEmpty(), u => u.gw == input.gw)
  141 + .WhereIF(input.enabledMark.HasValue, u => u.enabledMark == input.enabledMark)
139 142 .Where(a => a.deleteMark == null)
140 143 .OrderBy(a => a.sortCode).OrderBy(a => a.creatorTime, OrderByType.Desc)
141 144 .ToPagedListAsync(pageInput.currentPage, pageInput.pageSize);
... ...
scripts/sh/test_user_list_search.sh 0 → 100755
  1 +#!/bin/bash
  2 +
  3 +# 测试用户列表接口 - 门店ID和岗位搜索功能
  4 +# 使用方法: ./scripts/sh/test_user_list_search.sh
  5 +
  6 +BASE_URL="http://localhost:2011"
  7 +TOKEN=""
  8 +
  9 +echo "=========================================="
  10 +echo "测试用户列表接口 - 门店ID和岗位搜索"
  11 +echo "=========================================="
  12 +echo ""
  13 +
  14 +# 1. 登录获取token
  15 +echo "1. 登录获取token..."
  16 +LOGIN_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/oauth/Login" \
  17 + -H "Content-Type: application/x-www-form-urlencoded" \
  18 + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e")
  19 +
  20 +TOKEN=$(echo $LOGIN_RESPONSE | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['token'])" 2>/dev/null)
  21 +
  22 +if [ -z "$TOKEN" ]; then
  23 + echo "❌ 登录失败,无法获取token"
  24 + exit 1
  25 +fi
  26 +
  27 +echo "✅ Token获取成功"
  28 +echo ""
  29 +
  30 +# 2. 测试基本查询(无搜索条件)
  31 +echo "2. 测试基本查询(无搜索条件)..."
  32 +BASIC_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5" \
  33 + -H "Authorization: ${TOKEN}" \
  34 + -H "Content-Type: application/json")
  35 +
  36 +echo "$BASIC_RESPONSE" | python3 -m json.tool | head -30
  37 +echo ""
  38 +
  39 +# 3. 测试岗位搜索(gw=健康师)
  40 +echo "3. 测试岗位搜索(gw=健康师)..."
  41 +GW_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5&gw=健康师" \
  42 + -H "Authorization: ${TOKEN}" \
  43 + -H "Content-Type: application/json")
  44 +
  45 +echo "$GW_RESPONSE" | python3 -m json.tool | head -40
  46 +echo ""
  47 +
  48 +# 4. 测试门店ID搜索(使用实际的门店ID)
  49 +echo "4. 测试门店ID搜索..."
  50 +MDID="1649328471923847168"
  51 +MDID_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5&mdid=${MDID}" \
  52 + -H "Authorization: ${TOKEN}" \
  53 + -H "Content-Type: application/json")
  54 +
  55 +echo "$MDID_RESPONSE" | python3 -m json.tool | head -40
  56 +echo ""
  57 +
  58 +# 5. 测试组合搜索(门店ID + 岗位)
  59 +echo "5. 测试组合搜索(门店ID + 岗位)..."
  60 +COMBINED_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5&mdid=${MDID}&gw=健康师" \
  61 + -H "Authorization: ${TOKEN}" \
  62 + -H "Content-Type: application/json")
  63 +
  64 +echo "$COMBINED_RESPONSE" | python3 -m json.tool | head -40
  65 +echo ""
  66 +
  67 +# 6. 测试其他岗位
  68 +echo "6. 测试其他岗位(gw=店助)..."
  69 +GW2_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5&gw=店助" \
  70 + -H "Authorization: ${TOKEN}" \
  71 + -H "Content-Type: application/json")
  72 +
  73 +echo "$GW2_RESPONSE" | python3 -m json.tool | head -40
  74 +echo ""
  75 +
  76 +echo "=========================================="
  77 +echo "测试完成"
  78 +echo "=========================================="
... ...
scripts/sh/test_user_list_status_filter.sh 0 → 100755
  1 +#!/bin/bash
  2 +
  3 +# 测试用户列表接口 - 状态筛选功能
  4 +# 使用方法: ./scripts/sh/test_user_list_status_filter.sh
  5 +
  6 +BASE_URL="http://localhost:2011"
  7 +TOKEN=""
  8 +
  9 +echo "=========================================="
  10 +echo "测试用户列表接口 - 状态筛选功能"
  11 +echo "=========================================="
  12 +echo ""
  13 +
  14 +# 1. 登录获取token
  15 +echo "1. 登录获取token..."
  16 +LOGIN_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/oauth/Login" \
  17 + -H "Content-Type: application/x-www-form-urlencoded" \
  18 + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e")
  19 +
  20 +TOKEN=$(echo $LOGIN_RESPONSE | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['token'])" 2>/dev/null)
  21 +
  22 +if [ -z "$TOKEN" ]; then
  23 + echo "❌ 登录失败,无法获取token"
  24 + exit 1
  25 +fi
  26 +
  27 +echo "✅ Token获取成功"
  28 +echo ""
  29 +
  30 +# 2. 测试查询全部(不筛选状态)
  31 +echo "2. 测试查询全部(不筛选状态)..."
  32 +ALL_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3" \
  33 + -H "Authorization: ${TOKEN}" \
  34 + -H "Content-Type: application/json")
  35 +
  36 +ALL_TOTAL=$(echo "$ALL_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null)
  37 +echo " 总记录数: ${ALL_TOTAL}"
  38 +echo ""
  39 +
  40 +# 3. 测试查询启用状态(enabledMark=1)
  41 +echo "3. 测试查询启用状态(enabledMark=1)..."
  42 +ENABLED_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3&enabledMark=1" \
  43 + -H "Authorization: ${TOKEN}" \
  44 + -H "Content-Type: application/json")
  45 +
  46 +ENABLED_TOTAL=$(echo "$ENABLED_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null)
  47 +echo " 总记录数: ${ENABLED_TOTAL}"
  48 +echo "$ENABLED_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20
  49 +echo ""
  50 +
  51 +# 4. 测试查询禁用状态(enabledMark=0)
  52 +echo "4. 测试查询禁用状态(enabledMark=0)..."
  53 +DISABLED_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3&enabledMark=0" \
  54 + -H "Authorization: ${TOKEN}" \
  55 + -H "Content-Type: application/json")
  56 +
  57 +DISABLED_TOTAL=$(echo "$DISABLED_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null)
  58 +echo " 总记录数: ${DISABLED_TOTAL}"
  59 +echo "$DISABLED_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20
  60 +echo ""
  61 +
  62 +# 5. 测试组合搜索(状态 + 岗位)
  63 +echo "5. 测试组合搜索(启用状态 + 岗位=健康师)..."
  64 +COMBINED1_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3&enabledMark=1&gw=%E5%81%A5%E5%BA%B7%E5%B8%88" \
  65 + -H "Authorization: ${TOKEN}" \
  66 + -H "Content-Type: application/json")
  67 +
  68 +COMBINED1_TOTAL=$(echo "$COMBINED1_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null)
  69 +echo " 总记录数: ${COMBINED1_TOTAL}"
  70 +echo "$COMBINED1_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20
  71 +echo ""
  72 +
  73 +# 6. 测试组合搜索(状态 + 门店ID)
  74 +echo "6. 测试组合搜索(启用状态 + 门店ID)..."
  75 +COMBINED2_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3&enabledMark=1&mdid=1649328471923847168" \
  76 + -H "Authorization: ${TOKEN}" \
  77 + -H "Content-Type: application/json")
  78 +
  79 +COMBINED2_TOTAL=$(echo "$COMBINED2_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null)
  80 +echo " 总记录数: ${COMBINED2_TOTAL}"
  81 +echo "$COMBINED2_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20
  82 +echo ""
  83 +
  84 +# 7. 测试组合搜索(状态 + 门店ID + 岗位)
  85 +echo "7. 测试组合搜索(启用状态 + 门店ID + 岗位)..."
  86 +COMBINED3_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3&enabledMark=1&mdid=1649328471923847168&gw=%E5%81%A5%E5%BA%B7%E5%B8%88" \
  87 + -H "Authorization: ${TOKEN}" \
  88 + -H "Content-Type: application/json")
  89 +
  90 +COMBINED3_TOTAL=$(echo "$COMBINED3_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null)
  91 +echo " 总记录数: ${COMBINED3_TOTAL}"
  92 +echo "$COMBINED3_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20
  93 +echo ""
  94 +
  95 +# 8. 验证返回数据的状态字段
  96 +echo "8. 验证返回数据的状态字段..."
  97 +echo "$ENABLED_RESPONSE" | python3 -c "
  98 +import sys, json
  99 +data = json.load(sys.stdin)
  100 +if data['code'] == 200:
  101 + users = data['data']['list']
  102 + print(f' 检查前3条记录的状态字段:')
  103 + for i, u in enumerate(users[:3], 1):
  104 + enabled = u.get('enabledMark', 'N/A')
  105 + print(f' 用户{i}: {u[\"realName\"]} - enabledMark: {enabled}')
  106 +else:
  107 + print(' 查询失败')
  108 +"
  109 +echo ""
  110 +
  111 +echo "=========================================="
  112 +echo "测试完成"
  113 +echo "=========================================="
  114 +echo ""
  115 +echo "测试结果汇总:"
  116 +echo " 全部用户: ${ALL_TOTAL}"
  117 +echo " 启用用户: ${ENABLED_TOTAL}"
  118 +echo " 禁用用户: ${DISABLED_TOTAL}"
  119 +echo " 启用+健康师: ${COMBINED1_TOTAL}"
  120 +echo " 启用+门店: ${COMBINED2_TOTAL}"
  121 +echo " 启用+门店+健康师: ${COMBINED3_TOTAL}"
... ...