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 | 1 | using Microsoft.AspNetCore.Authorization; |
| 2 | 2 | using Microsoft.AspNetCore.Mvc; |
| 3 | +using Microsoft.Extensions.Logging; | |
| 3 | 4 | using NCC.Common.Enum; |
| 4 | 5 | using NCC.Common.Filter; |
| 5 | 6 | using NCC.Common.Helper; |
| 6 | 7 | using NCC.Dependency; |
| 7 | 8 | using NCC.DynamicApiController; |
| 8 | 9 | using NCC.Extend.Entitys.Dto.LqBusinessUnitManagerSalary; |
| 10 | +using NCC.Extend.Entitys.Dto.LqSalary; | |
| 9 | 11 | using NCC.Extend.Entitys.Enum; |
| 10 | 12 | using NCC.Extend.Entitys.lq_attendance_summary; |
| 11 | 13 | using NCC.Extend.Entitys.lq_cooperation_cost; |
| ... | ... | @@ -17,13 +19,17 @@ using NCC.Extend.Entitys.lq_md_target; |
| 17 | 19 | using NCC.Extend.Entitys.lq_mdxx; |
| 18 | 20 | using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics; |
| 19 | 21 | using NCC.Extend.Entitys.lq_store_expense; |
| 22 | +using NCC.FriendlyException; | |
| 20 | 23 | using NCC.System.Entitys.Permission; |
| 21 | 24 | using SqlSugar; |
| 22 | 25 | using System; |
| 23 | 26 | using System.Collections.Generic; |
| 27 | +using System.Data; | |
| 28 | +using System.IO; | |
| 24 | 29 | using System.Linq; |
| 25 | 30 | using System.Threading.Tasks; |
| 26 | 31 | using Yitter.IdGenerator; |
| 32 | +using Microsoft.AspNetCore.Http; | |
| 27 | 33 | |
| 28 | 34 | namespace NCC.Extend |
| 29 | 35 | { |
| ... | ... | @@ -35,13 +41,15 @@ namespace NCC.Extend |
| 35 | 41 | public class LqBusinessUnitManagerSalaryService : IDynamicApiController, ITransient |
| 36 | 42 | { |
| 37 | 43 | private readonly ISqlSugarClient _db; |
| 44 | + private readonly ILogger<LqBusinessUnitManagerSalaryService> _logger; | |
| 38 | 45 | |
| 39 | 46 | /// <summary> |
| 40 | 47 | /// 初始化一个<see cref="LqBusinessUnitManagerSalaryService"/>类型的新实例 |
| 41 | 48 | /// </summary> |
| 42 | - public LqBusinessUnitManagerSalaryService(ISqlSugarClient db) | |
| 49 | + public LqBusinessUnitManagerSalaryService(ISqlSugarClient db, ILogger<LqBusinessUnitManagerSalaryService> logger) | |
| 43 | 50 | { |
| 44 | 51 | _db = db; |
| 52 | + _logger = logger; | |
| 45 | 53 | } |
| 46 | 54 | |
| 47 | 55 | /// <summary> |
| ... | ... | @@ -387,8 +395,8 @@ namespace NCC.Extend |
| 387 | 395 | |
| 388 | 396 | // 计算提成(必须满足提成阶梯1才能有提成资格,使用毛利计算) |
| 389 | 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 | 401 | totalCommission += commissionAmount; |
| 394 | 402 | |
| ... | ... | @@ -468,12 +476,41 @@ namespace NCC.Extend |
| 468 | 476 | // 3. 保存数据 |
| 469 | 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 | 632 | public decimal CommissionAmount { get; set; } |
| 596 | 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; | ... | ... |