Commit eb04731ec7152305ffeaf2021beafb394e5b1168
Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP
Showing
9 changed files
with
348 additions
and
41 deletions
docs/加班系数逻辑说明及修改方案.md
| ... | ... | @@ -19,16 +19,19 @@ |
| 19 | 19 | |
| 20 | 20 | #### 📊 **主表(lq_xh_hyhk)计算** |
| 21 | 21 | |
| 22 | +**重要**:科技部老师不参与加班,主表加班手工费仅来自健康师。 | |
| 23 | + | |
| 22 | 24 | ``` |
| 23 | -加班手工费(F_OvertimeSgfy)= 原始手工费(F_OriginalSgfy)× 加班系数(F_OvertimeCoefficient) | |
| 25 | +加班手工费(F_OvertimeSgfy)= 健康师原始手工费之和 × 加班系数(F_OvertimeCoefficient) | |
| 24 | 26 | 最终手工费(sgfy)= 原始手工费(F_OriginalSgfy)+ 加班手工费(F_OvertimeSgfy) |
| 25 | 27 | ``` |
| 26 | 28 | |
| 27 | -**示例**: | |
| 28 | -- 原始手工费 = 100元 | |
| 29 | +**示例**(健康师12元 + 科技部40元 = 整单原始52元): | |
| 30 | +- 原始手工费 = 52元(健康师12 + 科技部40) | |
| 31 | +- 健康师原始手工费之和 = 12元 | |
| 29 | 32 | - 加班系数 = 0.5 |
| 30 | -- 加班手工费 = 100 × 0.5 = 50元 | |
| 31 | -- 最终手工费 = 100 + 50 = 150元 | |
| 33 | +- 加班手工费 = 12 × 0.5 = 6元(仅健康师参与) | |
| 34 | +- 最终手工费 = 52 + 6 = 58元(= 健康师18 + 科技部40) | |
| 32 | 35 | |
| 33 | 36 | --- |
| 34 | 37 | |
| ... | ... | @@ -94,8 +97,8 @@ LaborCost = ikjbs_tem.laborCost, |
| 94 | 97 | ┌─────────────────────────────────────────────────────────────┐ |
| 95 | 98 | │ lq_xh_hyhk(耗卡主表) │ |
| 96 | 99 | │ F_OvertimeCoefficient(加班系数) │ |
| 97 | -│ F_OriginalSgfy(原始手工费) │ | |
| 98 | -│ F_OvertimeSgfy(加班手工费)= OriginalSgfy × Coefficient │ | |
| 100 | +│ F_OriginalSgfy(原始手工费 = 健康师+科技部) │ | |
| 101 | +│ F_OvertimeSgfy(加班手工费)= Σ健康师原始手工费 × 系数 │ | |
| 99 | 102 | │ sgfy(最终手工费)= OriginalSgfy + OvertimeSgfy │ |
| 100 | 103 | └─────────────────────────────────────────────────────────────┘ |
| 101 | 104 | │ |
| ... | ... | @@ -127,11 +130,12 @@ LaborCost = ikjbs_tem.laborCost, |
| 127 | 130 | |
| 128 | 131 | **流程**: |
| 129 | 132 | 1. 接收 `LqXhHyhkCrInput` 参数,包含 `overtimeCoefficient` |
| 130 | -2. 计算主表加班字段: | |
| 133 | +2. 计算主表加班字段(科技部不参与加班,主表加班手工费 = 健康师加班手工费之和): | |
| 131 | 134 | ```csharp |
| 132 | 135 | entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0; |
| 133 | 136 | entity.OriginalSgfy = input.sgfy; |
| 134 | - entity.OvertimeSgfy = entity.OriginalSgfy * entity.OvertimeCoefficient; | |
| 137 | + var jksOriginalLaborCostSum = input.lqXhPxmxList?.SelectMany(p => p.lqXhJksyjList ?? ...).Sum(j => j.laborCost ?? 0) ?? 0; | |
| 138 | + entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * entity.OvertimeCoefficient); | |
| 135 | 139 | entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy; |
| 136 | 140 | ``` |
| 137 | 141 | 3. 遍历品项明细,计算每个品项的加班字段: |
| ... | ... | @@ -236,7 +240,7 @@ LaborCost = ikjbs_tem.laborCost, |
| 236 | 240 | ``` |
| 237 | 241 | |
| 238 | 242 | 3. **计算逻辑**: |
| 239 | - - 主表:重新计算 `F_OvertimeSgfy` 和 `sgfy` | |
| 243 | + - 主表:重新计算 `F_OvertimeSgfy`(= 健康师原始手工费之和 × 系数)和 `sgfy`(科技部不参与加班) | |
| 240 | 244 | - 品项明细表:重新计算 `F_OvertimeProjectNumber` 和 `F_ProjectNumber` |
| 241 | 245 | - 健康师业绩表:重新计算 `F_OvertimeKdpxNumber`、`F_kdpxNumber`、`F_OvertimeLaborCost`、`F_LaborCost` |
| 242 | 246 | - 科技部老师业绩表:如果需要支持,重新计算相关字段 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TechTeacherDailyStatisticsOutput.cs
| ... | ... | @@ -47,6 +47,11 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport |
| 47 | 47 | /// 开单业绩 |
| 48 | 48 | /// </summary> |
| 49 | 49 | public decimal OrderAchievement { get; set; } |
| 50 | + | |
| 51 | + /// <summary> | |
| 52 | + /// 退卡业绩(用于计算净业绩:净业绩 = 耗卡业绩 - 退卡业绩) | |
| 53 | + /// </summary> | |
| 54 | + public decimal RefundAchievement { get; set; } | |
| 50 | 55 | } |
| 51 | 56 | } |
| 52 | 57 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqReimbursementApplicationMapper.cs
| 1 | -using NCC.Common.Helper; | |
| 1 | +using NCC.Common.Helper; | |
| 2 | +using NCC.Extend.Entitys; | |
| 2 | 3 | using NCC.Extend.Entitys.Dto.LqReimbursementApplication; |
| 3 | 4 | using Mapster; |
| 4 | 5 | using System.Collections.Generic; |
| ... | ... | @@ -9,6 +10,9 @@ namespace NCC.Extend.Entitys.Mapper.LqReimbursementApplication |
| 9 | 10 | { |
| 10 | 11 | public void Register(TypeAdapterConfig config) |
| 11 | 12 | { |
| 13 | + // 确保 camelCase 的 workflowConfigId 正确映射到 Entity 的 WorkflowConfigId | |
| 14 | + config.NewConfig<LqReimbursementApplicationCrInput, LqReimbursementApplicationEntity>() | |
| 15 | + .Map(d => d.WorkflowConfigId, s => s.workflowConfigId); | |
| 12 | 16 | } |
| 13 | 17 | } |
| 14 | 18 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
| ... | ... | @@ -1159,6 +1159,7 @@ namespace NCC.Extend |
| 1159 | 1159 | /// - ConsumeProjectCount: 消耗项目数 |
| 1160 | 1160 | /// - ConsumeAchievement: 消耗业绩 |
| 1161 | 1161 | /// - OrderAchievement: 开单业绩 |
| 1162 | + /// - RefundAchievement: 退卡业绩(净业绩 = 耗卡业绩 - 退卡业绩) | |
| 1162 | 1163 | /// </remarks> |
| 1163 | 1164 | /// <param name="input">查询参数</param> |
| 1164 | 1165 | /// <returns>科技部老师统计列表</returns> |
| ... | ... | @@ -1193,6 +1194,13 @@ namespace NCC.Extend |
| 1193 | 1194 | teacherFilterForOrder = $"AND ord.kjbls IN ('{teacherIdsStr}')"; |
| 1194 | 1195 | } |
| 1195 | 1196 | |
| 1197 | + var teacherFilterForRefund = ""; | |
| 1198 | + if (input.TeacherIds != null && input.TeacherIds.Any()) | |
| 1199 | + { | |
| 1200 | + var teacherIdsStr = string.Join("','", input.TeacherIds); | |
| 1201 | + teacherFilterForRefund = $"AND refund.kjbls IN ('{teacherIdsStr}')"; | |
| 1202 | + } | |
| 1203 | + | |
| 1196 | 1204 | // SQL查询:统计科技部老师的消耗业绩、见客数、项目数 |
| 1197 | 1205 | // 注意:GROUP BY 中移除了 user.F_RealName,避免同一老师ID因姓名不同产生重复记录 |
| 1198 | 1206 | var consumeSql = $@" |
| ... | ... | @@ -1242,6 +1250,29 @@ namespace NCC.Extend |
| 1242 | 1250 | |
| 1243 | 1251 | var orderResult = await _db.Ado.SqlQueryAsync<dynamic>(orderSql); |
| 1244 | 1252 | |
| 1253 | + // 查询退卡业绩(与耗卡使用相同过滤条件:门店、时间、人员、科技部) | |
| 1254 | + var refundSql = $@" | |
| 1255 | + SELECT | |
| 1256 | + techDept.F_Id as TechDepartmentId, | |
| 1257 | + techDept.F_FullName as TechDepartmentName, | |
| 1258 | + refund.kjbls as TeacherId, | |
| 1259 | + MAX(user.F_RealName) as TeacherName, | |
| 1260 | + SUM(refund.kjblsyj) as RefundAchievement | |
| 1261 | + FROM lq_hytk_kjbsyj refund | |
| 1262 | + INNER JOIN lq_hytk_hytk hytk ON refund.gltkbh = hytk.F_Id | |
| 1263 | + INNER JOIN lq_mdxx store ON hytk.md = store.F_Id | |
| 1264 | + LEFT JOIN base_organize techDept ON store.kjb = techDept.F_Id | |
| 1265 | + LEFT JOIN BASE_USER user ON refund.kjbls = user.F_Id | |
| 1266 | + WHERE refund.F_IsEffective = 1 | |
| 1267 | + AND hytk.F_IsEffective = 1 | |
| 1268 | + AND DATE(hytk.tksj) >= '{startDate:yyyy-MM-dd}' | |
| 1269 | + AND DATE(hytk.tksj) <= '{endDate:yyyy-MM-dd}' | |
| 1270 | + {techFilter} | |
| 1271 | + {teacherFilterForRefund} | |
| 1272 | + GROUP BY techDept.F_Id, techDept.F_FullName, refund.kjbls"; | |
| 1273 | + | |
| 1274 | + var refundResult = await _db.Ado.SqlQueryAsync<dynamic>(refundSql); | |
| 1275 | + | |
| 1245 | 1276 | // 合并数据:按员工ID汇总,避免同一员工在多个科技部重复出现 |
| 1246 | 1277 | // 使用 TeacherId 作为唯一键,汇总所有科技部的数据 |
| 1247 | 1278 | var teacherDict = new Dictionary<string, TechTeacherDailyStatisticsOutput>(); |
| ... | ... | @@ -1287,7 +1318,8 @@ namespace NCC.Extend |
| 1287 | 1318 | CustomerCount = 0, |
| 1288 | 1319 | ConsumeProjectCount = 0, |
| 1289 | 1320 | ConsumeAchievement = 0, |
| 1290 | - OrderAchievement = 0 | |
| 1321 | + OrderAchievement = 0, | |
| 1322 | + RefundAchievement = 0 | |
| 1291 | 1323 | }; |
| 1292 | 1324 | } |
| 1293 | 1325 | |
| ... | ... | @@ -1334,7 +1366,8 @@ namespace NCC.Extend |
| 1334 | 1366 | CustomerCount = 0, |
| 1335 | 1367 | ConsumeProjectCount = 0, |
| 1336 | 1368 | ConsumeAchievement = 0, |
| 1337 | - OrderAchievement = 0 | |
| 1369 | + OrderAchievement = 0, | |
| 1370 | + RefundAchievement = 0 | |
| 1338 | 1371 | }; |
| 1339 | 1372 | } |
| 1340 | 1373 | else |
| ... | ... | @@ -1349,7 +1382,38 @@ namespace NCC.Extend |
| 1349 | 1382 | teacherDict[teacherId].OrderAchievement += orderAchievement; |
| 1350 | 1383 | } |
| 1351 | 1384 | |
| 1352 | - // 第三步:确定每个员工的主要科技部 | |
| 1385 | + // 第三步:处理退卡业绩,按员工ID汇总 | |
| 1386 | + foreach (var item in refundResult ?? Enumerable.Empty<dynamic>()) | |
| 1387 | + { | |
| 1388 | + var teacherId = item.TeacherId?.ToString(); | |
| 1389 | + var techDeptId = item.TechDepartmentId?.ToString(); | |
| 1390 | + var techDeptName = item.TechDepartmentName?.ToString(); | |
| 1391 | + var refundAchievement = Convert.ToDecimal(item.RefundAchievement); | |
| 1392 | + | |
| 1393 | + if (string.IsNullOrEmpty(teacherId)) | |
| 1394 | + continue; | |
| 1395 | + | |
| 1396 | + if (!teacherDict.ContainsKey(teacherId)) | |
| 1397 | + { | |
| 1398 | + // 仅有退卡数据、无消耗和开单时,也创建记录 | |
| 1399 | + teacherDict[teacherId] = new TechTeacherDailyStatisticsOutput | |
| 1400 | + { | |
| 1401 | + TechDepartmentId = techDeptId, | |
| 1402 | + TechDepartmentName = techDeptName, | |
| 1403 | + TeacherId = teacherId, | |
| 1404 | + TeacherName = item.TeacherName?.ToString(), | |
| 1405 | + CustomerCount = 0, | |
| 1406 | + ConsumeProjectCount = 0, | |
| 1407 | + ConsumeAchievement = 0, | |
| 1408 | + OrderAchievement = 0, | |
| 1409 | + RefundAchievement = 0 | |
| 1410 | + }; | |
| 1411 | + } | |
| 1412 | + | |
| 1413 | + teacherDict[teacherId].RefundAchievement += refundAchievement; | |
| 1414 | + } | |
| 1415 | + | |
| 1416 | + // 第四步:确定每个员工的主要科技部 | |
| 1353 | 1417 | // 优先按消耗业绩最多的科技部,如果消耗业绩为0,则按开单业绩最多的科技部 |
| 1354 | 1418 | foreach (var teacherId in teacherDict.Keys.ToList()) |
| 1355 | 1419 | { |
| ... | ... | @@ -1424,7 +1488,7 @@ namespace NCC.Extend |
| 1424 | 1488 | } |
| 1425 | 1489 | } |
| 1426 | 1490 | |
| 1427 | - // 第四步:重新计算见客数(去重,因为同一个客户可能在多个科技部被统计) | |
| 1491 | + // 第五步:重新计算见客数(去重,因为同一个客户可能在多个科技部被统计) | |
| 1428 | 1492 | // 由于已经汇总了数据,这里需要重新查询去重后的见客数 |
| 1429 | 1493 | var teacherIds = teacherDict.Keys.ToList(); |
| 1430 | 1494 | if (teacherIds.Any()) |
| ... | ... | @@ -1453,7 +1517,7 @@ namespace NCC.Extend |
| 1453 | 1517 | } |
| 1454 | 1518 | } |
| 1455 | 1519 | |
| 1456 | - // 第五步:统一查询所有用户名称,确保所有用户都能获取到名称 | |
| 1520 | + // 第六步:统一查询所有用户名称,确保所有用户都能获取到名称 | |
| 1457 | 1521 | var teacherNamesSql = $@" |
| 1458 | 1522 | SELECT |
| 1459 | 1523 | F_Id as TeacherId, | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
| ... | ... | @@ -282,8 +282,8 @@ namespace NCC.Extend.LqReimbursementApplication |
| 282 | 282 | |
| 283 | 283 | // 先查询实体类中有CompletionTime字段且符合条件的申请ID |
| 284 | 284 | var entitiesWithCompletionTime = await _db.Queryable<LqReimbursementApplicationEntity>() |
| 285 | - .Where(x => x.CompletionTime.HasValue | |
| 286 | - && x.CompletionTime.Value >= startDate | |
| 285 | + .Where(x => x.CompletionTime.HasValue | |
| 286 | + && x.CompletionTime.Value >= startDate | |
| 287 | 287 | && x.CompletionTime.Value <= endDate) |
| 288 | 288 | .Select(x => x.Id) |
| 289 | 289 | .ToListAsync(); |
| ... | ... | @@ -528,11 +528,8 @@ namespace NCC.Extend.LqReimbursementApplication |
| 528 | 528 | } |
| 529 | 529 | } |
| 530 | 530 | |
| 531 | - // 2. 设置报销申请初始状态 | |
| 532 | - if (!string.IsNullOrEmpty(input.workflowConfigId)) | |
| 533 | - { | |
| 534 | - entity.WorkflowConfigId = input.workflowConfigId.Trim(); | |
| 535 | - } | |
| 531 | + // 2. 设置报销申请初始状态(含流程配置ID,确保入库) | |
| 532 | + entity.WorkflowConfigId = !string.IsNullOrEmpty(input.workflowConfigId) ? input.workflowConfigId.Trim() : null; | |
| 536 | 533 | entity.NodeCount = input.nodes.Count; |
| 537 | 534 | entity.CurrentNodeOrder = 0; |
| 538 | 535 | entity.ApprovalStatus = "待审批"; |
| ... | ... | @@ -546,8 +543,8 @@ namespace NCC.Extend.LqReimbursementApplication |
| 546 | 543 | entity.ApplicationUserName = userInfo.userName; |
| 547 | 544 | } |
| 548 | 545 | |
| 549 | - // 3. 保存报销申请表(不使用IgnoreColumns,确保新字段被保存) | |
| 550 | - var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); | |
| 546 | + // 3. 保存报销申请表(ignoreNullColumn: false 确保 WorkflowConfigId 等可选字段能正确入库) | |
| 547 | + var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: false).ExecuteCommandAsync(); | |
| 551 | 548 | if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); |
| 552 | 549 | |
| 553 | 550 | // 4. 创建节点配置 |
| ... | ... | @@ -825,8 +822,10 @@ namespace NCC.Extend.LqReimbursementApplication |
| 825 | 822 | .ExecuteCommandAsync(); |
| 826 | 823 | } |
| 827 | 824 | |
| 828 | - // 更新报销申请表(确保 purchaseRecordsId 字段被正确更新) | |
| 825 | + // 更新报销申请表(确保 purchaseRecordsId、WorkflowConfigId 等字段被正确更新) | |
| 829 | 826 | var entity = input.Adapt<LqReimbursementApplicationEntity>(); |
| 827 | + entity.Id = id; // 确保使用路由参数中的主键 | |
| 828 | + entity.WorkflowConfigId = !string.IsNullOrEmpty(input.workflowConfigId) ? input.workflowConfigId.Trim() : oldEntity?.WorkflowConfigId; | |
| 830 | 829 | var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); |
| 831 | 830 | if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001); |
| 832 | 831 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
| ... | ... | @@ -1104,9 +1104,12 @@ namespace NCC.Extend.LqXhHyhk |
| 1104 | 1104 | entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0; |
| 1105 | 1105 | entity.OriginalSgfy = input.sgfy; |
| 1106 | 1106 | entity.AppointmentId = input.appointmentId; |
| 1107 | - //加班手工费 = 原始手工费 * 加班系数 | |
| 1108 | - entity.OvertimeSgfy = entity.OriginalSgfy * entity.OvertimeCoefficient; | |
| 1109 | - //最终手工费 = 原始手工费 + 加班手工费 | |
| 1107 | + // 加班手工费 = 健康师原始手工费之和 × 加班系数(科技部不参与加班) | |
| 1108 | + var jksOriginalLaborCostSum = input.lqXhPxmxList? | |
| 1109 | + .SelectMany(p => p.lqXhJksyjList ?? Enumerable.Empty<LqXhJksyjCrInput>()) | |
| 1110 | + .Sum(j => j.laborCost ?? 0) ?? 0; | |
| 1111 | + entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * entity.OvertimeCoefficient); | |
| 1112 | + // 最终手工费 = 原始手工费 + 加班手工费 | |
| 1110 | 1113 | entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy; |
| 1111 | 1114 | try |
| 1112 | 1115 | { |
| ... | ... | @@ -1489,9 +1492,13 @@ namespace NCC.Extend.LqXhHyhk |
| 1489 | 1492 | throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在或已作废"); |
| 1490 | 1493 | } |
| 1491 | 1494 | entity.UpdateTime = DateTime.Now; |
| 1492 | - entity.OvertimeCoefficient = input.overtimeCoefficient; | |
| 1495 | + entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0; | |
| 1493 | 1496 | entity.OriginalSgfy = input.sgfy; |
| 1494 | - entity.OvertimeSgfy = (decimal)(entity.OvertimeCoefficient * input.sgfy); | |
| 1497 | + // 加班手工费 = 健康师原始手工费之和 × 加班系数(科技部不参与加班) | |
| 1498 | + var jksOriginalLaborCostSum = input.lqXhPxmxList? | |
| 1499 | + .SelectMany(p => p.lqXhJksyjList ?? Enumerable.Empty<LqXhJksyjCrInput>()) | |
| 1500 | + .Sum(j => j.laborCost ?? 0) ?? 0; | |
| 1501 | + entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * (entity.OvertimeCoefficient ?? 0)); | |
| 1495 | 1502 | entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy; |
| 1496 | 1503 | entity.AppointmentId = input.appointmentId; |
| 1497 | 1504 | //更新会员耗卡记录 |
| ... | ... | @@ -1823,7 +1830,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1823 | 1830 | /// |
| 1824 | 1831 | /// 计算逻辑: |
| 1825 | 1832 | /// 1. 主表(lq_xh_hyhk): |
| 1826 | - /// - 加班手工费 = 原始手工费 × 新加班系数 | |
| 1833 | + /// - 加班手工费 = 健康师原始手工费之和 × 新加班系数(科技部不参与加班) | |
| 1827 | 1834 | /// - 最终手工费 = 原始手工费 + 加班手工费 |
| 1828 | 1835 | /// |
| 1829 | 1836 | /// 2. 品项明细表(lq_xh_pxmx): |
| ... | ... | @@ -1881,11 +1888,26 @@ namespace NCC.Extend.LqXhHyhk |
| 1881 | 1888 | } |
| 1882 | 1889 | } |
| 1883 | 1890 | |
| 1884 | - // 2. 更新主表加班系数和相关字段 | |
| 1891 | + // 2. 查询健康师业绩,计算主表加班手工费(科技部不参与加班) | |
| 1892 | + var jksyjList = await _db.Queryable<LqXhJksyjEntity>() | |
| 1893 | + .Where(x => x.Glkdbh == id && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1894 | + .ToListAsync(); | |
| 1895 | + var jksOriginalLaborCostSum = jksyjList.Sum(j => | |
| 1896 | + { | |
| 1897 | + var original = j.OriginalLaborCost ?? 0; | |
| 1898 | + if (original == 0 && (j.LaborCost ?? 0) > 0) | |
| 1899 | + { | |
| 1900 | + original = (j.LaborCost ?? 0) - (j.OvertimeLaborCost ?? 0); | |
| 1901 | + if (original < 0) original = 0; | |
| 1902 | + } | |
| 1903 | + return original; | |
| 1904 | + }); | |
| 1905 | + | |
| 1906 | + // 3. 更新主表加班系数和相关字段 | |
| 1885 | 1907 | var newCoefficient = input.overtimeCoefficient ?? 0; |
| 1886 | 1908 | var originalSgfy = entity.OriginalSgfy ?? 0; |
| 1887 | 1909 | entity.OvertimeCoefficient = newCoefficient; |
| 1888 | - entity.OvertimeSgfy = (decimal)(originalSgfy * newCoefficient); | |
| 1910 | + entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * newCoefficient); | |
| 1889 | 1911 | entity.Sgfy = originalSgfy + (entity.OvertimeSgfy ?? 0); |
| 1890 | 1912 | entity.UpdateTime = DateTime.Now; |
| 1891 | 1913 | |
| ... | ... | @@ -1893,7 +1915,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1893 | 1915 | .UpdateColumns(x => new { x.OvertimeCoefficient, x.OvertimeSgfy, x.Sgfy, x.UpdateTime }) |
| 1894 | 1916 | .ExecuteCommandAsync(); |
| 1895 | 1917 | |
| 1896 | - // 3. 查询所有品项明细,更新加班字段 | |
| 1918 | + // 4. 查询所有品项明细,更新加班字段 | |
| 1897 | 1919 | var pxmxList = await _db.Queryable<LqXhPxmxEntity>() |
| 1898 | 1920 | .Where(x => x.ConsumeInfoId == id && x.IsEffective == StatusEnum.有效.GetHashCode()) |
| 1899 | 1921 | .ToListAsync(); |
| ... | ... | @@ -1922,11 +1944,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1922 | 1944 | .ExecuteCommandAsync(); |
| 1923 | 1945 | } |
| 1924 | 1946 | |
| 1925 | - // 4. 查询所有健康师业绩,更新加班字段 | |
| 1926 | - var jksyjList = await _db.Queryable<LqXhJksyjEntity>() | |
| 1927 | - .Where(x => x.Glkdbh == id && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1928 | - .ToListAsync(); | |
| 1929 | - | |
| 1947 | + // 5. 更新健康师业绩加班字段(jksyjList 已在步骤2查询) | |
| 1930 | 1948 | foreach (var jksyj in jksyjList) |
| 1931 | 1949 | { |
| 1932 | 1950 | // 如果原始耗卡品项次数为空,使用当前值作为原始值 |
| ... | ... | @@ -1969,7 +1987,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1969 | 1987 | .ExecuteCommandAsync(); |
| 1970 | 1988 | } |
| 1971 | 1989 | |
| 1972 | - // 5. 科技部老师业绩表:当前代码中不参与加班计算,保持原值不变 | |
| 1990 | + // 6. 科技部老师业绩表:当前代码中不参与加班计算,保持原值不变 | |
| 1973 | 1991 | // 如果需要支持,可以取消注释以下代码 |
| 1974 | 1992 | /* |
| 1975 | 1993 | var kjbsyjList = await _db.Queryable<LqXhKjbsyjEntity>() | ... | ... |
sql/修复消耗单主表加班手工费历史数据.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 修复消耗单主表(lq_xh_hyhk)F_OvertimeSgfy 和 sgfy 历史数据 | |
| 3 | +-- ============================================ | |
| 4 | +-- 背景:主表加班手工费已改为「健康师原始手工费之和 × 加班系数」,科技部不参与加班。 | |
| 5 | +-- 本脚本修复历史数据,使 F_OvertimeSgfy 和 sgfy 符合新逻辑。 | |
| 6 | +-- | |
| 7 | +-- 修复逻辑: | |
| 8 | +-- F_OvertimeSgfy = 健康师原始手工费之和 × F_OvertimeCoefficient | |
| 9 | +-- sgfy = F_OriginalSgfy + F_OvertimeSgfy | |
| 10 | +-- | |
| 11 | +-- 健康师原始手工费:优先用 F_OriginalLaborCost,为空时用 F_LaborCost - F_OvertimeLaborCost | |
| 12 | +-- F_OriginalSgfy 为空时:用 健康师原始之和 + 科技部原始之和 补全 | |
| 13 | +-- | |
| 14 | +-- 执行前请先备份!建议在测试环境验证后再在生产环境执行。 | |
| 15 | + | |
| 16 | +-- ============================================ | |
| 17 | +-- 1. 预览:查看需要修复的记录(只读,可先执行验证) | |
| 18 | +-- ============================================ | |
| 19 | +/* | |
| 20 | +SELECT | |
| 21 | + h.F_Id, | |
| 22 | + h.F_OriginalSgfy AS 当前原始手工费, | |
| 23 | + h.F_OvertimeSgfy AS 当前加班手工费, | |
| 24 | + h.sgfy AS 当前最终手工费, | |
| 25 | + h.F_OvertimeCoefficient AS 加班系数, | |
| 26 | + COALESCE(j.jks_sum, 0) AS 健康师原始手工费之和, | |
| 27 | + COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0) AS 新加班手工费, | |
| 28 | + COALESCE(h.F_OriginalSgfy, COALESCE(j.jks_sum, 0) + COALESCE(k.kjb_sum, 0)) + (COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0)) AS 新最终手工费 | |
| 29 | +FROM lq_xh_hyhk h | |
| 30 | +LEFT JOIN ( | |
| 31 | + SELECT glkdbh, | |
| 32 | + SUM(COALESCE(F_OriginalLaborCost, GREATEST(0, COALESCE(F_LaborCost, 0) - COALESCE(F_OvertimeLaborCost, 0)))) AS jks_sum | |
| 33 | + FROM lq_xh_jksyj | |
| 34 | + WHERE F_IsEffective = 1 | |
| 35 | + GROUP BY glkdbh | |
| 36 | +) j ON h.F_Id = j.glkdbh | |
| 37 | +LEFT JOIN ( | |
| 38 | + SELECT glkdbh, | |
| 39 | + SUM(COALESCE(F_OriginalLaborCost, F_LaborCost)) AS kjb_sum | |
| 40 | + FROM lq_xh_kjbsyj | |
| 41 | + WHERE F_IsEffective = 1 | |
| 42 | + GROUP BY glkdbh | |
| 43 | +) k ON h.F_Id = k.glkdbh | |
| 44 | +WHERE h.F_IsEffective = 1 | |
| 45 | + AND ( | |
| 46 | + COALESCE(h.F_OvertimeSgfy, 0) != COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0) | |
| 47 | + OR COALESCE(h.sgfy, 0) != COALESCE(h.F_OriginalSgfy, COALESCE(j.jks_sum, 0) + COALESCE(k.kjb_sum, 0)) + (COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0)) | |
| 48 | + ) | |
| 49 | +LIMIT 50; | |
| 50 | +*/ | |
| 51 | + | |
| 52 | +-- ============================================ | |
| 53 | +-- 2. 执行修复:更新 F_OvertimeSgfy 和 sgfy | |
| 54 | +-- ============================================ | |
| 55 | +UPDATE lq_xh_hyhk h | |
| 56 | +INNER JOIN ( | |
| 57 | + SELECT glkdbh, | |
| 58 | + SUM(COALESCE(F_OriginalLaborCost, GREATEST(0, COALESCE(F_LaborCost, 0) - COALESCE(F_OvertimeLaborCost, 0)))) AS jks_sum | |
| 59 | + FROM lq_xh_jksyj | |
| 60 | + WHERE F_IsEffective = 1 | |
| 61 | + GROUP BY glkdbh | |
| 62 | +) j ON h.F_Id = j.glkdbh | |
| 63 | +LEFT JOIN ( | |
| 64 | + SELECT glkdbh, | |
| 65 | + SUM(COALESCE(F_OriginalLaborCost, F_LaborCost)) AS kjb_sum | |
| 66 | + FROM lq_xh_kjbsyj | |
| 67 | + WHERE F_IsEffective = 1 | |
| 68 | + GROUP BY glkdbh | |
| 69 | +) k ON h.F_Id = k.glkdbh | |
| 70 | +SET | |
| 71 | + h.F_OvertimeSgfy = COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0), | |
| 72 | + h.sgfy = COALESCE(h.F_OriginalSgfy, COALESCE(j.jks_sum, 0) + COALESCE(k.kjb_sum, 0)) + (COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0)) | |
| 73 | +WHERE h.F_IsEffective = 1; | |
| 74 | + | |
| 75 | +-- 说明:上述 UPDATE 使用 INNER JOIN j,因此仅更新「存在健康师业绩」的耗卡记录。 | |
| 76 | +-- 若耗卡仅有科技部、无健康师,则 j 子查询无结果,该记录不会被更新。 | |
| 77 | +-- 对于「仅科技部」的耗卡,正确逻辑应为:F_OvertimeSgfy=0,sgfy=F_OriginalSgfy,需单独处理。 | |
| 78 | + | |
| 79 | +-- ============================================ | |
| 80 | +-- 3. 补充修复:仅科技部、无健康师的耗卡(F_OvertimeSgfy 应为 0,sgfy = 原始手工费) | |
| 81 | +-- ============================================ | |
| 82 | +UPDATE lq_xh_hyhk h | |
| 83 | +LEFT JOIN ( | |
| 84 | + SELECT glkdbh | |
| 85 | + FROM lq_xh_jksyj | |
| 86 | + WHERE F_IsEffective = 1 | |
| 87 | + GROUP BY glkdbh | |
| 88 | +) j ON h.F_Id = j.glkdbh | |
| 89 | +LEFT JOIN ( | |
| 90 | + SELECT glkdbh, | |
| 91 | + SUM(COALESCE(F_OriginalLaborCost, F_LaborCost)) AS kjb_sum | |
| 92 | + FROM lq_xh_kjbsyj | |
| 93 | + WHERE F_IsEffective = 1 | |
| 94 | + GROUP BY glkdbh | |
| 95 | +) k ON h.F_Id = k.glkdbh | |
| 96 | +SET | |
| 97 | + h.F_OvertimeSgfy = 0, | |
| 98 | + h.sgfy = COALESCE(h.F_OriginalSgfy, COALESCE(k.kjb_sum, 0), h.sgfy) | |
| 99 | +WHERE h.F_IsEffective = 1 | |
| 100 | + AND j.glkdbh IS NULL | |
| 101 | + AND (COALESCE(h.F_OvertimeSgfy, 0) != 0); | |
| 102 | + | |
| 103 | +-- ============================================ | |
| 104 | +-- 4. 验证修复结果(可选) | |
| 105 | +-- ============================================ | |
| 106 | +-- 检查是否仍有不一致记录(应返回 0) | |
| 107 | +/* | |
| 108 | +SELECT COUNT(*) AS 仍不一致记录数 | |
| 109 | +FROM lq_xh_hyhk h | |
| 110 | +LEFT JOIN ( | |
| 111 | + SELECT glkdbh, | |
| 112 | + SUM(COALESCE(F_OriginalLaborCost, GREATEST(0, COALESCE(F_LaborCost, 0) - COALESCE(F_OvertimeLaborCost, 0)))) AS jks_sum | |
| 113 | + FROM lq_xh_jksyj | |
| 114 | + WHERE F_IsEffective = 1 | |
| 115 | + GROUP BY glkdbh | |
| 116 | +) j ON h.F_Id = j.glkdbh | |
| 117 | +LEFT JOIN ( | |
| 118 | + SELECT glkdbh, | |
| 119 | + SUM(COALESCE(F_OriginalLaborCost, F_LaborCost)) AS kjb_sum | |
| 120 | + FROM lq_xh_kjbsyj | |
| 121 | + WHERE F_IsEffective = 1 | |
| 122 | + GROUP BY glkdbh | |
| 123 | +) k ON h.F_Id = k.glkdbh | |
| 124 | +WHERE h.F_IsEffective = 1 | |
| 125 | + AND ( | |
| 126 | + COALESCE(h.F_OvertimeSgfy, 0) != COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0) | |
| 127 | + OR COALESCE(h.sgfy, 0) != COALESCE(h.F_OriginalSgfy, COALESCE(j.jks_sum, 0) + COALESCE(k.kjb_sum, 0)) + (COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0)) | |
| 128 | + ); | |
| 129 | +*/ | ... | ... |
test-get-tech-teacher-daily-statistics.sh
0 → 100755
| 1 | +#!/bin/bash | |
| 2 | +# GetTechTeacherDailyStatistics 接口测试脚本 | |
| 3 | +# 使用方式: ./test-get-tech-teacher-daily-statistics.sh [BASE_URL],默认 http://localhost:2011 | |
| 4 | +# 前置:API 需已启动且已重新编译(含 RefundAchievement 的 DTO 变更) | |
| 5 | + | |
| 6 | +BASE_URL="${1:-http://localhost:2011}" | |
| 7 | + | |
| 8 | +echo "=== 1. 获取 Token ===" | |
| 9 | +TOKEN=$(curl -s -X POST "$BASE_URL/api/oauth/Login" \ | |
| 10 | + -H "Content-Type: application/x-www-form-urlencoded" \ | |
| 11 | + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('token','') if d.get('code')==200 else '')" 2>/dev/null) | |
| 12 | +if [ -z "$TOKEN" ]; then | |
| 13 | + echo "获取 Token 失败,请检查 API 是否启动" | |
| 14 | + exit 1 | |
| 15 | +fi | |
| 16 | +echo "Token 获取成功" | |
| 17 | + | |
| 18 | +echo "" | |
| 19 | +echo "=== 2. 调用 GetTechTeacherDailyStatistics 接口 ===" | |
| 20 | +echo "时间范围: 2026-03-01 ~ 2026-03-16" | |
| 21 | +RESP=$(curl -s -X POST "$BASE_URL/api/Extend/LqDailyReport/get-tech-teacher-daily-statistics" \ | |
| 22 | + -H "Authorization: $TOKEN" \ | |
| 23 | + -H "Content-Type: application/json" \ | |
| 24 | + -d '{"startTime":"2026-03-01T00:00:00","endTime":"2026-03-16T23:59:59"}') | |
| 25 | +echo "$RESP" | python3 -m json.tool 2>/dev/null || echo "$RESP" | |
| 26 | + | |
| 27 | +CODE=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('code',-1))" 2>/dev/null) | |
| 28 | +if [ "$CODE" != "200" ]; then | |
| 29 | + echo "" | |
| 30 | + echo "接口返回非 200,若为 500 且提示 set_RefundAchievement,请重启 API 服务后重试" | |
| 31 | + exit 1 | |
| 32 | +fi | |
| 33 | + | |
| 34 | +echo "" | |
| 35 | +echo "=== 3. 数据库验证 SQL(MCP MySQL 或手动执行)===" | |
| 36 | +echo "耗卡业绩汇总(lq_xh_kjbsyj + lq_xh_hyhk):" | |
| 37 | +echo "SELECT consume.kjbls as TeacherId, SUM(consume.kjblsyj) as ConsumeAchievement" | |
| 38 | +echo "FROM lq_xh_kjbsyj consume" | |
| 39 | +echo "INNER JOIN lq_xh_hyhk hyhk ON consume.glkdbh = hyhk.F_Id" | |
| 40 | +echo "WHERE consume.F_IsEffective = 1 AND hyhk.F_IsEffective = 1" | |
| 41 | +echo " AND DATE(hyhk.hksj) >= '2026-03-01' AND DATE(hyhk.hksj) <= '2026-03-16'" | |
| 42 | +echo "GROUP BY consume.kjbls;" | |
| 43 | +echo "" | |
| 44 | +echo "退卡业绩汇总(lq_hytk_kjbsyj + lq_hytk_hytk):" | |
| 45 | +echo "SELECT refund.kjbls as TeacherId, SUM(refund.kjblsyj) as RefundAchievement" | |
| 46 | +echo "FROM lq_hytk_kjbsyj refund" | |
| 47 | +echo "INNER JOIN lq_hytk_hytk hytk ON refund.gltkbh = hytk.F_Id" | |
| 48 | +echo "WHERE refund.F_IsEffective = 1 AND hytk.F_IsEffective = 1" | |
| 49 | +echo " AND DATE(hytk.tksj) >= '2026-03-01' AND DATE(hytk.tksj) <= '2026-03-16'" | |
| 50 | +echo "GROUP BY refund.kjbls;" | ... | ... |
test-xh-overtime-apis.sh
0 → 100644
| 1 | +#!/bin/bash | |
| 2 | +# 消耗单(会员耗卡)加班手工费接口测试脚本 | |
| 3 | +# 使用方式: ./test-xh-overtime-apis.sh [BASE_URL],默认 http://localhost:2011 | |
| 4 | +# 前置:API 需已启动 | |
| 5 | + | |
| 6 | +BASE_URL="${1:-http://localhost:2011}" | |
| 7 | + | |
| 8 | +echo "=== 1. 获取 Token ===" | |
| 9 | +TOKEN=$(curl -s -X POST "$BASE_URL/api/oauth/Login" \ | |
| 10 | + -H "Content-Type: application/x-www-form-urlencoded" \ | |
| 11 | + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('token','') if d.get('code')==200 else '')" 2>/dev/null) | |
| 12 | +if [ -z "$TOKEN" ]; then | |
| 13 | + echo "获取 Token 失败,请检查 API 是否启动" | |
| 14 | + exit 1 | |
| 15 | +fi | |
| 16 | +echo "Token 获取成功" | |
| 17 | + | |
| 18 | +echo "" | |
| 19 | +echo "=== 2. Create 消耗单(健康师12+科技部40=52,系数0.5)===" | |
| 20 | +echo "预期:F_OvertimeSgfy=6, sgfy=58" | |
| 21 | +CREATE_BODY='{"hy":"742276000326354181","md":"1649328471923847174","mdbh":"001","mdmc":"绿纤优品道店","hyzh":"17882541236","hymc":"咕噜","xfje":100,"sgfy":52,"hksj":"2026-03-16T10:00:00","overtimeCoefficient":0.5,"lqXhPxmxList":[{"px":"1","pxmc":"活动陪同","pxjg":100,"projectNumber":1,"sourceType":"耗卡","lqXhJksyjList":[{"jks":"13032810387","jksxm":"范时依","jkszh":"13032810387","jksyj":50,"laborCost":12,"kdpxNumber":1}],"lqXhKjbsyjList":[{"kjbls":"13110190690","kjblsxm":"刘雨佳","kjblszh":"13110190690","kjblsyj":50,"laborCost":40,"hdpxNumber":1}]}]}' | |
| 22 | +curl -s -X POST "$BASE_URL/api/Extend/LqXhHyhk" -H "Authorization: $TOKEN" -H "Content-Type: application/json" -d "$CREATE_BODY" | python3 -m json.tool 2>/dev/null || echo "请求失败" | |
| 23 | + | |
| 24 | +echo "" | |
| 25 | +echo "=== 3. 创建后需查库验证主表 F_OvertimeSgfy、sgfy ===" | |
| 26 | +echo "SQL: SELECT F_Id, sgfy, F_OriginalSgfy, F_OvertimeSgfy FROM lq_xh_hyhk WHERE hy='742276000326354181' ORDER BY F_CreateTime DESC LIMIT 1" | |
| 27 | + | |
| 28 | +echo "" | |
| 29 | +echo "=== 4. Update 消耗单(需替换 {id} 为实际耗卡ID,body 必须包含 id 字段)===" | |
| 30 | +echo "curl -X PUT \"$BASE_URL/api/Extend/LqXhHyhk/{id}\" -H \"Authorization: \$TOKEN\" -H \"Content-Type: application/json\" -d '{\"id\":\"{id}\",\"hy\":\"742276000326354181\",\"md\":\"1649328471923847174\",\"sgfy\":52,\"overtimeCoefficient\":0.5,\"lqXhPxmxList\":[...]}'" | |
| 31 | + | |
| 32 | +echo "" | |
| 33 | +echo "=== 5. UpdateOvertimeCoefficient(需替换 {id} 为实际耗卡ID)===" | |
| 34 | +echo "curl -X PUT \"$BASE_URL/api/Extend/LqXhHyhk/{id}/overtime-coefficient\" -H \"Authorization: \$TOKEN\" -H \"Content-Type: application/json\" -d '{\"overtimeCoefficient\":1.0}'" | ... | ... |