Commit 8b44d4bd9ad36ec5c24615ba9a16d10c1cbc4f1c

Authored by “wangming”
1 parent 6af31905

feat: 添加健康师额外工资和合作成本导入的清理功能

- 健康师额外工资导入:添加清理导入月份数据参数,默认true
- 合作成本导入:添加清理导入月份数据参数,默认true,支持多成本类型和多笔记录
- 合作成本导入返回结果:添加成本类型字段到successRecords
- 移除合作成本导入的重复检查,支持同一门店同一月份多笔记录
antis-ncc-admin/.env.development
@@ -2,8 +2,8 @@ @@ -2,8 +2,8 @@
2 2
3 VUE_CLI_BABEL_TRANSPILE_MODULES = true 3 VUE_CLI_BABEL_TRANSPILE_MODULES = true
4 # VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com' 4 # VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com'
5 -VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'  
6 -# VUE_APP_BASE_API = 'http://localhost:2011' 5 +# VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
  6 +VUE_APP_BASE_API = 'http://localhost:2011'
7 # VUE_APP_BASE_API = 'http://localhost:2011' 7 # VUE_APP_BASE_API = 'http://localhost:2011'
8 VUE_APP_IMG_API = '' 8 VUE_APP_IMG_API = ''
9 VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket' 9 VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket'
antis-ncc-admin/src/api/extend/healthCoachSalary.js
@@ -27,9 +27,10 @@ export function getExtraCalculationList(params) { @@ -27,9 +27,10 @@ export function getExtraCalculationList(params) {
27 } 27 }
28 28
29 // 导入额外计算数据 29 // 导入额外计算数据
30 -export function importExtraCalculationFromExcel(file) { 30 +export function importExtraCalculationFromExcel(file, clearBeforeImport = false) {
31 const formData = new FormData() 31 const formData = new FormData()
32 formData.append('file', file) 32 formData.append('file', file)
  33 + formData.append('clearBeforeImport', clearBeforeImport)
33 return request({ 34 return request({
34 url: '/api/Extend/lqsalaryextracalculation/ImportFromExcel', 35 url: '/api/Extend/lqsalaryextracalculation/ImportFromExcel',
35 method: 'POST', 36 method: 'POST',
antis-ncc-admin/src/views/lqCooperationCost/import-dialog.vue
@@ -21,12 +21,25 @@ @@ -21,12 +21,25 @@
21 <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> 21 <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
22 <div class="el-upload__tip" slot="tip">只能上传xlsx/xls文件,且不超过10MB</div> 22 <div class="el-upload__tip" slot="tip">只能上传xlsx/xls文件,且不超过10MB</div>
23 </el-upload> 23 </el-upload>
  24 + <div style="margin-top: 15px;">
  25 + <el-checkbox v-model="clearBeforeImport">
  26 + 清理导入月份数据
  27 + </el-checkbox>
  28 + </div>
24 <div v-if="uploadResult" class="upload-result" :class="uploadResult.type"> 29 <div v-if="uploadResult" class="upload-result" :class="uploadResult.type">
25 <div class="result-title">{{ uploadResult.title }}</div> 30 <div class="result-title">{{ uploadResult.title }}</div>
26 <div class="result-description">{{ uploadResult.description }}</div> 31 <div class="result-description">{{ uploadResult.description }}</div>
27 <div v-if="uploadResult.failMessages && uploadResult.failMessages.length > 0" class="fail-messages"> 32 <div v-if="uploadResult.failMessages && uploadResult.failMessages.length > 0" class="fail-messages">
28 <div v-for="(msg, index) in uploadResult.failMessages" :key="index" class="fail-message">{{ msg }}</div> 33 <div v-for="(msg, index) in uploadResult.failMessages" :key="index" class="fail-message">{{ msg }}</div>
29 </div> 34 </div>
  35 + <div v-if="uploadResult.successRecords && uploadResult.successRecords.length > 0" class="success-records">
  36 + <div class="success-title">成功导入的记录(包含成本类型):</div>
  37 + <div v-for="(record, index) in uploadResult.successRecords" :key="index" class="success-record">
  38 + 门店ID: {{ record.storeId }}, 年份: {{ record.year }}, 月份: {{ record.month }},
  39 + 金额: {{ record.totalAmount }}, 成本类型: {{ record.costType || '无' }},
  40 + 备注: {{ record.remarks || '无' }}
  41 + </div>
  42 + </div>
30 </div> 43 </div>
31 </div> 44 </div>
32 <div slot="footer" class="dialog-footer"> 45 <div slot="footer" class="dialog-footer">
@@ -45,6 +58,7 @@ export default { @@ -45,6 +58,7 @@ export default {
45 visible: false, 58 visible: false,
46 fileList: [], 59 fileList: [],
47 uploading: false, 60 uploading: false,
  61 + clearBeforeImport: true, // 是否需要清理导入月份数据,默认true
48 uploadResult: null 62 uploadResult: null
49 } 63 }
50 }, 64 },
@@ -53,6 +67,7 @@ export default { @@ -53,6 +67,7 @@ export default {
53 this.visible = true 67 this.visible = true
54 this.fileList = [] 68 this.fileList = []
55 this.uploading = false 69 this.uploading = false
  70 + this.clearBeforeImport = true
56 this.uploadResult = null 71 this.uploadResult = null
57 }, 72 },
58 handleFileChange(file, fileList) { 73 handleFileChange(file, fileList) {
@@ -73,6 +88,7 @@ export default { @@ -73,6 +88,7 @@ export default {
73 this.uploadResult = null 88 this.uploadResult = null
74 const formData = new FormData() 89 const formData = new FormData()
75 formData.append('file', file.raw) 90 formData.append('file', file.raw)
  91 + formData.append('clearBeforeImport', this.clearBeforeImport)
76 request({ 92 request({
77 url: '/api/Extend/LqCooperationCost/Actions/Import', 93 url: '/api/Extend/LqCooperationCost/Actions/Import',
78 method: 'POST', 94 method: 'POST',
@@ -88,7 +104,8 @@ export default { @@ -88,7 +104,8 @@ export default {
88 type: 'success', 104 type: 'success',
89 title: '导入成功', 105 title: '导入成功',
90 description: `成功导入 ${data.successCount || 0} 条,失败 ${data.failCount || 0} 条`, 106 description: `成功导入 ${data.successCount || 0} 条,失败 ${data.failCount || 0} 条`,
91 - failMessages: data.failMessages || [] 107 + failMessages: data.failMessages || [],
  108 + successRecords: data.successRecords || []
92 } 109 }
93 this.$message.success('导入成功') 110 this.$message.success('导入成功')
94 setTimeout(() => { 111 setTimeout(() => {
@@ -163,6 +180,24 @@ export default { @@ -163,6 +180,24 @@ export default {
163 margin-bottom: 5px; 180 margin-bottom: 5px;
164 } 181 }
165 } 182 }
  183 +
  184 + .success-records {
  185 + margin-top: 10px;
  186 + padding-top: 10px;
  187 + border-top: 1px solid #ddd;
  188 +
  189 + .success-title {
  190 + font-weight: 600;
  191 + margin-bottom: 8px;
  192 + color: #67C23A;
  193 + }
  194 +
  195 + .success-record {
  196 + color: #606266;
  197 + margin-bottom: 5px;
  198 + font-size: 12px;
  199 + }
  200 + }
166 } 201 }
167 </style> 202 </style>
168 203
antis-ncc-admin/src/views/wageManagement/extra-data-dialog.vue
@@ -57,6 +57,12 @@ @@ -57,6 +57,12 @@
57 > 57 >
58 导入 58 导入
59 </el-button> 59 </el-button>
  60 + <el-checkbox
  61 + v-model="clearBeforeImport"
  62 + style="margin-left: 20px;"
  63 + >
  64 + 清理导入月份数据
  65 + </el-checkbox>
60 </div> 66 </div>
61 67
62 <!-- 数据表格 --> 68 <!-- 数据表格 -->
@@ -190,6 +196,7 @@ export default { @@ -190,6 +196,7 @@ export default {
190 loading: false, 196 loading: false,
191 exportTemplateLoading: false, 197 exportTemplateLoading: false,
192 importLoading: false, 198 importLoading: false,
  199 + clearBeforeImport: true, // 是否需要清理导入月份数据,默认true
193 list: [], 200 list: [],
194 total: 0, 201 total: 0,
195 queryParams: { 202 queryParams: {
@@ -451,7 +458,7 @@ export default { @@ -451,7 +458,7 @@ export default {
451 458
452 this.importLoading = true 459 this.importLoading = true
453 try { 460 try {
454 - const response = await importExtraCalculationFromExcel(file) 461 + const response = await importExtraCalculationFromExcel(file, this.clearBeforeImport)
455 462
456 if (response.code === 200) { 463 if (response.code === 200) {
457 this.$message.success('导入成功') 464 this.$message.success('导入成功')
excel/健康师额外数据模板_2025年11月_20251219120434.xlsx 0 → 100644
No preview for this file type
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMajorProjectTeacherSalary/MajorProjectTeacherSalaryOutput.cs
@@ -18,12 +18,12 @@ namespace NCC.Extend.Entitys.Dto.LqMajorProjectTeacherSalary @@ -18,12 +18,12 @@ namespace NCC.Extend.Entitys.Dto.LqMajorProjectTeacherSalary
18 public string StatisticsMonth { get; set; } 18 public string StatisticsMonth { get; set; }
19 19
20 /// <summary> 20 /// <summary>
21 - /// 门店ID 21 + /// 部门ID(使用StoreId字段存储部门ID)
22 /// </summary> 22 /// </summary>
23 public string StoreId { get; set; } 23 public string StoreId { get; set; }
24 24
25 /// <summary> 25 /// <summary>
26 - /// 门店名称 26 + /// 部门名称(使用StoreName字段存储部门名称)
27 /// </summary> 27 /// </summary>
28 public string StoreName { get; set; } 28 public string StoreName { get; set; }
29 29
netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs
@@ -334,53 +334,18 @@ namespace NCC.Extend @@ -334,53 +334,18 @@ namespace NCC.Extend
334 decimal performanceRatio = salary.StoreTotalPerformance / salary.StoreLifeline; 334 decimal performanceRatio = salary.StoreTotalPerformance / salary.StoreLifeline;
335 335
336 // 根据岗位类型确定提成比例 336 // 根据岗位类型确定提成比例
337 - if (isDirector) 337 + // 店助和店助主任使用相同的阶梯提成规则
  338 + // 先计算总提成金额(阶梯计算)
  339 + decimal totalCommission = CalculateAssistantCommission(salary.StoreTotalPerformance, salary.StoreLifeline);
  340 +
  341 + // 计算平均提成比例(用于显示)
  342 + if (salary.StoreTotalPerformance > 0)
338 { 343 {
339 - // 店助主任:使用固定比例(按规则文档,业绩≥100%时使用阶梯提成,但为保持比例固定,使用平均比例)  
340 - // 业绩 < 70%:0%  
341 - // 70% ≤ 业绩 < 100%:0.4%  
342 - // 业绩 ≥ 100%:使用阶梯提成计算总提成,然后计算平均比例  
343 - if (performanceRatio < 0.7m)  
344 - {  
345 - commissionRate = 0;  
346 - }  
347 - else if (performanceRatio < 1.0m)  
348 - {  
349 - commissionRate = 0.004m; // 0.4%  
350 - }  
351 - else  
352 - {  
353 - // 业绩 ≥ 100%:使用阶梯提成计算总提成,然后计算平均比例  
354 - // ≤生命线部分:0.6%,>生命线部分:1%  
355 - decimal storeTotalCommission = CalculateDirectorCommission(salary.StoreTotalPerformance, salary.StoreLifeline);  
356 - if (salary.StoreTotalPerformance > 0)  
357 - {  
358 - commissionRate = storeTotalCommission / salary.StoreTotalPerformance;  
359 - }  
360 - else  
361 - {  
362 - commissionRate = 0;  
363 - }  
364 - } 344 + commissionRate = totalCommission / salary.StoreTotalPerformance;
365 } 345 }
366 else 346 else
367 { 347 {
368 - // 店助:使用固定比例  
369 - // 业绩 < 70%:0%  
370 - // 70% ≤ 业绩 < 100%:0.4%  
371 - // 业绩 ≥ 100%:0.6%  
372 - if (performanceRatio < 0.7m)  
373 - {  
374 - commissionRate = 0;  
375 - }  
376 - else if (performanceRatio < 1.0m)  
377 - {  
378 - commissionRate = 0.004m; // 0.4%  
379 - }  
380 - else  
381 - {  
382 - commissionRate = 0.006m; // 0.6%  
383 - } 348 + commissionRate = 0;
384 } 349 }
385 } 350 }
386 351
@@ -467,12 +432,15 @@ namespace NCC.Extend @@ -467,12 +432,15 @@ namespace NCC.Extend
467 } 432 }
468 433
469 // 2.11 按在店天数比例计算店助的提成和奖励 434 // 2.11 按在店天数比例计算店助的提成和奖励
470 - // 逻辑:提成金额 = 门店业绩 × 提成比例 / 当月天数 × 在店天数 435 + // 逻辑:提成金额 = 门店总提成(阶梯计算) / 当月天数 × 在店天数
471 // 阶段奖励 = 门店总奖励 / 当月天数 × 在店天数 436 // 阶段奖励 = 门店总奖励 / 当月天数 × 在店天数
472 if (daysInMonth > 0 && workingDays > 0) 437 if (daysInMonth > 0 && workingDays > 0)
473 { 438 {
474 - // 按比例计算提成:门店业绩 × 提成比例 / 当月天数 × 在店天数  
475 - salary.CommissionAmount = salary.StoreTotalPerformance * commissionRate / daysInMonth * workingDays; 439 + // 先计算门店总提成(阶梯计算)- 店助和店助主任使用相同的规则
  440 + decimal storeTotalCommission = CalculateAssistantCommission(salary.StoreTotalPerformance, salary.StoreLifeline);
  441 +
  442 + // 按比例计算提成:门店总提成 / 当月天数 × 在店天数
  443 + salary.CommissionAmount = storeTotalCommission / daysInMonth * workingDays;
476 444
477 // 按比例计算奖励 445 // 按比例计算奖励
478 salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays; 446 salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays;
@@ -650,21 +618,18 @@ namespace NCC.Extend @@ -650,21 +618,18 @@ namespace NCC.Extend
650 else 618 else
651 { 619 {
652 // 门店业绩 ≥ 门店生命线 × 100% → 阶梯提成 620 // 门店业绩 ≥ 门店生命线 × 100% → 阶梯提成
653 - // 70%以下部分:0%  
654 - // 70%-100%部分:0.4%  
655 - // ≤生命线部分:0.6%,>生命线部分:1%  
656 - decimal stage70 = storeLifeline * 0.7m; 621 + // ≤生命线部分(整个生命线):0.6%
  622 + // >生命线部分:1%
657 decimal stage100 = storeLifeline; 623 decimal stage100 = storeLifeline;
658 - decimal performance70To100 = stage100 - stage70;  
659 - decimal performanceAbove100 = storePerformance - stage100; 624 + decimal performanceUpToLifeline = stage100; // ≤生命线部分(整个生命线)
  625 + decimal performanceAbove100 = storePerformance - stage100; // >生命线部分
660 626
661 - // 70%-100%部分:0.4%  
662 - decimal commission70To100 = performance70To100 * 0.004m;  
663 - // ≤生命线部分(0-70%):0%,70%-100%部分:0.4%,已计算 627 + // ≤生命线部分:0.6%
  628 + decimal commissionUpToLifeline = performanceUpToLifeline * 0.006m;
664 // >生命线部分:1% 629 // >生命线部分:1%
665 decimal commissionAbove100 = performanceAbove100 * 0.01m; // 1% 630 decimal commissionAbove100 = performanceAbove100 * 0.01m; // 1%
666 631
667 - return commission70To100 + commissionAbove100; 632 + return commissionUpToLifeline + commissionAbove100;
668 } 633 }
669 } 634 }
670 } 635 }
netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs
@@ -409,11 +409,12 @@ namespace NCC.Extend.LqCooperationCost @@ -409,11 +409,12 @@ namespace NCC.Extend.LqCooperationCost
409 /// Content-Type: multipart/form-data 409 /// Content-Type: multipart/form-data
410 /// </remarks> 410 /// </remarks>
411 /// <param name="file">Excel文件</param> 411 /// <param name="file">Excel文件</param>
  412 + /// <param name="clearBeforeImport">是否需要清理导入月份数据(默认:true,清理)</param>
412 /// <returns>导入结果</returns> 413 /// <returns>导入结果</returns>
413 /// <response code="200">导入成功</response> 414 /// <response code="200">导入成功</response>
414 /// <response code="400">文件格式错误或数据验证失败</response> 415 /// <response code="400">文件格式错误或数据验证失败</response>
415 [HttpPost("Actions/Import")] 416 [HttpPost("Actions/Import")]
416 - public async Task<dynamic> Import(IFormFile file) 417 + public async Task<dynamic> Import(IFormFile file, bool clearBeforeImport = true)
417 { 418 {
418 try 419 try
419 { 420 {
@@ -433,6 +434,7 @@ namespace NCC.Extend.LqCooperationCost @@ -433,6 +434,7 @@ namespace NCC.Extend.LqCooperationCost
433 var successCount = 0; 434 var successCount = 0;
434 var failCount = 0; 435 var failCount = 0;
435 var failMessages = new List<string>(); 436 var failMessages = new List<string>();
  437 + var importData = new List<(string StoreId, int Year, string Month, decimal TotalAmount, string CostType, string Remarks)>();
436 438
437 // 保存临时文件 439 // 保存临时文件
438 var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName)); 440 var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName));
@@ -517,44 +519,14 @@ namespace NCC.Extend.LqCooperationCost @@ -517,44 +519,14 @@ namespace NCC.Extend.LqCooperationCost
517 continue; 519 continue;
518 } 520 }
519 521
520 - // 检查是否已存在相同门店、年份、月份的记录  
521 - var exists = await _db.Queryable<LqCooperationCostEntity>()  
522 - .Where(x => x.StoreId == storeId && x.Year == year && x.Month == monthText && x.IsEffective == StatusEnum.有效.GetHashCode())  
523 - .AnyAsync(); 522 + // 注意:一个门店在同一个月份可以有多笔成本记录(包括相同成本类型、相同金额)
  523 + // 因为业务上可能需要记录多笔成本,比如多次合作项目、多次设备维护等
  524 + // 统计时会按门店汇总所有记录的金额,所以不需要做重复检查
  525 + // 如果用户需要避免重复导入,可以使用"清理导入月份数据"功能
524 526
525 - if (exists)  
526 - {  
527 - failMessages.Add($"第{i + 1}行:该门店{year}年{monthText}月的记录已存在");  
528 - failCount++;  
529 - continue;  
530 - }  
531 -  
532 - // 创建记录  
533 - var entity = new LqCooperationCostEntity  
534 - {  
535 - Id = YitIdHelper.NextId().ToString(),  
536 - StoreId = storeId,  
537 - StoreName = storeName,  
538 - Year = year,  
539 - Month = monthText,  
540 - TotalAmount = totalAmount,  
541 - CostType = costType,  
542 - Remarks = remarks,  
543 - IsEffective = StatusEnum.有效.GetHashCode(),  
544 - CreateUser = _userManager.UserId,  
545 - CreateTime = DateTime.Now  
546 - };  
547 -  
548 - var isOk = await _db.Insertable(entity).ExecuteCommandAsync();  
549 - if (isOk > 0)  
550 - {  
551 - successCount++;  
552 - }  
553 - else  
554 - {  
555 - failMessages.Add($"第{i + 1}行:保存失败");  
556 - failCount++;  
557 - } 527 + // 添加到导入数据列表
  528 + importData.Add((storeId, year, monthText, totalAmount, costType, remarks));
  529 + successCount++;
558 } 530 }
559 catch (Exception ex) 531 catch (Exception ex)
560 { 532 {
@@ -572,13 +544,71 @@ namespace NCC.Extend.LqCooperationCost @@ -572,13 +544,71 @@ namespace NCC.Extend.LqCooperationCost
572 } 544 }
573 } 545 }
574 546
  547 + // 如果需要清理导入月份数据,先删除导入数据中所有涉及的年份+月份组合的数据
  548 + if (clearBeforeImport && importData.Any())
  549 + {
  550 + // 获取导入数据中所有唯一的年份+月份组合
  551 + var yearMonthPairs = importData
  552 + .Select(x => new { x.Year, x.Month })
  553 + .Distinct()
  554 + .ToList();
  555 +
  556 + foreach (var pair in yearMonthPairs)
  557 + {
  558 + await _db.Deleteable<LqCooperationCostEntity>()
  559 + .Where(x => x.Year == pair.Year && x.Month == pair.Month && x.IsEffective == StatusEnum.有效.GetHashCode())
  560 + .ExecuteCommandAsync();
  561 + }
  562 + }
  563 +
  564 + // 批量插入导入数据
  565 + if (importData.Any())
  566 + {
  567 + // 批量获取门店信息
  568 + var storeIds = importData.Select(x => x.StoreId).Distinct().ToList();
  569 + var stores = await _db.Queryable<LqMdxxEntity>()
  570 + .Where(x => storeIds.Contains(x.Id))
  571 + .Select(x => new { x.Id, x.Dm })
  572 + .ToListAsync();
  573 + var storeDict = stores.ToDictionary(x => x.Id, x => x.Dm);
  574 +
  575 + var entities = importData.Select(x => new LqCooperationCostEntity
  576 + {
  577 + Id = YitIdHelper.NextId().ToString(),
  578 + StoreId = x.StoreId,
  579 + StoreName = storeDict.ContainsKey(x.StoreId) ? storeDict[x.StoreId] : "",
  580 + Year = x.Year,
  581 + Month = x.Month,
  582 + TotalAmount = x.TotalAmount,
  583 + CostType = x.CostType,
  584 + Remarks = x.Remarks,
  585 + IsEffective = StatusEnum.有效.GetHashCode(),
  586 + CreateUser = _userManager.UserId,
  587 + CreateTime = DateTime.Now
  588 + }).ToList();
  589 +
  590 + await _db.Insertable(entities).ExecuteCommandAsync();
  591 + }
  592 +
  593 + // 构建返回结果,包含成功导入的记录的成本类型信息
  594 + var successRecords = importData.Select(x => new
  595 + {
  596 + storeId = x.StoreId,
  597 + year = x.Year,
  598 + month = x.Month,
  599 + totalAmount = x.TotalAmount,
  600 + costType = x.CostType,
  601 + remarks = x.Remarks
  602 + }).ToList();
  603 +
575 return new 604 return new
576 { 605 {
577 success = true, 606 success = true,
578 message = $"导入完成:成功{successCount}条,失败{failCount}条", 607 message = $"导入完成:成功{successCount}条,失败{failCount}条",
579 successCount = successCount, 608 successCount = successCount,
580 failCount = failCount, 609 failCount = failCount,
581 - failMessages = failMessages 610 + failMessages = failMessages,
  611 + successRecords = successRecords
582 }; 612 };
583 } 613 }
584 catch (Exception ex) 614 catch (Exception ex)
netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs
@@ -7,7 +7,10 @@ using NCC.DynamicApiController; @@ -7,7 +7,10 @@ using NCC.DynamicApiController;
7 using NCC.Extend.Entitys.Dto.LqMajorProjectDirectorSalary; 7 using NCC.Extend.Entitys.Dto.LqMajorProjectDirectorSalary;
8 using NCC.Extend.Entitys.lq_attendance_summary; 8 using NCC.Extend.Entitys.lq_attendance_summary;
9 using NCC.Extend.Entitys.lq_hytk_hytk; 9 using NCC.Extend.Entitys.lq_hytk_hytk;
  10 +using NCC.Extend.Entitys.lq_hytk_mx;
10 using NCC.Extend.Entitys.lq_kd_kdjlb; 11 using NCC.Extend.Entitys.lq_kd_kdjlb;
  12 +using NCC.Extend.Entitys.lq_kd_pxmx;
  13 +using NCC.Extend.Entitys.lq_kd_jksyj;
11 using NCC.Extend.Entitys.lq_md_target; 14 using NCC.Extend.Entitys.lq_md_target;
12 using NCC.Extend.Entitys.lq_mdxx; 15 using NCC.Extend.Entitys.lq_mdxx;
13 using NCC.Extend.Entitys.lq_major_project_director_salary_statistics; 16 using NCC.Extend.Entitys.lq_major_project_director_salary_statistics;
@@ -214,19 +217,33 @@ namespace NCC.Extend @@ -214,19 +217,33 @@ namespace NCC.Extend
214 { 217 {
215 foreach (var storeId in allManagedStoreIds) 218 foreach (var storeId in allManagedStoreIds)
216 { 219 {
217 - // 该门店的开单金额  
218 - var storeBillingAmount = await _db.Queryable<LqKdKdjlbEntity>()  
219 - .Where(x => x.IsEffective == 1  
220 - && x.Djmd == storeId  
221 - && x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1))  
222 - .SumAsync(x => (decimal?)x.Sfyj) ?? 0m;  
223 -  
224 - // 该门店的退卡金额(优先使用ActualRefundAmount,如果没有则使用Tkje)  
225 - var storeRefundAmount = await _db.Queryable<LqHytkHytkEntity>()  
226 - .Where(x => x.IsEffective == 1  
227 - && x.Md == storeId  
228 - && x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1))  
229 - .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m; 220 + // 该门店的开单金额(只统计医美类型的开单,从lq_kd_jksyj表统计医美品项的健康师业绩)
  221 + var storeBillingList = await _db.Queryable<LqKdJksyjEntity, LqKdKdjlbEntity>(
  222 + (jksyj, kdjlb) => new JoinQueryInfos(
  223 + JoinType.Inner, jksyj.Glkdbh == kdjlb.Id))
  224 + .Where((jksyj, kdjlb) =>
  225 + kdjlb.IsEffective == 1
  226 + && jksyj.IsEffective == 1
  227 + && kdjlb.Djmd == storeId
  228 + && kdjlb.Kdrq >= startDate && kdjlb.Kdrq <= endDate.AddDays(1)
  229 + && jksyj.ItemCategory == "医美"
  230 + && !string.IsNullOrEmpty(jksyj.Jksyj))
  231 + .Select((jksyj, kdjlb) => jksyj.Jksyj)
  232 + .ToListAsync();
  233 + var storeBillingAmount = storeBillingList
  234 + .Sum(x => decimal.TryParse(x, out var val) ? val : 0m);
  235 +
  236 + // 该门店的退卡金额(只统计医美类型的退卡,从lq_hytk_mx表统计医美品项的退卡金额)
  237 + var storeRefundAmount = await _db.Queryable<LqHytkMxEntity, LqHytkHytkEntity>(
  238 + (mx, hytk) => new JoinQueryInfos(
  239 + JoinType.Inner, mx.RefundInfoId == hytk.Id))
  240 + .Where((mx, hytk) =>
  241 + hytk.IsEffective == 1
  242 + && mx.IsEffective == 1
  243 + && hytk.Md == storeId
  244 + && hytk.Tksj >= startDate && hytk.Tksj <= endDate.AddDays(1)
  245 + && mx.ItemCategory == "医美")
  246 + .SumAsync((mx, hytk) => (decimal?)(mx.Tkje ?? 0)) ?? 0m;
230 247
231 // 该门店的净总业绩 248 // 该门店的净总业绩
232 var storeTotalPerformance = storeBillingAmount - storeRefundAmount; 249 var storeTotalPerformance = storeBillingAmount - storeRefundAmount;
netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs
@@ -7,7 +7,10 @@ using NCC.DynamicApiController; @@ -7,7 +7,10 @@ using NCC.DynamicApiController;
7 using NCC.Extend.Entitys.Dto.LqMajorProjectTeacherSalary; 7 using NCC.Extend.Entitys.Dto.LqMajorProjectTeacherSalary;
8 using NCC.Extend.Entitys.lq_attendance_summary; 8 using NCC.Extend.Entitys.lq_attendance_summary;
9 using NCC.Extend.Entitys.lq_hytk_hytk; 9 using NCC.Extend.Entitys.lq_hytk_hytk;
  10 +using NCC.Extend.Entitys.lq_hytk_mx;
10 using NCC.Extend.Entitys.lq_kd_kdjlb; 11 using NCC.Extend.Entitys.lq_kd_kdjlb;
  12 +using NCC.Extend.Entitys.lq_kd_pxmx;
  13 +using NCC.Extend.Entitys.lq_kd_jksyj;
11 using NCC.Extend.Entitys.lq_md_major_project_teacher_assignment; 14 using NCC.Extend.Entitys.lq_md_major_project_teacher_assignment;
12 using NCC.Extend.Entitys.lq_md_xdbhsj; 15 using NCC.Extend.Entitys.lq_md_xdbhsj;
13 using NCC.Extend.Entitys.lq_mdxx; 16 using NCC.Extend.Entitys.lq_mdxx;
@@ -182,25 +185,40 @@ namespace NCC.Extend @@ -182,25 +185,40 @@ namespace NCC.Extend
182 .ToDictionary(g => g.Key, g => g.First()); 185 .ToDictionary(g => g.Key, g => g.First());
183 186
184 // 1.4 门店总业绩计算 (开单实付 - 退卡金额) 187 // 1.4 门店总业绩计算 (开单实付 - 退卡金额)
185 - // 开单实付(从lq_kd_kdjlb表统计sfyj字段)  
186 - var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>()  
187 - .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1)  
188 - .Select(x => new { x.Djmd, x.Sfyj }) 188 + // 开单实付(只统计医美类型的开单,从lq_kd_jksyj表统计医美品项的健康师业绩)
  189 + var storeBillingList = await _db.Queryable<LqKdJksyjEntity, LqKdKdjlbEntity>(
  190 + (jksyj, kdjlb) => new JoinQueryInfos(
  191 + JoinType.Inner, jksyj.Glkdbh == kdjlb.Id))
  192 + .Where((jksyj, kdjlb) =>
  193 + kdjlb.Kdrq >= startDate &&
  194 + kdjlb.Kdrq <= endDate.AddDays(1) &&
  195 + kdjlb.IsEffective == 1 &&
  196 + jksyj.IsEffective == 1 &&
  197 + jksyj.ItemCategory == "医美" &&
  198 + !string.IsNullOrEmpty(jksyj.Jksyj))
  199 + .Select((jksyj, kdjlb) => new { kdjlb.Djmd, Jksyj = jksyj.Jksyj })
189 .ToListAsync(); 200 .ToListAsync();
190 var storeBillingDict = storeBillingList 201 var storeBillingDict = storeBillingList
191 .Where(x => !string.IsNullOrEmpty(x.Djmd)) 202 .Where(x => !string.IsNullOrEmpty(x.Djmd))
192 .GroupBy(x => x.Djmd) 203 .GroupBy(x => x.Djmd)
193 - .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj));  
194 -  
195 - // 退卡金额(从lq_hytk_hytk表统计,使用F_ActualRefundAmount,如果没有则使用tkje)  
196 - var storeRefundList = await _db.Queryable<LqHytkHytkEntity>()  
197 - .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)  
198 - .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje }) 204 + .ToDictionary(g => g.Key, g => g.Sum(x => decimal.TryParse(x.Jksyj, out var val) ? val : 0m));
  205 +
  206 + // 退卡金额(只统计医美类型的退卡,从lq_hytk_mx表统计医美品项的退卡金额)
  207 + var storeRefundList = await _db.Queryable<LqHytkMxEntity, LqHytkHytkEntity>(
  208 + (mx, hytk) => new JoinQueryInfos(
  209 + JoinType.Inner, mx.RefundInfoId == hytk.Id))
  210 + .Where((mx, hytk) =>
  211 + hytk.Tksj >= startDate &&
  212 + hytk.Tksj <= endDate.AddDays(1) &&
  213 + hytk.IsEffective == 1 &&
  214 + mx.IsEffective == 1 &&
  215 + mx.ItemCategory == "医美")
  216 + .Select((mx, hytk) => new { hytk.Md, mx.Tkje })
199 .ToListAsync(); 217 .ToListAsync();
200 var storeRefundDict = storeRefundList 218 var storeRefundDict = storeRefundList
201 .Where(x => !string.IsNullOrEmpty(x.Md)) 219 .Where(x => !string.IsNullOrEmpty(x.Md))
202 .GroupBy(x => x.Md) 220 .GroupBy(x => x.Md)
203 - .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0)); 221 + .ToDictionary(g => g.Key, g => g.Sum(x => x.Tkje ?? 0));
204 222
205 // 1.5 考勤数据 (lq_attendance_summary) 223 // 1.5 考勤数据 (lq_attendance_summary)
206 var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>() 224 var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
@@ -212,10 +230,18 @@ namespace NCC.Extend @@ -212,10 +230,18 @@ namespace NCC.Extend
212 var teacherIds = assignmentList.Select(x => x.TeacherId).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList(); 230 var teacherIds = assignmentList.Select(x => x.TeacherId).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
213 var userList = await _db.Queryable<UserEntity>() 231 var userList = await _db.Queryable<UserEntity>()
214 .Where(x => teacherIds.Contains(x.Id)) 232 .Where(x => teacherIds.Contains(x.Id))
215 - .Select(x => new { x.Id, x.RealName, x.Account, x.IsOnJob }) 233 + .Select(x => new { x.Id, x.RealName, x.Account, x.IsOnJob, x.OrganizeId })
216 .ToListAsync(); 234 .ToListAsync();
217 var userDict = userList.ToDictionary(x => x.Id, x => x); 235 var userDict = userList.ToDictionary(x => x.Id, x => x);
218 236
  237 + // 1.7 获取部门信息 (BASE_ORGANIZE)
  238 + var organizeIds = userList.Where(x => !string.IsNullOrEmpty(x.OrganizeId)).Select(x => x.OrganizeId).Distinct().ToList();
  239 + var organizeList = await _db.Queryable<OrganizeEntity>()
  240 + .Where(x => organizeIds.Contains(x.Id))
  241 + .Select(x => new { x.Id, x.FullName })
  242 + .ToListAsync();
  243 + var organizeDict = organizeList.ToDictionary(x => x.Id, x => x);
  244 +
219 // 2. 按大项目部老师聚合数据 245 // 2. 按大项目部老师聚合数据
220 var teacherStats = new Dictionary<string, LqMajorProjectTeacherSalaryStatisticsEntity>(); 246 var teacherStats = new Dictionary<string, LqMajorProjectTeacherSalaryStatisticsEntity>();
221 247
@@ -268,35 +294,29 @@ namespace NCC.Extend @@ -268,35 +294,29 @@ namespace NCC.Extend
268 salary.EmployeeName = user.RealName ?? ""; 294 salary.EmployeeName = user.RealName ?? "";
269 salary.EmployeeAccount = user.Account ?? ""; 295 salary.EmployeeAccount = user.Account ?? "";
270 salary.IsTerminated = user.IsOnJob == 0 ? 1 : 0; 296 salary.IsTerminated = user.IsOnJob == 0 ? 1 : 0;
271 - }  
272 297
273 - // 2.3 填充门店信息  
274 - if (storeDict.ContainsKey(storeId))  
275 - {  
276 - var store = storeDict[storeId];  
277 - salary.StoreId = storeId;  
278 - salary.StoreName = store.Dm ?? "";  
279 - salary.StoreType = store.StoreType;  
280 - salary.StoreCategory = store.StoreCategory; 298 + // 2.3 填充部门信息(使用StoreId和StoreName字段存储部门信息)
  299 + if (!string.IsNullOrEmpty(user.OrganizeId) && organizeDict.ContainsKey(user.OrganizeId))
  300 + {
  301 + var organize = organizeDict[user.OrganizeId];
  302 + salary.StoreId = user.OrganizeId; // 存储部门ID
  303 + salary.StoreName = organize.FullName ?? ""; // 存储部门名称
  304 + }
  305 + else
  306 + {
  307 + salary.StoreId = user.OrganizeId ?? "";
  308 + salary.StoreName = "";
  309 + }
281 } 310 }
282 else 311 else
283 { 312 {
284 - salary.StoreId = storeId; 313 + salary.StoreId = "";
285 salary.StoreName = ""; 314 salary.StoreName = "";
286 } 315 }
287 316
288 - // 2.4 新店保护信息  
289 - if (!string.IsNullOrEmpty(salary.StoreId) && newStoreProtectionDict.ContainsKey(salary.StoreId))  
290 - {  
291 - var protection = newStoreProtectionDict[salary.StoreId];  
292 - salary.IsNewStore = "是";  
293 - salary.NewStoreProtectionStage = protection.Stage;  
294 - }  
295 - else  
296 - {  
297 - salary.IsNewStore = "否";  
298 - salary.NewStoreProtectionStage = 0;  
299 - } 317 + // 2.4 新店保护信息(不再使用,因为现在存储的是部门信息)
  318 + salary.IsNewStore = "否";
  319 + salary.NewStoreProtectionStage = 0;
