Blame view

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