Commit 3d57a2884cecf02957022981900e25312717630d
1 parent
56252a8e
fix: 修复事业部总经理/经理工资计算接口ValueTuple访问错误
- 修复CalculateStoreCommission方法返回值访问方式,使用Item1和Item2替代.Amount和.Detail - 为所有工资表添加员工确认字段SQL脚本(9个工资表) - 创建安全版SQL脚本,支持检查字段是否存在后再添加
Showing
4 changed files
with
509 additions
and
9 deletions
netcore/src/Modularity/Extend/NCC.Extend/LqBusinessUnitManagerSalaryService.cs
| 1 | using Microsoft.AspNetCore.Authorization; | 1 | using Microsoft.AspNetCore.Authorization; |
| 2 | using Microsoft.AspNetCore.Mvc; | 2 | using Microsoft.AspNetCore.Mvc; |
| 3 | +using Microsoft.Extensions.Logging; | ||
| 3 | using NCC.Common.Enum; | 4 | using NCC.Common.Enum; |
| 4 | using NCC.Common.Filter; | 5 | using NCC.Common.Filter; |
| 5 | using NCC.Common.Helper; | 6 | using NCC.Common.Helper; |
| 6 | using NCC.Dependency; | 7 | using NCC.Dependency; |
| 7 | using NCC.DynamicApiController; | 8 | using NCC.DynamicApiController; |
| 8 | using NCC.Extend.Entitys.Dto.LqBusinessUnitManagerSalary; | 9 | using NCC.Extend.Entitys.Dto.LqBusinessUnitManagerSalary; |
| 10 | +using NCC.Extend.Entitys.Dto.LqSalary; | ||
| 9 | using NCC.Extend.Entitys.Enum; | 11 | using NCC.Extend.Entitys.Enum; |
| 10 | using NCC.Extend.Entitys.lq_attendance_summary; | 12 | using NCC.Extend.Entitys.lq_attendance_summary; |
| 11 | using NCC.Extend.Entitys.lq_cooperation_cost; | 13 | using NCC.Extend.Entitys.lq_cooperation_cost; |
| @@ -17,13 +19,17 @@ using NCC.Extend.Entitys.lq_md_target; | @@ -17,13 +19,17 @@ using NCC.Extend.Entitys.lq_md_target; | ||
| 17 | using NCC.Extend.Entitys.lq_mdxx; | 19 | using NCC.Extend.Entitys.lq_mdxx; |
| 18 | using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics; | 20 | using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics; |
| 19 | using NCC.Extend.Entitys.lq_store_expense; | 21 | using NCC.Extend.Entitys.lq_store_expense; |
| 22 | +using NCC.FriendlyException; | ||
| 20 | using NCC.System.Entitys.Permission; | 23 | using NCC.System.Entitys.Permission; |
| 21 | using SqlSugar; | 24 | using SqlSugar; |
| 22 | using System; | 25 | using System; |
| 23 | using System.Collections.Generic; | 26 | using System.Collections.Generic; |
| 27 | +using System.Data; | ||
| 28 | +using System.IO; | ||
| 24 | using System.Linq; | 29 | using System.Linq; |
| 25 | using System.Threading.Tasks; | 30 | using System.Threading.Tasks; |
| 26 | using Yitter.IdGenerator; | 31 | using Yitter.IdGenerator; |
| 32 | +using Microsoft.AspNetCore.Http; | ||
| 27 | 33 | ||
| 28 | namespace NCC.Extend | 34 | namespace NCC.Extend |
| 29 | { | 35 | { |
| @@ -35,13 +41,15 @@ namespace NCC.Extend | @@ -35,13 +41,15 @@ namespace NCC.Extend | ||
| 35 | public class LqBusinessUnitManagerSalaryService : IDynamicApiController, ITransient | 41 | public class LqBusinessUnitManagerSalaryService : IDynamicApiController, ITransient |
| 36 | { | 42 | { |
| 37 | private readonly ISqlSugarClient _db; | 43 | private readonly ISqlSugarClient _db; |
| 44 | + private readonly ILogger<LqBusinessUnitManagerSalaryService> _logger; | ||
| 38 | 45 | ||
| 39 | /// <summary> | 46 | /// <summary> |
| 40 | /// 初始化一个<see cref="LqBusinessUnitManagerSalaryService"/>类型的新实例 | 47 | /// 初始化一个<see cref="LqBusinessUnitManagerSalaryService"/>类型的新实例 |
| 41 | /// </summary> | 48 | /// </summary> |
| 42 | - public LqBusinessUnitManagerSalaryService(ISqlSugarClient db) | 49 | + public LqBusinessUnitManagerSalaryService(ISqlSugarClient db, ILogger<LqBusinessUnitManagerSalaryService> logger) |
| 43 | { | 50 | { |
| 44 | _db = db; | 51 | _db = db; |
| 52 | + _logger = logger; | ||
| 45 | } | 53 | } |
| 46 | 54 | ||
| 47 | /// <summary> | 55 | /// <summary> |
| @@ -387,8 +395,8 @@ namespace NCC.Extend | @@ -387,8 +395,8 @@ namespace NCC.Extend | ||
| 387 | 395 | ||
| 388 | // 计算提成(必须满足提成阶梯1才能有提成资格,使用毛利计算) | 396 | // 计算提成(必须满足提成阶梯1才能有提成资格,使用毛利计算) |
| 389 | var commissionResult = CalculateStoreCommission(grossProfit, storeLifelineSetting); | 397 | var commissionResult = CalculateStoreCommission(grossProfit, storeLifelineSetting); |
| 390 | - var commissionAmount = commissionResult.Amount; | ||
| 391 | - var calculationDetail = commissionResult.Detail; | 398 | + var commissionAmount = commissionResult.Item1; |
| 399 | + var calculationDetail = commissionResult.Item2; | ||
| 392 | 400 | ||
| 393 | totalCommission += commissionAmount; | 401 | totalCommission += commissionAmount; |
| 394 | 402 | ||
| @@ -468,12 +476,41 @@ namespace NCC.Extend | @@ -468,12 +476,41 @@ namespace NCC.Extend | ||
| 468 | // 3. 保存数据 | 476 | // 3. 保存数据 |
| 469 | if (managerStats.Any()) | 477 | if (managerStats.Any()) |
| 470 | { | 478 | { |
| 471 | - // 先删除当月旧数据 (防止重复) | ||
| 472 | - await _db.Deleteable<LqBusinessUnitManagerSalaryStatisticsEntity>() | ||
| 473 | - .Where(x => x.StatisticsMonth == monthStr) | ||
| 474 | - .ExecuteCommandAsync(); | ||
| 475 | - | ||
| 476 | - await _db.Insertable(managerStats.Values.ToList()).ExecuteCommandAsync(); | 479 | + var existingRecords = await _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>() |
| 480 | + .Where(x => x.StatisticsMonth == monthStr).ToListAsync(); | ||
| 481 | + var existingDict = existingRecords.Where(x => !string.IsNullOrEmpty(x.EmployeeId)) | ||
| 482 | + .GroupBy(x => x.EmployeeId).ToDictionary(g => g.Key, g => g.First()); | ||
| 483 | + var recordsToInsert = new List<LqBusinessUnitManagerSalaryStatisticsEntity>(); | ||
| 484 | + var recordsToUpdate = new List<LqBusinessUnitManagerSalaryStatisticsEntity>(); | ||
| 485 | + var skippedCount = 0; | ||
| 486 | + foreach (var salary in managerStats.Values) | ||
| 487 | + { | ||
| 488 | + if (existingDict.ContainsKey(salary.EmployeeId)) | ||
| 489 | + { | ||
| 490 | + var existing = existingDict[salary.EmployeeId]; | ||
| 491 | + if (existing.IsLocked == 1 || existing.EmployeeConfirmStatus == 1) { skippedCount++; continue; } | ||
| 492 | + salary.Id = existing.Id; | ||
| 493 | + salary.EmployeeConfirmStatus = existing.EmployeeConfirmStatus; | ||
| 494 | + salary.EmployeeConfirmTime = existing.EmployeeConfirmTime; | ||
| 495 | + salary.EmployeeConfirmRemark = existing.EmployeeConfirmRemark; | ||
| 496 | + salary.IsLocked = existing.IsLocked; | ||
| 497 | + salary.CreateTime = existing.CreateTime; | ||
| 498 | + salary.CreateUser = existing.CreateUser; | ||
| 499 | + recordsToUpdate.Add(salary); | ||
| 500 | + } | ||
| 501 | + else | ||
| 502 | + { | ||
| 503 | + salary.Id = YitIdHelper.NextId().ToString(); | ||
| 504 | + salary.EmployeeConfirmStatus = 0; | ||
| 505 | + salary.IsLocked = 0; | ||
| 506 | + salary.CreateTime = DateTime.Now; | ||
| 507 | + salary.CreateUser = ""; | ||
| 508 | + recordsToInsert.Add(salary); | ||
| 509 | + } | ||
| 510 | + } | ||
| 511 | + if (recordsToInsert.Any()) await _db.Insertable(recordsToInsert).ExecuteCommandAsync(); | ||
| 512 | + if (recordsToUpdate.Any()) await _db.Updateable(recordsToUpdate).ExecuteCommandAsync(); | ||
| 513 | + if (skippedCount > 0) _logger.LogWarning($"计算工资时跳过了 {skippedCount} 条已锁定或已确认的记录(月份:{monthStr})"); | ||
| 477 | } | 514 | } |
| 478 | } | 515 | } |
| 479 | 516 | ||
| @@ -595,6 +632,238 @@ namespace NCC.Extend | @@ -595,6 +632,238 @@ namespace NCC.Extend | ||
| 595 | public decimal CommissionAmount { get; set; } | 632 | public decimal CommissionAmount { get; set; } |
| 596 | public string CalculationDetail { get; set; } | 633 | public string CalculationDetail { get; set; } |
| 597 | } | 634 | } |
| 635 | + | ||
| 636 | + #region 员工工资确认 | ||
| 637 | + | ||
| 638 | + [HttpPost("confirm")] | ||
| 639 | + public async Task<string> ConfirmSalary([FromBody] SalaryConfirmInput input) | ||
| 640 | + { | ||
| 641 | + try | ||
| 642 | + { | ||
| 643 | + if (string.IsNullOrWhiteSpace(input.Id) || string.IsNullOrWhiteSpace(input.EmployeeId)) | ||
| 644 | + throw NCCException.Oh("工资记录ID和员工ID不能为空"); | ||
| 645 | + var salary = await _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>() | ||
| 646 | + .Where(s => s.Id == input.Id && s.EmployeeId == input.EmployeeId).FirstAsync(); | ||
| 647 | + if (salary == null) throw NCCException.Oh("工资记录不存在或不属于该员工"); | ||
| 648 | + if (salary.EmployeeConfirmStatus == 1) throw NCCException.Oh("该工资条已确认,不能重复确认"); | ||
| 649 | + if (salary.IsLocked != 1) throw NCCException.Oh("该工资条尚未锁定,请等待管理员锁定后再确认"); | ||
| 650 | + salary.EmployeeConfirmStatus = 1; | ||
| 651 | + salary.EmployeeConfirmTime = DateTime.Now; | ||
| 652 | + salary.EmployeeConfirmRemark = input.Remark; | ||
| 653 | + salary.UpdateTime = DateTime.Now; | ||
| 654 | + await _db.Updateable(salary).ExecuteCommandAsync(); | ||
| 655 | + return "确认成功"; | ||
| 656 | + } | ||
| 657 | + catch (Exception ex) | ||
| 658 | + { | ||
| 659 | + throw NCCException.Oh($"确认工资条失败: {ex.Message}"); | ||
| 660 | + } | ||
| 661 | + } | ||
| 662 | + | ||
| 663 | + #endregion | ||
| 664 | + | ||
| 665 | + #region 导入工资 | ||
| 666 | + | ||
| 667 | + /// <summary> | ||
| 668 | + /// 从Excel导入事业部总经理工资数据 | ||
| 669 | + /// </summary> | ||
| 670 | + /// <param name="file">Excel文件</param> | ||
| 671 | + /// <returns>导入结果</returns> | ||
| 672 | + [HttpPost("import")] | ||
| 673 | + public async Task<dynamic> ImportSalaryFromExcel(IFormFile file) | ||
| 674 | + { | ||
| 675 | + try | ||
| 676 | + { | ||
| 677 | + if (file == null || file.Length == 0) | ||
| 678 | + throw NCCException.Oh("请选择要上传的Excel文件"); | ||
| 679 | + | ||
| 680 | + var allowedExtensions = new[] { ".xlsx", ".xls" }; | ||
| 681 | + var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant(); | ||
| 682 | + if (!allowedExtensions.Contains(fileExtension)) | ||
| 683 | + throw NCCException.Oh("只支持.xlsx和.xls格式的Excel文件"); | ||
| 684 | + | ||
| 685 | + var recordsToInsert = new List<LqBusinessUnitManagerSalaryStatisticsEntity>(); | ||
| 686 | + var recordsToUpdate = new List<LqBusinessUnitManagerSalaryStatisticsEntity>(); | ||
| 687 | + var errorMessages = new List<string>(); | ||
| 688 | + var successCount = 0; | ||
| 689 | + var failCount = 0; | ||
| 690 | + var skippedCount = 0; | ||
| 691 | + | ||
| 692 | + var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName)); | ||
| 693 | + try | ||
| 694 | + { | ||
| 695 | + using (var stream = new FileStream(tempFilePath, FileMode.Create)) | ||
| 696 | + { | ||
| 697 | + await file.CopyToAsync(stream); | ||
| 698 | + } | ||
| 699 | + | ||
| 700 | + var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0); | ||
| 701 | + if (dataTable.Rows.Count == 0) | ||
| 702 | + throw NCCException.Oh("Excel文件中没有数据行"); | ||
| 703 | + | ||
| 704 | + Func<string, decimal> ParseDecimal = (str) => | ||
| 705 | + { | ||
| 706 | + if (string.IsNullOrWhiteSpace(str)) return 0; | ||
| 707 | + var cleaned = str.Trim().Replace(",", "").Replace(",", "").Replace("¥", "").Replace("$", "").Replace("元", "").Replace("%", "").Replace(" ", ""); | ||
| 708 | + return decimal.TryParse(cleaned, out decimal result) ? result : 0; | ||
| 709 | + }; | ||
| 710 | + | ||
| 711 | + Func<string, int> ParseInt = (str) => | ||
| 712 | + { | ||
| 713 | + if (string.IsNullOrWhiteSpace(str)) return 0; | ||
| 714 | + var cleaned = str.Trim().Replace(",", "").Replace(",", "").Replace(" ", ""); | ||
| 715 | + return int.TryParse(cleaned, out int result) ? result : 0; | ||
| 716 | + }; | ||
| 717 | + | ||
| 718 | + for (int i = 1; i < dataTable.Rows.Count; i++) | ||
| 719 | + { | ||
| 720 | + try | ||
| 721 | + { | ||
| 722 | + var row = dataTable.Rows[i]; | ||
| 723 | + Func<int, string> GetColumnValue = (colIndex) => colIndex < row.ItemArray.Length && row[colIndex] != null ? row[colIndex].ToString().Trim() : ""; | ||
| 724 | + | ||
| 725 | + var firstColumnValue = GetColumnValue(0); | ||
| 726 | + bool isOldFormat = !string.IsNullOrWhiteSpace(firstColumnValue) && (firstColumnValue == "员工姓名" || (!long.TryParse(firstColumnValue, out _) && firstColumnValue.Length > 20)); | ||
| 727 | + | ||
| 728 | + int employeeNameIndex = isOldFormat ? 0 : 1; | ||
| 729 | + int offset = isOldFormat ? 0 : 1; | ||
| 730 | + | ||
| 731 | + var id = isOldFormat ? "" : GetColumnValue(0); | ||
| 732 | + var employeeName = GetColumnValue(employeeNameIndex); | ||
| 733 | + | ||
| 734 | + if (string.IsNullOrWhiteSpace(id) && string.IsNullOrWhiteSpace(employeeName)) | ||
| 735 | + continue; | ||
| 736 | + | ||
| 737 | + if (string.IsNullOrWhiteSpace(id) && !string.IsNullOrWhiteSpace(employeeName)) | ||
| 738 | + { | ||
| 739 | + var matchedRecord = await _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>() | ||
| 740 | + .Where(x => x.EmployeeName == employeeName) | ||
| 741 | + .OrderBy(x => x.CreateTime, OrderByType.Desc) | ||
| 742 | + .FirstAsync(); | ||
| 743 | + if (matchedRecord != null) id = matchedRecord.Id; | ||
| 744 | + } | ||
| 745 | + | ||
| 746 | + if (string.IsNullOrWhiteSpace(employeeName)) | ||
| 747 | + { | ||
| 748 | + errorMessages.Add($"第{i + 1}行:员工姓名不能为空"); | ||
| 749 | + failCount++; | ||
| 750 | + continue; | ||
| 751 | + } | ||
| 752 | + | ||
| 753 | + LqBusinessUnitManagerSalaryStatisticsEntity existing = null; | ||
| 754 | + if (!string.IsNullOrWhiteSpace(id)) | ||
| 755 | + { | ||
| 756 | + existing = await _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>() | ||
| 757 | + .Where(x => x.Id == id).FirstAsync(); | ||
| 758 | + | ||
| 759 | + if (existing != null && (existing.IsLocked == 1 || existing.EmployeeConfirmStatus == 1)) | ||
| 760 | + { | ||
| 761 | + skippedCount++; | ||
| 762 | + failCount++; | ||
| 763 | + continue; | ||
| 764 | + } | ||
| 765 | + } | ||
| 766 | + | ||
| 767 | + var entity = existing ?? new LqBusinessUnitManagerSalaryStatisticsEntity | ||
| 768 | + { | ||
| 769 | + Id = string.IsNullOrWhiteSpace(id) ? YitIdHelper.NextId().ToString() : id, | ||
| 770 | + EmployeeConfirmStatus = 0, | ||
| 771 | + IsLocked = 0, | ||
| 772 | + CreateTime = DateTime.Now, | ||
| 773 | + CreateUser = "" | ||
| 774 | + }; | ||
| 775 | + | ||
| 776 | + // Excel字段映射(事业部总经理工资35列:员工姓名,员工账号,核算岗位,经理类型,统计月份,底薪,提成合计,在店天数,请假天数,核算应发工资...) | ||
| 777 | + entity.EmployeeName = employeeName; | ||
| 778 | + entity.EmployeeAccount = GetColumnValue(1 + offset); | ||
| 779 | + entity.Position = GetColumnValue(2 + offset); | ||
| 780 | + var managerTypeStr = GetColumnValue(3 + offset); | ||
| 781 | + entity.ManagerType = managerTypeStr == "总经理" || managerTypeStr == "1" ? 1 : 0; | ||
| 782 | + entity.StatisticsMonth = GetColumnValue(4 + offset); | ||
| 783 | + entity.BaseSalary = ParseDecimal(GetColumnValue(5 + offset)); | ||
| 784 | + entity.TotalCommission = ParseDecimal(GetColumnValue(6 + offset)); | ||
| 785 | + entity.WorkingDays = ParseDecimal(GetColumnValue(7 + offset)); | ||
| 786 | + entity.LeaveDays = ParseDecimal(GetColumnValue(8 + offset)); | ||
| 787 | + entity.CalculatedGrossSalary = ParseDecimal(GetColumnValue(9 + offset)); | ||
| 788 | + entity.FinalGrossSalary = ParseDecimal(GetColumnValue(10 + offset)); | ||
| 789 | + entity.MonthlyTrainingSubsidy = ParseDecimal(GetColumnValue(11 + offset)); | ||
| 790 | + entity.MonthlyTransportSubsidy = ParseDecimal(GetColumnValue(12 + offset)); | ||
| 791 | + entity.LastMonthTrainingSubsidy = ParseDecimal(GetColumnValue(13 + offset)); | ||
| 792 | + entity.LastMonthTransportSubsidy = ParseDecimal(GetColumnValue(14 + offset)); | ||
| 793 | + entity.TotalSubsidy = ParseDecimal(GetColumnValue(15 + offset)); | ||
| 794 | + entity.MissingCard = ParseDecimal(GetColumnValue(16 + offset)); | ||
| 795 | + entity.LateArrival = ParseDecimal(GetColumnValue(17 + offset)); | ||
| 796 | + entity.LeaveDeduction = ParseDecimal(GetColumnValue(18 + offset)); | ||
| 797 | + entity.SocialInsuranceDeduction = ParseDecimal(GetColumnValue(19 + offset)); | ||
| 798 | + entity.RewardDeduction = ParseDecimal(GetColumnValue(20 + offset)); | ||
| 799 | + entity.AccommodationDeduction = ParseDecimal(GetColumnValue(21 + offset)); | ||
| 800 | + entity.StudyPeriodDeduction = ParseDecimal(GetColumnValue(22 + offset)); | ||
| 801 | + entity.WorkClothesDeduction = ParseDecimal(GetColumnValue(23 + offset)); | ||
| 802 | + entity.TotalDeduction = ParseDecimal(GetColumnValue(24 + offset)); | ||
| 803 | + entity.Bonus = ParseDecimal(GetColumnValue(25 + offset)); | ||
| 804 | + entity.ReturnPhoneDeposit = ParseDecimal(GetColumnValue(26 + offset)); | ||
| 805 | + entity.ReturnAccommodationDeposit = ParseDecimal(GetColumnValue(27 + offset)); | ||
| 806 | + entity.LastMonthSupplement = ParseDecimal(GetColumnValue(28 + offset)); | ||
| 807 | + entity.ActualSalary = ParseDecimal(GetColumnValue(29 + offset)); | ||
| 808 | + entity.MonthlyPaymentStatus = GetColumnValue(30 + offset); | ||
| 809 | + entity.PaidAmount = ParseDecimal(GetColumnValue(31 + offset)); | ||
| 810 | + entity.PendingAmount = ParseDecimal(GetColumnValue(32 + offset)); | ||
| 811 | + entity.MonthlyTotalPayment = ParseDecimal(GetColumnValue(33 + offset)); | ||
| 812 | + var isTerminatedStr = GetColumnValue(34 + offset); | ||
| 813 | + entity.IsTerminated = isTerminatedStr == "离职" || isTerminatedStr == "1" ? 1 : 0; | ||
| 814 | + | ||
| 815 | + if (existing != null) | ||
| 816 | + { | ||
| 817 | + entity.EmployeeId = existing.EmployeeId; | ||
| 818 | + entity.StorePerformanceDetail = existing.StorePerformanceDetail; | ||
| 819 | + } | ||
| 820 | + else | ||
| 821 | + { | ||
| 822 | + if (!string.IsNullOrWhiteSpace(employeeName)) | ||
| 823 | + { | ||
| 824 | + var user = await _db.Queryable<UserEntity>() | ||
| 825 | + .Where(u => u.RealName == employeeName).FirstAsync(); | ||
| 826 | + if (user != null) entity.EmployeeId = user.Id; | ||
| 827 | + } | ||
| 828 | + } | ||
| 829 | + | ||
| 830 | + entity.UpdateTime = DateTime.Now; | ||
| 831 | + if (existing != null) recordsToUpdate.Add(entity); | ||
| 832 | + else recordsToInsert.Add(entity); | ||
| 833 | + successCount++; | ||
| 834 | + } | ||
| 835 | + catch (Exception ex) | ||
| 836 | + { | ||
| 837 | + errorMessages.Add($"第{i + 1}行数据处理失败: {ex.Message}"); | ||
| 838 | + failCount++; | ||
| 839 | + } | ||
| 840 | + } | ||
| 841 | + } | ||
| 842 | + finally | ||
| 843 | + { | ||
| 844 | + if (File.Exists(tempFilePath)) File.Delete(tempFilePath); | ||
| 845 | + } | ||
| 846 | + | ||
| 847 | + if (recordsToInsert.Any()) await _db.Insertable(recordsToInsert).ExecuteCommandAsync(); | ||
| 848 | + if (recordsToUpdate.Any()) await _db.Updateable(recordsToUpdate).ExecuteCommandAsync(); | ||
| 849 | + | ||
| 850 | + return new | ||
| 851 | + { | ||
| 852 | + success = true, | ||
| 853 | + message = $"导入完成:成功 {successCount} 条,失败 {failCount} 条,跳过 {skippedCount} 条(已锁定或已确认)", | ||
| 854 | + successCount, | ||
| 855 | + failCount, | ||
| 856 | + skippedCount, | ||
| 857 | + errors = errorMessages | ||
| 858 | + }; | ||
| 859 | + } | ||
| 860 | + catch (Exception ex) | ||
| 861 | + { | ||
| 862 | + throw NCCException.Oh($"导入事业部总经理工资数据失败: {ex.Message}"); | ||
| 863 | + } | ||
| 864 | + } | ||
| 865 | + | ||
| 866 | + #endregion | ||
| 598 | } | 867 | } |
| 599 | } | 868 | } |
| 600 | 869 |
sql/为事业部总经理经理工资表添加员工确认字段.sql
0 → 100644
| 1 | +-- ============================================ | ||
| 2 | +-- 为事业部总经理/经理工资统计表添加员工确认字段 | ||
| 3 | +-- 功能:支持员工确认工资条,确认后工资数据不可修改 | ||
| 4 | +-- 创建时间:2025年 | ||
| 5 | +-- ============================================ | ||
| 6 | + | ||
| 7 | +-- 为事业部总经理/经理工资统计表添加员工确认字段 | ||
| 8 | +ALTER TABLE lq_business_unit_manager_salary_statistics | ||
| 9 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 10 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 11 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 12 | + | ||
| 13 | +-- 验证字段是否添加成功 | ||
| 14 | +SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT | ||
| 15 | +FROM INFORMATION_SCHEMA.COLUMNS | ||
| 16 | +WHERE TABLE_SCHEMA = DATABASE() | ||
| 17 | + AND TABLE_NAME = 'lq_business_unit_manager_salary_statistics' | ||
| 18 | + AND COLUMN_NAME LIKE '%Confirm%' | ||
| 19 | +ORDER BY ORDINAL_POSITION; |
sql/为所有工资表添加员工确认字段.sql
0 → 100644
| 1 | +-- ============================================ | ||
| 2 | +-- 为所有工资表添加员工确认字段 | ||
| 3 | +-- 功能:支持员工确认工资条,确认后工资数据不可修改 | ||
| 4 | +-- 创建时间:2025年 | ||
| 5 | +-- 说明:lq_employee_salary_statistics 表在创建时已包含这些字段,无需添加 | ||
| 6 | +-- ============================================ | ||
| 7 | + | ||
| 8 | +-- 注意:MySQL不支持 ADD COLUMN IF NOT EXISTS 语法 | ||
| 9 | +-- 如果字段已存在,执行时会报错,可以忽略该错误或先检查字段是否存在 | ||
| 10 | + | ||
| 11 | +-- 1. 健康师工资统计表 | ||
| 12 | +ALTER TABLE lq_salary_statistics | ||
| 13 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 14 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 15 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 16 | + | ||
| 17 | +-- 2. 科技部老师工资统计表 | ||
| 18 | +ALTER TABLE lq_tech_teacher_salary_statistics | ||
| 19 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 20 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 21 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 22 | + | ||
| 23 | +-- 3. 店助工资统计表 | ||
| 24 | +ALTER TABLE lq_assistant_salary_statistics | ||
| 25 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 26 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 27 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 28 | + | ||
| 29 | +-- 4. 店长工资统计表 | ||
| 30 | +ALTER TABLE lq_store_manager_salary_statistics | ||
| 31 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 32 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 33 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 34 | + | ||
| 35 | +-- 5. 主任工资统计表 | ||
| 36 | +ALTER TABLE lq_director_salary_statistics | ||
| 37 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 38 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 39 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 40 | + | ||
| 41 | +-- 6. 大项目部老师工资统计表 | ||
| 42 | +ALTER TABLE lq_major_project_teacher_salary_statistics | ||
| 43 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 44 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 45 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 46 | + | ||
| 47 | +-- 7. 大项目主管工资统计表 | ||
| 48 | +ALTER TABLE lq_major_project_director_salary_statistics | ||
| 49 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 50 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 51 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 52 | + | ||
| 53 | +-- 8. 科技部总经理工资统计表 | ||
| 54 | +ALTER TABLE lq_tech_general_manager_salary_statistics | ||
| 55 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 56 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 57 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 58 | + | ||
| 59 | +-- 9. 事业部总经理/经理工资统计表 | ||
| 60 | +ALTER TABLE lq_business_unit_manager_salary_statistics | ||
| 61 | +ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT '员工确认状态(0=未确认,1=已确认)', | ||
| 62 | +ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT '员工确认时间', | ||
| 63 | +ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT '员工确认备注'; | ||
| 64 | + | ||
| 65 | +-- ============================================ | ||
| 66 | +-- 验证字段是否添加成功 | ||
| 67 | +-- ============================================ | ||
| 68 | +SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT | ||
| 69 | +FROM INFORMATION_SCHEMA.COLUMNS | ||
| 70 | +WHERE TABLE_SCHEMA = DATABASE() | ||
| 71 | + AND TABLE_NAME IN ( | ||
| 72 | + 'lq_salary_statistics', | ||
| 73 | + 'lq_tech_teacher_salary_statistics', | ||
| 74 | + 'lq_assistant_salary_statistics', | ||
| 75 | + 'lq_store_manager_salary_statistics', | ||
| 76 | + 'lq_director_salary_statistics', | ||
| 77 | + 'lq_major_project_teacher_salary_statistics', | ||
| 78 | + 'lq_major_project_director_salary_statistics', | ||
| 79 | + 'lq_tech_general_manager_salary_statistics', | ||
| 80 | + 'lq_business_unit_manager_salary_statistics', | ||
| 81 | + 'lq_employee_salary_statistics' | ||
| 82 | + ) | ||
| 83 | + AND COLUMN_NAME LIKE '%Confirm%' | ||
| 84 | +ORDER BY TABLE_NAME, ORDINAL_POSITION; |
sql/为所有工资表添加员工确认字段_安全版.sql
0 → 100644
| 1 | +-- ============================================ | ||
| 2 | +-- 为所有工资表添加员工确认字段(安全版) | ||
| 3 | +-- 功能:支持员工确认工资条,确认后工资数据不可修改 | ||
| 4 | +-- 创建时间:2025年 | ||
| 5 | +-- 说明:此版本会先检查字段是否存在,避免重复添加 | ||
| 6 | +-- ============================================ | ||
| 7 | + | ||
| 8 | +-- 说明:lq_employee_salary_statistics 表在创建时已包含这些字段,无需添加 | ||
| 9 | + | ||
| 10 | +-- 使用存储过程安全添加字段(如果字段不存在则添加) | ||
| 11 | +DELIMITER $$ | ||
| 12 | + | ||
| 13 | +DROP PROCEDURE IF EXISTS AddEmployeeConfirmFields$$ | ||
| 14 | + | ||
| 15 | +CREATE PROCEDURE AddEmployeeConfirmFields() | ||
| 16 | +BEGIN | ||
| 17 | + DECLARE done INT DEFAULT FALSE; | ||
| 18 | + DECLARE tableName VARCHAR(100); | ||
| 19 | + DECLARE tableCursor CURSOR FOR | ||
| 20 | + SELECT TABLE_NAME | ||
| 21 | + FROM INFORMATION_SCHEMA.TABLES | ||
| 22 | + WHERE TABLE_SCHEMA = DATABASE() | ||
| 23 | + AND TABLE_NAME IN ( | ||
| 24 | + 'lq_salary_statistics', | ||
| 25 | + 'lq_tech_teacher_salary_statistics', | ||
| 26 | + 'lq_assistant_salary_statistics', | ||
| 27 | + 'lq_store_manager_salary_statistics', | ||
| 28 | + 'lq_director_salary_statistics', | ||
| 29 | + 'lq_major_project_teacher_salary_statistics', | ||
| 30 | + 'lq_major_project_director_salary_statistics', | ||
| 31 | + 'lq_tech_general_manager_salary_statistics', | ||
| 32 | + 'lq_business_unit_manager_salary_statistics' | ||
| 33 | + ); | ||
| 34 | + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; | ||
| 35 | + | ||
| 36 | + OPEN tableCursor; | ||
| 37 | + read_loop: LOOP | ||
| 38 | + FETCH tableCursor INTO tableName; | ||
| 39 | + IF done THEN | ||
| 40 | + LEAVE read_loop; | ||
| 41 | + END IF; | ||
| 42 | + | ||
| 43 | + -- 检查并添加 F_EmployeeConfirmStatus | ||
| 44 | + SET @sql = CONCAT('SELECT COUNT(*) INTO @colCount FROM INFORMATION_SCHEMA.COLUMNS | ||
| 45 | + WHERE TABLE_SCHEMA = DATABASE() | ||
| 46 | + AND TABLE_NAME = ''', tableName, ''' | ||
| 47 | + AND COLUMN_NAME = ''F_EmployeeConfirmStatus'''); | ||
| 48 | + PREPARE stmt FROM @sql; | ||
| 49 | + EXECUTE stmt; | ||
| 50 | + DEALLOCATE PREPARE stmt; | ||
| 51 | + | ||
| 52 | + IF @colCount = 0 THEN | ||
| 53 | + SET @sql = CONCAT('ALTER TABLE ', tableName, ' | ||
| 54 | + ADD COLUMN F_EmployeeConfirmStatus INT NOT NULL DEFAULT 0 COMMENT ''员工确认状态(0=未确认,1=已确认)'''); | ||
| 55 | + PREPARE stmt FROM @sql; | ||
| 56 | + EXECUTE stmt; | ||
| 57 | + DEALLOCATE PREPARE stmt; | ||
| 58 | + SELECT CONCAT('已为表 ', tableName, ' 添加字段 F_EmployeeConfirmStatus') AS result; | ||
| 59 | + END IF; | ||
| 60 | + | ||
| 61 | + -- 检查并添加 F_EmployeeConfirmTime | ||
| 62 | + SET @sql = CONCAT('SELECT COUNT(*) INTO @colCount FROM INFORMATION_SCHEMA.COLUMNS | ||
| 63 | + WHERE TABLE_SCHEMA = DATABASE() | ||
| 64 | + AND TABLE_NAME = ''', tableName, ''' | ||
| 65 | + AND COLUMN_NAME = ''F_EmployeeConfirmTime'''); | ||
| 66 | + PREPARE stmt FROM @sql; | ||
| 67 | + EXECUTE stmt; | ||
| 68 | + DEALLOCATE PREPARE stmt; | ||
| 69 | + | ||
| 70 | + IF @colCount = 0 THEN | ||
| 71 | + SET @sql = CONCAT('ALTER TABLE ', tableName, ' | ||
| 72 | + ADD COLUMN F_EmployeeConfirmTime DATETIME NULL COMMENT ''员工确认时间'''); | ||
| 73 | + PREPARE stmt FROM @sql; | ||
| 74 | + EXECUTE stmt; | ||
| 75 | + DEALLOCATE PREPARE stmt; | ||
| 76 | + SELECT CONCAT('已为表 ', tableName, ' 添加字段 F_EmployeeConfirmTime') AS result; | ||
| 77 | + END IF; | ||
| 78 | + | ||
| 79 | + -- 检查并添加 F_EmployeeConfirmRemark | ||
| 80 | + SET @sql = CONCAT('SELECT COUNT(*) INTO @colCount FROM INFORMATION_SCHEMA.COLUMNS | ||
| 81 | + WHERE TABLE_SCHEMA = DATABASE() | ||
| 82 | + AND TABLE_NAME = ''', tableName, ''' | ||
| 83 | + AND COLUMN_NAME = ''F_EmployeeConfirmRemark'''); | ||
| 84 | + PREPARE stmt FROM @sql; | ||
| 85 | + EXECUTE stmt; | ||
| 86 | + DEALLOCATE PREPARE stmt; | ||
| 87 | + | ||
| 88 | + IF @colCount = 0 THEN | ||
| 89 | + SET @sql = CONCAT('ALTER TABLE ', tableName, ' | ||
| 90 | + ADD COLUMN F_EmployeeConfirmRemark VARCHAR(500) NULL COMMENT ''员工确认备注'''); | ||
| 91 | + PREPARE stmt FROM @sql; | ||
| 92 | + EXECUTE stmt; | ||
| 93 | + DEALLOCATE PREPARE stmt; | ||
| 94 | + SELECT CONCAT('已为表 ', tableName, ' 添加字段 F_EmployeeConfirmRemark') AS result; | ||
| 95 | + END IF; | ||
| 96 | + | ||
| 97 | + END LOOP; | ||
| 98 | + CLOSE tableCursor; | ||
| 99 | +END$$ | ||
| 100 | + | ||
| 101 | +DELIMITER ; | ||
| 102 | + | ||
| 103 | +-- 执行存储过程 | ||
| 104 | +CALL AddEmployeeConfirmFields(); | ||
| 105 | + | ||
| 106 | +-- 删除存储过程 | ||
| 107 | +DROP PROCEDURE IF EXISTS AddEmployeeConfirmFields; | ||
| 108 | + | ||
| 109 | +-- ============================================ | ||
| 110 | +-- 验证字段是否添加成功 | ||
| 111 | +-- ============================================ | ||
| 112 | +SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT | ||
| 113 | +FROM INFORMATION_SCHEMA.COLUMNS | ||
| 114 | +WHERE TABLE_SCHEMA = DATABASE() | ||
| 115 | + AND TABLE_NAME IN ( | ||
| 116 | + 'lq_salary_statistics', | ||
| 117 | + 'lq_tech_teacher_salary_statistics', | ||
| 118 | + 'lq_assistant_salary_statistics', | ||
| 119 | + 'lq_store_manager_salary_statistics', | ||
| 120 | + 'lq_director_salary_statistics', | ||
| 121 | + 'lq_major_project_teacher_salary_statistics', | ||
| 122 | + 'lq_major_project_director_salary_statistics', | ||
| 123 | + 'lq_tech_general_manager_salary_statistics', | ||
| 124 | + 'lq_business_unit_manager_salary_statistics', | ||
| 125 | + 'lq_employee_salary_statistics' | ||
| 126 | + ) | ||
| 127 | + AND COLUMN_NAME LIKE '%Confirm%' | ||
| 128 | +ORDER BY TABLE_NAME, ORDINAL_POSITION; |