300 320
301 // 2.5 统计门店总业绩(开单业绩 - 退卡业绩) 321 // 2.5 统计门店总业绩(开单业绩 - 退卡业绩)
302 var billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; 322 var billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0;
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
@@ -106,9 +106,10 @@ namespace NCC.Extend @@ -106,9 +106,10 @@ namespace NCC.Extend
106 /// 从Excel导入健康师工资额外计算数据 106 /// 从Excel导入健康师工资额外计算数据
107 /// </summary> 107 /// </summary>
108 /// <param name="file">Excel文件</param> 108 /// <param name="file">Excel文件</param>
  109 + /// <param name="clearBeforeImport">是否需要清理导入月份数据(默认:true,清理)</param>
109 /// <returns>导入结果</returns> 110 /// <returns>导入结果</returns>
110 [HttpPost("ImportFromExcel")] 111 [HttpPost("ImportFromExcel")]
111 - public async Task<dynamic> ImportFromExcel(IFormFile file) 112 + public async Task<dynamic> ImportFromExcel(IFormFile file, bool clearBeforeImport = true)
112 { 113 {
113 try 114 try
114 { 115 {
@@ -277,6 +278,23 @@ namespace NCC.Extend @@ -277,6 +278,23 @@ namespace NCC.Extend
277 throw NCCException.Oh("Excel文件中没有有效的数据行"); 278 throw NCCException.Oh("Excel文件中没有有效的数据行");
278 } 279 }
279 280
  281 + // 如果需要清理导入月份数据,先删除导入数据中所有涉及的年份+月份组合的数据
  282 + if (clearBeforeImport)
  283 + {
  284 + // 获取导入数据中所有唯一的年份+月份组合
  285 + var yearMonthPairs = importData
  286 + .Select(x => new { x.Year, x.Month })
  287 + .Distinct()
  288 + .ToList();
  289 +
  290 + foreach (var pair in yearMonthPairs)
  291 + {
  292 + await _db.Deleteable<LqSalaryExtraCalculationEntity>()
  293 + .Where(x => x.Year == pair.Year && x.Month == pair.Month)
  294 + .ExecuteCommandAsync();
  295 + }
  296 + }
  297 +
280 // 处理导入数据 298 // 处理导入数据
281 return await ProcessImportData(importData); 299 return await ProcessImportData(importData);
282 } 300 }
netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsHqService.cs
@@ -8,6 +8,8 @@ using NCC.Extend.Entitys.lq_kd_kdjlb; @@ -8,6 +8,8 @@ using NCC.Extend.Entitys.lq_kd_kdjlb;
8 using NCC.Extend.Entitys.lq_hytk_hytk; 8 using NCC.Extend.Entitys.lq_hytk_hytk;
9 using NCC.Extend.Entitys.lq_kd_pxmx; 9 using NCC.Extend.Entitys.lq_kd_pxmx;
10 using NCC.Extend.Entitys.lq_contract_rent_detail; 10 using NCC.Extend.Entitys.lq_contract_rent_detail;
  11 +using NCC.Extend.Entitys.lq_contract;
  12 +using NCC.Extend.Entitys;
