Commit 3d57a2884cecf02957022981900e25312717630d

Authored by “wangming”
1 parent 56252a8e

fix: 修复事业部总经理/经理工资计算接口ValueTuple访问错误

- 修复CalculateStoreCommission方法返回值访问方式,使用Item1和Item2替代.Amount和.Detail
- 为所有工资表添加员工确认字段SQL脚本(9个工资表)
- 创建安全版SQL脚本,支持检查字段是否存在后再添加
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;
... ...