diff --git a/docs/员工工资查询接口逻辑梳理.md b/docs/员工工资查询接口逻辑梳理.md new file mode 100644 index 0000000..2951ca5 --- /dev/null +++ b/docs/员工工资查询接口逻辑梳理.md @@ -0,0 +1,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 +{ + /// + /// 年份 + /// + public int Year { get; set; } + + /// + /// 月份 + /// + public int Month { get; set; } + + /// + /// 员工ID + /// + 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() + .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 +**适用范围**: 所有薪酬服务的员工工资查询接口 diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs index 8f05181..6d25715 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs @@ -124,8 +124,12 @@ namespace NCC.Extend } /// - /// 通过月份和员工ID查询工资(仅查询已锁定的工资) + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资) /// + /// + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录 + /// **注意**:已确认的工资记录无法通过此接口查询 + /// [HttpGet("query-by-employee")] public async Task GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input) { @@ -135,7 +139,7 @@ namespace NCC.Extend throw NCCException.Oh("员工ID不能为空"); var monthStr = $"{input.Year}{input.Month:D2}"; var salary = await _db.Queryable() - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1) + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1) .Select(x => new AssistantSalaryOutput { Id = x.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqBusinessUnitManagerSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqBusinessUnitManagerSalaryService.cs index 6977ef6..78eaa8d 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqBusinessUnitManagerSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqBusinessUnitManagerSalaryService.cs @@ -132,8 +132,12 @@ namespace NCC.Extend } /// - /// 通过月份和员工ID查询工资(仅查询已锁定的工资) + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资) /// + /// + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录 + /// **注意**:已确认的工资记录无法通过此接口查询 + /// [HttpGet("query-by-employee")] public async Task GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input) { @@ -143,7 +147,7 @@ namespace NCC.Extend throw NCCException.Oh("员工ID不能为空"); var monthStr = $"{input.Year}{input.Month:D2}"; var salary = await _db.Queryable() - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1) + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1) .Select(x => new BusinessUnitManagerSalaryOutput { Id = x.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs index 847972b..ad0ddcc 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs @@ -154,8 +154,12 @@ namespace NCC.Extend } /// - /// 通过月份和员工ID查询工资(仅查询已锁定的工资) + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资) /// + /// + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录 + /// **注意**:已确认的工资记录无法通过此接口查询 + /// [HttpGet("query-by-employee")] public async Task GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input) { @@ -165,7 +169,7 @@ namespace NCC.Extend throw NCCException.Oh("员工ID不能为空"); var monthStr = $"{input.Year}{input.Month:D2}"; var salary = await _db.Queryable() - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1) + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1) .Select(x => new DirectorSalaryOutput { Id = x.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs index 440c4c1..da81aa9 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs @@ -127,8 +127,12 @@ namespace NCC.Extend } /// - /// 通过月份和员工ID查询工资(仅查询已锁定的工资) + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资) /// + /// + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录 + /// **注意**:已确认的工资记录无法通过此接口查询 + /// [HttpGet("query-by-employee")] public async Task GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input) { @@ -138,7 +142,7 @@ namespace NCC.Extend throw NCCException.Oh("员工ID不能为空"); var monthStr = $"{input.Year}{input.Month:D2}"; var salary = await _db.Queryable() - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1) + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1) .Select(x => new MajorProjectDirectorSalaryOutput { Id = x.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs index 8433524..c46b991 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs @@ -142,8 +142,12 @@ namespace NCC.Extend } /// - /// 通过月份和员工ID查询工资(仅查询已锁定的工资) + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资) /// + /// + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录 + /// **注意**:已确认的工资记录无法通过此接口查询 + /// [HttpGet("query-by-employee")] public async Task GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input) { @@ -153,7 +157,7 @@ namespace NCC.Extend throw NCCException.Oh("员工ID不能为空"); var monthStr = $"{input.Year}{input.Month:D2}"; var salary = await _db.Queryable() - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1) + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1) .Select(x => new MajorProjectTeacherSalaryOutput { Id = x.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs index cfcb1f7..da4ab24 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs @@ -175,12 +175,13 @@ namespace NCC.Extend } /// - /// 通过月份和员工ID查询工资(仅查询已锁定的工资) + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资) /// /// /// 根据年份、月份和员工ID查询对应员工的工资记录 /// - /// **重要**:此接口只能查询已锁定(IsLocked=1)的工资记录 + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录 + /// **注意**:已确认的工资记录无法通过此接口查询 /// /// 示例请求: /// ``` @@ -212,7 +213,7 @@ namespace NCC.Extend var monthStr = $"{input.Year}{input.Month:D2}"; var salary = await _db.Queryable() - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1) + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1) .Select(x => new HealthCoachSalaryOutput { Id = x.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs index 10d7482..34d0677 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs @@ -133,8 +133,12 @@ namespace NCC.Extend } /// - /// 通过月份和员工ID查询工资(仅查询已锁定的工资) + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资) /// + /// + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录 + /// **注意**:已确认的工资记录无法通过此接口查询 + /// [HttpGet("query-by-employee")] public async Task GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input) { @@ -144,7 +148,7 @@ namespace NCC.Extend throw NCCException.Oh("员工ID不能为空"); var monthStr = $"{input.Year}{input.Month:D2}"; var salary = await _db.Queryable() - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1) + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1) .Select(x => new StoreManagerSalaryOutput { Id = x.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.cs index 8765b4d..f2d1466 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.cs @@ -131,8 +131,12 @@ namespace NCC.Extend } /// - /// 通过月份和员工ID查询工资(仅查询已锁定的工资) + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资) /// + /// + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录 + /// **注意**:已确认的工资记录无法通过此接口查询 + /// [HttpGet("query-by-employee")] public async Task GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input) { @@ -142,7 +146,7 @@ namespace NCC.Extend throw NCCException.Oh("员工ID不能为空"); var monthStr = $"{input.Year}{input.Month:D2}"; var salary = await _db.Queryable() - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1) + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1) .Select(x => new TechGeneralManagerSalaryOutput { Id = x.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs index b0273ed..bdd907f 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs @@ -146,8 +146,12 @@ namespace NCC.Extend } /// - /// 通过月份和员工ID查询工资(仅查询已锁定的工资) + /// 通过月份和员工ID查询工资(仅查询已锁定且未确认的工资) /// + /// + /// **重要**:此接口只能查询已锁定(IsLocked=1)且未确认(EmployeeConfirmStatus=0或null)的工资记录 + /// **注意**:已确认的工资记录无法通过此接口查询 + /// [HttpGet("query-by-employee")] public async Task GetSalaryByEmployee([FromQuery] SalaryQueryByEmployeeInput input) { @@ -157,7 +161,7 @@ namespace NCC.Extend throw NCCException.Oh("员工ID不能为空"); var monthStr = $"{input.Year}{input.Month:D2}"; var salary = await _db.Queryable() - .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1) + .Where(x => x.StatisticsMonth == monthStr && x.EmployeeId == input.EmployeeId && x.IsLocked == 1 && x.EmployeeConfirmStatus != 1) .Select(x => new TechTeacherSalaryOutput { Id = x.Id, diff --git a/netcore/src/Modularity/System/NCC.System.Entitys/Dto/Permission/User/UserListQuery.cs b/netcore/src/Modularity/System/NCC.System.Entitys/Dto/Permission/User/UserListQuery.cs index df9ac8b..cba112c 100644 --- a/netcore/src/Modularity/System/NCC.System.Entitys/Dto/Permission/User/UserListQuery.cs +++ b/netcore/src/Modularity/System/NCC.System.Entitys/Dto/Permission/User/UserListQuery.cs @@ -13,5 +13,20 @@ namespace NCC.System.Entitys.Dto.Permission.User /// 机构ID /// public string organizeId { get; set; } + + /// + /// 门店ID(F_MDID) + /// + public string mdid { get; set; } + + /// + /// 岗位(F_GW) + /// + public string gw { get; set; } + + /// + /// 启用状态(0=禁用,1=启用,null=全部) + /// + public int? enabledMark { get; set; } } } diff --git a/netcore/src/Modularity/System/NCC.System/Service/Permission/UsersService.cs b/netcore/src/Modularity/System/NCC.System/Service/Permission/UsersService.cs index f337b4f..48acbcf 100644 --- a/netcore/src/Modularity/System/NCC.System/Service/Permission/UsersService.cs +++ b/netcore/src/Modularity/System/NCC.System/Service/Permission/UsersService.cs @@ -136,6 +136,9 @@ namespace NCC.System.Service.Permission //组织机构 .WhereIF(organizeIds.Any(), u => organizeIds.Contains(SqlFunc.ToString(u.organizeId))) .WhereIF(!pageInput.keyword.IsNullOrEmpty(), u => u.account.Contains(pageInput.keyword) || u.realName.Contains(pageInput.keyword)) + .WhereIF(!input.mdid.IsNullOrEmpty(), u => u.mdid == input.mdid) + .WhereIF(!input.gw.IsNullOrEmpty(), u => u.gw == input.gw) + .WhereIF(input.enabledMark.HasValue, u => u.enabledMark == input.enabledMark) .Where(a => a.deleteMark == null) .OrderBy(a => a.sortCode).OrderBy(a => a.creatorTime, OrderByType.Desc) .ToPagedListAsync(pageInput.currentPage, pageInput.pageSize); diff --git a/scripts/sh/test_user_list_search.sh b/scripts/sh/test_user_list_search.sh new file mode 100755 index 0000000..1ba0fec --- /dev/null +++ b/scripts/sh/test_user_list_search.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# 测试用户列表接口 - 门店ID和岗位搜索功能 +# 使用方法: ./scripts/sh/test_user_list_search.sh + +BASE_URL="http://localhost:2011" +TOKEN="" + +echo "==========================================" +echo "测试用户列表接口 - 门店ID和岗位搜索" +echo "==========================================" +echo "" + +# 1. 登录获取token +echo "1. 登录获取token..." +LOGIN_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/oauth/Login" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e") + +TOKEN=$(echo $LOGIN_RESPONSE | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['token'])" 2>/dev/null) + +if [ -z "$TOKEN" ]; then + echo "❌ 登录失败,无法获取token" + exit 1 +fi + +echo "✅ Token获取成功" +echo "" + +# 2. 测试基本查询(无搜索条件) +echo "2. 测试基本查询(无搜索条件)..." +BASIC_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +echo "$BASIC_RESPONSE" | python3 -m json.tool | head -30 +echo "" + +# 3. 测试岗位搜索(gw=健康师) +echo "3. 测试岗位搜索(gw=健康师)..." +GW_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5&gw=健康师" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +echo "$GW_RESPONSE" | python3 -m json.tool | head -40 +echo "" + +# 4. 测试门店ID搜索(使用实际的门店ID) +echo "4. 测试门店ID搜索..." +MDID="1649328471923847168" +MDID_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5&mdid=${MDID}" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +echo "$MDID_RESPONSE" | python3 -m json.tool | head -40 +echo "" + +# 5. 测试组合搜索(门店ID + 岗位) +echo "5. 测试组合搜索(门店ID + 岗位)..." +COMBINED_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5&mdid=${MDID}&gw=健康师" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +echo "$COMBINED_RESPONSE" | python3 -m json.tool | head -40 +echo "" + +# 6. 测试其他岗位 +echo "6. 测试其他岗位(gw=店助)..." +GW2_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=5&gw=店助" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +echo "$GW2_RESPONSE" | python3 -m json.tool | head -40 +echo "" + +echo "==========================================" +echo "测试完成" +echo "==========================================" diff --git a/scripts/sh/test_user_list_status_filter.sh b/scripts/sh/test_user_list_status_filter.sh new file mode 100755 index 0000000..71e3600 --- /dev/null +++ b/scripts/sh/test_user_list_status_filter.sh @@ -0,0 +1,121 @@ +#!/bin/bash + +# 测试用户列表接口 - 状态筛选功能 +# 使用方法: ./scripts/sh/test_user_list_status_filter.sh + +BASE_URL="http://localhost:2011" +TOKEN="" + +echo "==========================================" +echo "测试用户列表接口 - 状态筛选功能" +echo "==========================================" +echo "" + +# 1. 登录获取token +echo "1. 登录获取token..." +LOGIN_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/oauth/Login" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e") + +TOKEN=$(echo $LOGIN_RESPONSE | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['token'])" 2>/dev/null) + +if [ -z "$TOKEN" ]; then + echo "❌ 登录失败,无法获取token" + exit 1 +fi + +echo "✅ Token获取成功" +echo "" + +# 2. 测试查询全部(不筛选状态) +echo "2. 测试查询全部(不筛选状态)..." +ALL_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +ALL_TOTAL=$(echo "$ALL_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null) +echo " 总记录数: ${ALL_TOTAL}" +echo "" + +# 3. 测试查询启用状态(enabledMark=1) +echo "3. 测试查询启用状态(enabledMark=1)..." +ENABLED_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3&enabledMark=1" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +ENABLED_TOTAL=$(echo "$ENABLED_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null) +echo " 总记录数: ${ENABLED_TOTAL}" +echo "$ENABLED_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20 +echo "" + +# 4. 测试查询禁用状态(enabledMark=0) +echo "4. 测试查询禁用状态(enabledMark=0)..." +DISABLED_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3&enabledMark=0" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +DISABLED_TOTAL=$(echo "$DISABLED_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null) +echo " 总记录数: ${DISABLED_TOTAL}" +echo "$DISABLED_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20 +echo "" + +# 5. 测试组合搜索(状态 + 岗位) +echo "5. 测试组合搜索(启用状态 + 岗位=健康师)..." +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" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +COMBINED1_TOTAL=$(echo "$COMBINED1_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null) +echo " 总记录数: ${COMBINED1_TOTAL}" +echo "$COMBINED1_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20 +echo "" + +# 6. 测试组合搜索(状态 + 门店ID) +echo "6. 测试组合搜索(启用状态 + 门店ID)..." +COMBINED2_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/permission/Users?currentPage=1&pageSize=3&enabledMark=1&mdid=1649328471923847168" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +COMBINED2_TOTAL=$(echo "$COMBINED2_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null) +echo " 总记录数: ${COMBINED2_TOTAL}" +echo "$COMBINED2_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20 +echo "" + +# 7. 测试组合搜索(状态 + 门店ID + 岗位) +echo "7. 测试组合搜索(启用状态 + 门店ID + 岗位)..." +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" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json") + +COMBINED3_TOTAL=$(echo "$COMBINED3_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['pagination']['total'])" 2>/dev/null) +echo " 总记录数: ${COMBINED3_TOTAL}" +echo "$COMBINED3_RESPONSE" | python3 -m json.tool | grep -A 10 '"list"' | head -20 +echo "" + +# 8. 验证返回数据的状态字段 +echo "8. 验证返回数据的状态字段..." +echo "$ENABLED_RESPONSE" | python3 -c " +import sys, json +data = json.load(sys.stdin) +if data['code'] == 200: + users = data['data']['list'] + print(f' 检查前3条记录的状态字段:') + for i, u in enumerate(users[:3], 1): + enabled = u.get('enabledMark', 'N/A') + print(f' 用户{i}: {u[\"realName\"]} - enabledMark: {enabled}') +else: + print(' 查询失败') +" +echo "" + +echo "==========================================" +echo "测试完成" +echo "==========================================" +echo "" +echo "测试结果汇总:" +echo " 全部用户: ${ALL_TOTAL}" +echo " 启用用户: ${ENABLED_TOTAL}" +echo " 禁用用户: ${DISABLED_TOTAL}" +echo " 启用+健康师: ${COMBINED1_TOTAL}" +echo " 启用+门店: ${COMBINED2_TOTAL}" +echo " 启用+门店+健康师: ${COMBINED3_TOTAL}"