11 using SqlSugar; 13 using SqlSugar;
12 using System; 14 using System;
13 using System.Linq; 15 using System.Linq;
@@ -151,23 +153,64 @@ namespace NCC.Extend @@ -151,23 +153,64 @@ namespace NCC.Extend
151 /// </summary> 153 /// </summary>
152 private async Task CalculateCost(LqShareStatisticsHqEntity entity, DateTime startDate, DateTime endDate, string statisticsMonth) 154 private async Task CalculateCost(LqShareStatisticsHqEntity entity, DateTime startDate, DateTime endDate, string statisticsMonth)
153 { 155 {
154 - // 1. 成本-报销 (TODO: 需要确认总部报销的判定方式)  
155 - entity.CostReimbursement = 0; 156 + // 1. 成本-报销:筛选一级分类为"总部费用"的申请,审批状态为"已通过"
  157 + var reimbursementAmount = await _db.Queryable<LqReimbursementApplicationEntity, LqPurchaseRecordsEntity, LqReimbursementCategoryEntity>(
  158 + (app, purchase, category) => app.PurchaseRecordsId == purchase.Id && purchase.ReimbursementCategoryId == category.Id)
  159 + .Where((app, purchase, category) =>
  160 + category.Level1Name == "总部费用"
  161 + && app.ApprovalStatus == "已通过"
  162 + && app.ApplicationTime >= startDate
  163 + && app.ApplicationTime <= endDate.AddDays(1))
  164 + .SumAsync((app, purchase, category) => purchase.Amount);
  165 + entity.CostReimbursement = reimbursementAmount;
156 166
157 // 2. 成本-人工 (保留) 167 // 2. 成本-人工 (保留)
158 entity.CostLabor = 0; 168 entity.CostLabor = 0;
159 169
160 // 3. 成本-教育部房租 170 // 3. 成本-教育部房租
161 - // TODO: 需要确认如何识别教育部合同  
162 - entity.CostEducationRent = 0; 171 + // 从合同月租明细表获取:门店是总部,分类是教育,统计月份匹配
  172 + // 使用月份字符串匹配,避免时区问题
  173 + var educationRent = await _db.Ado.SqlQuerySingleAsync<decimal>(
  174 + $@"SELECT COALESCE(SUM(d.F_DueAmount), 0)
  175 + FROM lq_contract_rent_detail d
  176 + INNER JOIN lq_contract c ON d.F_ContractId = c.F_Id
  177 + WHERE c.F_StoreId = '1649328471923847168'
  178 + AND c.F_Category = '教育'
  179 + AND DATE_FORMAT(d.F_PaymentMonth, '%Y%m') = @StatisticsMonth
  180 + AND c.F_IsEffective = 1
  181 + AND d.F_IsEffective = 1",
  182 + new { StatisticsMonth = statisticsMonth });
  183 + entity.CostEducationRent = educationRent;
163 184
164 // 4. 成本-仓库房租 185 // 4. 成本-仓库房租
165 - // TODO: 需要确认如何识别仓库合同  
166 - entity.CostWarehouseRent = 0; 186 + // 从合同月租明细表获取:门店是总部,分类是仓库,统计月份匹配
  187 + // 使用月份字符串匹配,避免时区问题
  188 + var warehouseRent = await _db.Ado.SqlQuerySingleAsync<decimal>(
  189 + $@"SELECT COALESCE(SUM(d.F_DueAmount), 0)
  190 + FROM lq_contract_rent_detail d
  191 + INNER JOIN lq_contract c ON d.F_ContractId = c.F_Id
  192 + WHERE c.F_StoreId = '1649328471923847168'
  193 + AND c.F_Category = '仓库'
  194 + AND DATE_FORMAT(d.F_PaymentMonth, '%Y%m') = @StatisticsMonth
  195 + AND c.F_IsEffective = 1
  196 + AND d.F_IsEffective = 1",
  197 + new { StatisticsMonth = statisticsMonth });
  198 + entity.CostWarehouseRent = warehouseRent;
167 199
168 // 5. 成本-总部房租 200 // 5. 成本-总部房租
169 - // TODO: 需要确认如何识别总部合同  
170 - entity.CostHQRent = 0; 201 + // 从合同月租明细表获取:门店是总部,分类是总部,统计月份匹配
  202 + // 使用月份字符串匹配,避免时区问题
  203 + var hqRent = await _db.Ado.SqlQuerySingleAsync<decimal>(
  204 + $@"SELECT COALESCE(SUM(d.F_DueAmount), 0)
  205 + FROM lq_contract_rent_detail d
  206 + INNER JOIN lq_contract c ON d.F_ContractId = c.F_Id
  207 + WHERE c.F_StoreId = '1649328471923847168'
  208 + AND c.F_Category = '总部'
  209 + AND DATE_FORMAT(d.F_PaymentMonth, '%Y%m') = @StatisticsMonth
  210 + AND c.F_IsEffective = 1
  211 + AND d.F_IsEffective = 1",
  212 + new { StatisticsMonth = statisticsMonth });
  213 + entity.CostHQRent = hqRent;
171 } 214 }
172 215
173 /// <summary> 216 /// <summary>
netcore/src/Modularity/Extend/NCC.Extend/Utils/WeChatBotService.cs
@@ -25,7 +25,7 @@ namespace NCC.Extend.Utils @@ -25,7 +25,7 @@ namespace NCC.Extend.Utils
25 _httpClient = httpClient; 25 _httpClient = httpClient;
26 26
27 // 从配置文件中读取企业微信机器人配置 27 // 从配置文件中读取企业微信机器人配置
28 - _botApiUrl = App.Configuration["WeChatBot:BotApiUrl"] ?? "http://wx.lvqianmeiye.com/api/Bot/send-text"; 28 + _botApiUrl = App.Configuration["WeChatBot:BotApiUrl"] ?? "https://wx.lvqianmeiye.com/api/Bot/send-text";
29 29
30 // 从配置文件中读取Webhook地址(正式或测试地址,通过配置文件切换) 30 // 从配置文件中读取Webhook地址(正式或测试地址,通过配置文件切换)
31 _webhookUrl = App.Configuration["WeChatBot:WebhookUrl"]; 31 _webhookUrl = App.Configuration["WeChatBot:WebhookUrl"];
sql/排查生美业绩统计差异-简化版.sql
@@ -124,3 +124,5 @@ ORDER BY 生美业绩 DESC; @@ -124,3 +124,5 @@ ORDER BY 生美业绩 DESC;
124 124
125 125
126 126
  127 +
  128 +
sql/排查生美业绩统计差异详细.sql
@@ -176,3 +176,5 @@ HAVING COUNT(*) &gt; 1; @@ -176,3 +176,5 @@ HAVING COUNT(*) &gt; 1;
176 176
177 177
178 178
  179 +
  180 +
sql/检查生美业绩统计差异.sql
@@ -146,3 +146,5 @@ ORDER BY 生美业绩 DESC; @@ -146,3 +146,5 @@ ORDER BY 生美业绩 DESC;
146 146
147 147
148 148
  149 +
  150 +
test_tianwang_api.py
@@ -77,3 +77,5 @@ print(f&quot;\n教育一部+教育二部合计 BillingPerformance: {total2}&quot;) @@ -77,3 +77,5 @@ print(f&quot;\n教育一部+教育二部合计 BillingPerformance: {total2}&quot;)
77 77
78 78
79 79
  80 +
  81 +