Commit ac25ff124b26024e5debb94b5b3bca78ed99587c

Authored by “wangming”
1 parent b717f998

feat: 添加大项目部老师工资计算、事业部总经理/经理工资计算、健康师额外数据导入修复、OSS URL迁移功能

- 新增大项目部老师工资计算功能(LqMajorProjectTeacherSalaryService)
- 新增事业部总经理/经理工资计算功能(LqBusinessUnitManagerSalaryService)
- 修复健康师额外数据导入功能,支持列数不足的情况
- 新增OSS URL迁移功能,支持将旧OSS地址替换为新OSS地址
- 优化库存使用申请列表查询,添加审批状态和是否已领取字段
- 优化文件上传服务,统一返回OSS路径
Showing 45 changed files with 4509 additions and 70 deletions
excel/健康师额外数据模板.xlsx 0 → 100644
No preview for this file type
netcore/src/Application/NCC.API/appsettings.json
... ... @@ -189,7 +189,7 @@
189 189 "NCC_App": {
190 190 "CodeAreasName": "SubDev,Food,Extend,test",
191 191 //系统文件路径(末尾必须带斜杆)
192   - "SystemPath": "Files/",
  192 + "SystemPath": "/",
193 193 //微信公众号允许上传文件类型
194 194 "MPUploadFileType": "bmp,png,jpeg,jpg,gif,mp3,wma,wav,amr,mp4",
195 195 //微信允许上传文件类型
... ... @@ -213,7 +213,7 @@
213 213 "AccessKeySecret": "84dpUAlu2eoyFOIEhFGkZlIy45h0B6",
214 214 "Endpoint": "oss-cn-chengdu.aliyuncs.com",
215 215 "Region": "cn-chengdu",
216   - "CustomDomain": "http://oss.lvqianmeiye.com"
  216 + "CustomDomain": "https://lvqian-erip.oss-cn-chengdu.aliyuncs.com"
217 217 },
218 218 //================== 系统错误邮件报告反馈相关 ============================== -->
219 219 //软件的错误报告
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqBusinessUnitManagerSalary/BusinessUnitManagerSalaryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqBusinessUnitManagerSalary
  5 +{
  6 + /// <summary>
  7 + /// 事业部总经理/经理工资查询参数
  8 + /// </summary>
  9 + public class BusinessUnitManagerSalaryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 年份
  13 + /// </summary>
  14 + public int Year { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 月份
  18 + /// </summary>
  19 + public int Month { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 经理类型(0=经理,1=总经理,不传则查询全部)
  23 + /// </summary>
  24 + public int? ManagerType { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 员工姓名/账号(可选,用于模糊搜索)
  28 + /// </summary>
  29 + public string Keyword { get; set; }
  30 + }
  31 +}
  32 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqBusinessUnitManagerSalary/BusinessUnitManagerSalaryOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqBusinessUnitManagerSalary
  4 +{
  5 + /// <summary>
  6 + /// 事业部总经理/经理工资输出
  7 + /// </summary>
  8 + public class BusinessUnitManagerSalaryOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 统计月份
  17 + /// </summary>
  18 + public string StatisticsMonth { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 核算岗位
  22 + /// </summary>
  23 + public string Position { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 员工姓名
  27 + /// </summary>
  28 + public string EmployeeName { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 员工ID
  32 + /// </summary>
  33 + public string EmployeeId { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 员工账号
  37 + /// </summary>
  38 + public string EmployeeAccount { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 经理类型(0=经理,1=总经理)
  42 + /// </summary>
  43 + public int ManagerType { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 是否离职
  47 + /// </summary>
  48 + public int IsTerminated { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 门店业绩明细(JSON格式)
  52 + /// </summary>
  53 + public string StorePerformanceDetail { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 底薪
  57 + /// </summary>
  58 + public decimal BaseSalary { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 提成合计
  62 + /// </summary>
  63 + public decimal TotalCommission { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 在店天数
  67 + /// </summary>
  68 + public decimal WorkingDays { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 请假天数
  72 + /// </summary>
  73 + public decimal LeaveDays { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 核算应发工资
  77 + /// </summary>
  78 + public decimal CalculatedGrossSalary { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 最终应发工资
  82 + /// </summary>
  83 + public decimal FinalGrossSalary { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 当月培训补贴
  87 + /// </summary>
  88 + public decimal MonthlyTrainingSubsidy { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 当月交通补贴
  92 + /// </summary>
  93 + public decimal MonthlyTransportSubsidy { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 上月培训补贴
  97 + /// </summary>
  98 + public decimal LastMonthTrainingSubsidy { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 上月交通补贴
  102 + /// </summary>
  103 + public decimal LastMonthTransportSubsidy { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 补贴合计
  107 + /// </summary>
  108 + public decimal TotalSubsidy { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 缺卡扣款
  112 + /// </summary>
  113 + public decimal MissingCard { get; set; }
  114 +
  115 + /// <summary>
  116 + /// 迟到扣款
  117 + /// </summary>
  118 + public decimal LateArrival { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 请假扣款
  122 + /// </summary>
  123 + public decimal LeaveDeduction { get; set; }
  124 +
  125 + /// <summary>
  126 + /// 扣社保
  127 + /// </summary>
  128 + public decimal SocialInsuranceDeduction { get; set; }
  129 +
  130 + /// <summary>
  131 + /// 扣除奖励
  132 + /// </summary>
  133 + public decimal RewardDeduction { get; set; }
  134 +
  135 + /// <summary>
  136 + /// 扣住宿费
  137 + /// </summary>
  138 + public decimal AccommodationDeduction { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 扣学习期费用
  142 + /// </summary>
  143 + public decimal StudyPeriodDeduction { get; set; }
  144 +
  145 + /// <summary>
  146 + /// 扣工作服费用
  147 + /// </summary>
  148 + public decimal WorkClothesDeduction { get; set; }
  149 +
  150 + /// <summary>
  151 + /// 扣款合计
  152 + /// </summary>
  153 + public decimal TotalDeduction { get; set; }
  154 +
  155 + /// <summary>
  156 + /// 发奖金
  157 + /// </summary>
  158 + public decimal Bonus { get; set; }
  159 +
  160 + /// <summary>
  161 + /// 退手机押金
  162 + /// </summary>
  163 + public decimal ReturnPhoneDeposit { get; set; }
  164 +
  165 + /// <summary>
  166 + /// 退住宿押金
  167 + /// </summary>
  168 + public decimal ReturnAccommodationDeposit { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 实发工资
  172 + /// </summary>
  173 + public decimal ActualSalary { get; set; }
  174 +
  175 + /// <summary>
  176 + /// 当月是否发放
  177 + /// </summary>
  178 + public string MonthlyPaymentStatus { get; set; }
  179 +
  180 + /// <summary>
  181 + /// 支付金额
  182 + /// </summary>
  183 + public decimal PaidAmount { get; set; }
  184 +
  185 + /// <summary>
  186 + /// 待支付金额
  187 + /// </summary>
  188 + public decimal PendingAmount { get; set; }
  189 +
  190 + /// <summary>
  191 + /// 补发上月
  192 + /// </summary>
  193 + public decimal LastMonthSupplement { get; set; }
  194 +
  195 + /// <summary>
  196 + /// 当月支付总额
  197 + /// </summary>
  198 + public decimal MonthlyTotalPayment { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 是否锁定
  202 + /// </summary>
  203 + public int IsLocked { get; set; }
  204 +
  205 + /// <summary>
  206 + /// 更新时间
  207 + /// </summary>
  208 + public DateTime UpdateTime { get; set; }
  209 + }
  210 +}
  211 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs
... ... @@ -111,5 +111,15 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
111 111 /// 使用批次ID(同一批次申请的使用记录使用相同的批次ID)
112 112 /// </summary>
113 113 public string usageBatchId { get; set; }
  114 +
  115 + /// <summary>
  116 + /// 审批状态(待审批/审批中/已通过/未通过/已退回),通过usageBatchId关联申请表获取
  117 + /// </summary>
  118 + public string approvalStatus { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 是否已领取(1-已领取,0-未领取),通过usageBatchId关联申请表获取
  122 + /// </summary>
  123 + public int? isReceived { get; set; }
114 124 }
115 125 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMajorProjectTeacherSalary/MajorProjectTeacherSalaryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqMajorProjectTeacherSalary
  5 +{
  6 + /// <summary>
  7 + /// 大项目部老师工资查询参数
  8 + /// </summary>
  9 + public class MajorProjectTeacherSalaryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 年份
  13 + /// </summary>
  14 + public int Year { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 月份
  18 + /// </summary>
  19 + public int Month { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 门店ID(可选,用于筛选特定门店)
  23 + /// </summary>
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 员工姓名/账号(可选,用于模糊搜索)
  28 + /// </summary>
  29 + public string Keyword { get; set; }
  30 + }
  31 +}
  32 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMajorProjectTeacherSalary/MajorProjectTeacherSalaryOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqMajorProjectTeacherSalary
  4 +{
  5 + /// <summary>
  6 + /// 大项目部老师工资输出
  7 + /// </summary>
  8 + public class MajorProjectTeacherSalaryOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 统计月份
  17 + /// </summary>
  18 + public string StatisticsMonth { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID
  22 + /// </summary>
  23 + public string StoreId { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 门店名称
  27 + /// </summary>
  28 + public string StoreName { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 核算岗位
  32 + /// </summary>
  33 + public string Position { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 员工姓名
  37 + /// </summary>
  38 + public string EmployeeName { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 员工ID
  42 + /// </summary>
  43 + public string EmployeeId { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 员工账号
  47 + /// </summary>
  48 + public string EmployeeAccount { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 开单业绩
  52 + /// </summary>
  53 + public decimal OrderAchievement { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 消耗业绩
  57 + /// </summary>
  58 + public decimal ConsumeAchievement { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 退卡业绩
  62 + /// </summary>
  63 + public decimal RefundAchievement { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 总业绩
  67 + /// </summary>
  68 + public decimal TotalPerformance { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 底薪
  72 + /// </summary>
  73 + public decimal BaseSalary { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 业绩提成比例
  77 + /// </summary>
  78 + public decimal PerformanceCommissionRate { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 业绩提成金额
  82 + /// </summary>
  83 + public decimal PerformanceCommissionAmount { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 提成合计
  87 + /// </summary>
  88 + public decimal TotalCommission { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 手工费
  92 + /// </summary>
  93 + public decimal HandworkFee { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 在店天数
  97 + /// </summary>
  98 + public decimal WorkingDays { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 请假天数
  102 + /// </summary>
  103 + public decimal LeaveDays { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 车补
  107 + /// </summary>
  108 + public decimal TransportationAllowance { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 少休费
  112 + /// </summary>
  113 + public decimal LessRest { get; set; }
  114 +
  115 + /// <summary>
  116 + /// 全勤奖
  117 + /// </summary>
  118 + public decimal FullAttendance { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 核算应发工资
  122 + /// </summary>
  123 + public decimal CalculatedGrossSalary { get; set; }
  124 +
  125 + /// <summary>
  126 + /// 保底工资
  127 + /// </summary>
  128 + public decimal GuaranteedSalary { get; set; }
  129 +
  130 + /// <summary>
  131 + /// 保底请假扣款
  132 + /// </summary>
  133 + public decimal GuaranteedLeaveDeduction { get; set; }
  134 +
  135 + /// <summary>
  136 + /// 保底底薪
  137 + /// </summary>
  138 + public decimal GuaranteedBaseSalary { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 保底补差
  142 + /// </summary>
  143 + public decimal GuaranteedSupplement { get; set; }
  144 +
  145 + /// <summary>
  146 + /// 最终应发工资
  147 + /// </summary>
  148 + public decimal FinalGrossSalary { get; set; }
  149 +
  150 + /// <summary>
  151 + /// 当月培训补贴
  152 + /// </summary>
  153 + public decimal MonthlyTrainingSubsidy { get; set; }
  154 +
  155 + /// <summary>
  156 + /// 当月交通补贴
  157 + /// </summary>
  158 + public decimal MonthlyTransportSubsidy { get; set; }
  159 +
  160 + /// <summary>
  161 + /// 上月培训补贴
  162 + /// </summary>
  163 + public decimal LastMonthTrainingSubsidy { get; set; }
  164 +
  165 + /// <summary>
  166 + /// 上月交通补贴
  167 + /// </summary>
  168 + public decimal LastMonthTransportSubsidy { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 补贴合计
  172 + /// </summary>
  173 + public decimal TotalSubsidy { get; set; }
  174 +
  175 + /// <summary>
  176 + /// 缺卡扣款
  177 + /// </summary>
  178 + public decimal MissingCard { get; set; }
  179 +
  180 + /// <summary>
  181 + /// 迟到扣款
  182 + /// </summary>
  183 + public decimal LateArrival { get; set; }
  184 +
  185 + /// <summary>
  186 + /// 请假扣款
  187 + /// </summary>
  188 + public decimal LeaveDeduction { get; set; }
  189 +
  190 + /// <summary>
  191 + /// 扣社保
  192 + /// </summary>
  193 + public decimal SocialInsuranceDeduction { get; set; }
  194 +
  195 + /// <summary>
  196 + /// 扣除奖励
  197 + /// </summary>
  198 + public decimal RewardDeduction { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 扣住宿费
  202 + /// </summary>
  203 + public decimal AccommodationDeduction { get; set; }
  204 +
  205 + /// <summary>
  206 + /// 扣学习期费用
  207 + /// </summary>
  208 + public decimal StudyPeriodDeduction { get; set; }
  209 +
  210 + /// <summary>
  211 + /// 扣工作服费用
  212 + /// </summary>
  213 + public decimal WorkClothesDeduction { get; set; }
  214 +
  215 + /// <summary>
  216 + /// 扣款合计
  217 + /// </summary>
  218 + public decimal TotalDeduction { get; set; }
  219 +
  220 + /// <summary>
  221 + /// 发奖金
  222 + /// </summary>
  223 + public decimal Bonus { get; set; }
  224 +
  225 + /// <summary>
  226 + /// 退手机押金
  227 + /// </summary>
  228 + public decimal ReturnPhoneDeposit { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 退住宿押金
  232 + /// </summary>
  233 + public decimal ReturnAccommodationDeposit { get; set; }
  234 +
  235 + /// <summary>
  236 + /// 实发工资
  237 + /// </summary>
  238 + public decimal ActualSalary { get; set; }
  239 +
  240 + /// <summary>
  241 + /// 当月是否发放
  242 + /// </summary>
  243 + public string MonthlyPaymentStatus { get; set; }
  244 +
  245 + /// <summary>
  246 + /// 支付金额
  247 + /// </summary>
  248 + public decimal PaidAmount { get; set; }
  249 +
  250 + /// <summary>
  251 + /// 待支付金额
  252 + /// </summary>
  253 + public decimal PendingAmount { get; set; }
  254 +
  255 + /// <summary>
  256 + /// 补发上月
  257 + /// </summary>
  258 + public decimal LastMonthSupplement { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 当月支付总额
  262 + /// </summary>
  263 + public decimal MonthlyTotalPayment { get; set; }
  264 +
  265 + /// <summary>
  266 + /// 是否锁定
  267 + /// </summary>
  268 + public int IsLocked { get; set; }
  269 +
  270 + /// <summary>
  271 + /// 是否离职
  272 + /// </summary>
  273 + public int IsTerminated { get; set; }
  274 +
  275 + /// <summary>
  276 + /// 更新时间
  277 + /// </summary>
  278 + public DateTime UpdateTime { get; set; }
  279 +
  280 + /// <summary>
  281 + /// 门店类型
  282 + /// </summary>
  283 + public int? StoreType { get; set; }
  284 +
  285 + /// <summary>
  286 + /// 门店类别
  287 + /// </summary>
  288 + public int? StoreCategory { get; set; }
  289 +
  290 + /// <summary>
  291 + /// 是否新店
  292 + /// </summary>
  293 + public string IsNewStore { get; set; }
  294 +
  295 + /// <summary>
  296 + /// 新店保护阶段
  297 + /// </summary>
  298 + public int NewStoreProtectionStage { get; set; }
  299 + }
  300 +}
  301 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentCrInput.cs
... ... @@ -34,6 +34,11 @@ namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
34 34 public string teacherId { get; set; }
35 35  
36 36 /// <summary>
  37 + /// 教育部老师用户ID
  38 + /// </summary>
  39 + public string educationTeacherId { get; set; }
  40 +
  41 + /// <summary>
37 42 /// 备注说明
38 43 /// </summary>
39 44 public string remark { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentInfoOutput.cs
... ... @@ -43,6 +43,16 @@ namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
43 43 public string teacherName { get; set; }
44 44  
45 45 /// <summary>
  46 + /// 教育部老师用户ID
  47 + /// </summary>
  48 + public string educationTeacherId { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 教育部老师姓名
  52 + /// </summary>
  53 + public string educationTeacherName { get; set; }
  54 +
  55 + /// <summary>
46 56 /// 备注说明
47 57 /// </summary>
48 58 public string remark { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListOutput.cs
... ... @@ -43,6 +43,16 @@ namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
43 43 public string teacherName { get; set; }
44 44  
45 45 /// <summary>
  46 + /// 教育部老师用户ID
  47 + /// </summary>
  48 + public string educationTeacherId { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 教育部老师姓名
  52 + /// </summary>
  53 + public string educationTeacherName { get; set; }
  54 +
  55 + /// <summary>
46 56 /// 备注说明
47 57 /// </summary>
48 58 public string remark { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListQueryInput.cs
... ... @@ -26,5 +26,10 @@ namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
26 26 /// 大项目部老师用户ID
27 27 /// </summary>
28 28 public string teacherId { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 教育部老师用户ID
  32 + /// </summary>
  33 + public string educationTeacherId { get; set; }
29 34 }
30 35 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentUpInput.cs
... ... @@ -40,6 +40,11 @@ namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
40 40 public string teacherId { get; set; }
41 41  
42 42 /// <summary>
  43 + /// 教育部老师用户ID
  44 + /// </summary>
  45 + public string educationTeacherId { get; set; }
  46 +
  47 + /// <summary>
43 48 /// 备注说明
44 49 /// </summary>
45 50 public string remark { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_business_unit_manager_salary_statistics/LqBusinessUnitManagerSalaryStatisticsEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics
  6 +{
  7 + /// <summary>
  8 + /// 事业部总经理/经理工资统计表
  9 + /// </summary>
  10 + [SugarTable("lq_business_unit_manager_salary_statistics")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqBusinessUnitManagerSalaryStatisticsEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 统计月份(YYYYMM)
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_StatisticsMonth", Length = 6)]
  24 + public string StatisticsMonth { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 核算岗位
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_Position")]
  30 + public string Position { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 员工姓名
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_EmployeeName")]
  36 + public string EmployeeName { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 员工ID
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_EmployeeId")]
  42 + public string EmployeeId { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 员工账号
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_EmployeeAccount")]
  48 + public string EmployeeAccount { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 经理类型(0=经理,1=总经理)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_ManagerType")]
  54 + public int ManagerType { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 是否离职(0=在职,1=离职)
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_IsTerminated")]
  60 + public int IsTerminated { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 门店业绩明细(JSON格式)
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_StorePerformanceDetail", ColumnDataType = "TEXT")]
  66 + public string StorePerformanceDetail { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 底薪金额(固定4000元)
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_BaseSalary")]
  72 + public decimal BaseSalary { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 提成合计(所有门店提成金额汇总)
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_TotalCommission")]
  78 + public decimal TotalCommission { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 在店天数
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_WorkingDays")]
  84 + public decimal WorkingDays { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 请假天数
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_LeaveDays")]
  90 + public decimal LeaveDays { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 核算应发工资(底薪 + 提成合计)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_CalculatedGrossSalary")]
  96 + public decimal CalculatedGrossSalary { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 最终应发工资
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_FinalGrossSalary")]
  102 + public decimal FinalGrossSalary { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 当月培训补贴
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")]
  108 + public decimal MonthlyTrainingSubsidy { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 当月交通补贴
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")]
  114 + public decimal MonthlyTransportSubsidy { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 上月培训补贴
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")]
  120 + public decimal LastMonthTrainingSubsidy { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 上月交通补贴
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")]
  126 + public decimal LastMonthTransportSubsidy { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 补贴合计
  130 + /// </summary>
  131 + [SugarColumn(ColumnName = "F_TotalSubsidy")]
  132 + public decimal TotalSubsidy { get; set; }
  133 +
  134 + /// <summary>
  135 + /// 缺卡扣款
  136 + /// </summary>
  137 + [SugarColumn(ColumnName = "F_MissingCard")]
  138 + public decimal MissingCard { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 迟到扣款
  142 + /// </summary>
  143 + [SugarColumn(ColumnName = "F_LateArrival")]
  144 + public decimal LateArrival { get; set; }
  145 +
  146 + /// <summary>
  147 + /// 请假扣款
  148 + /// </summary>
  149 + [SugarColumn(ColumnName = "F_LeaveDeduction")]
  150 + public decimal LeaveDeduction { get; set; }
  151 +
  152 + /// <summary>
  153 + /// 扣社保
  154 + /// </summary>
  155 + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")]
  156 + public decimal SocialInsuranceDeduction { get; set; }
  157 +
  158 + /// <summary>
  159 + /// 扣除奖励
  160 + /// </summary>
  161 + [SugarColumn(ColumnName = "F_RewardDeduction")]
  162 + public decimal RewardDeduction { get; set; }
  163 +
  164 + /// <summary>
  165 + /// 扣住宿费
  166 + /// </summary>
  167 + [SugarColumn(ColumnName = "F_AccommodationDeduction")]
  168 + public decimal AccommodationDeduction { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 扣学习期费用
  172 + /// </summary>
  173 + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")]
  174 + public decimal StudyPeriodDeduction { get; set; }
  175 +
  176 + /// <summary>
  177 + /// 扣工作服费用
  178 + /// </summary>
  179 + [SugarColumn(ColumnName = "F_WorkClothesDeduction")]
  180 + public decimal WorkClothesDeduction { get; set; }
  181 +
  182 + /// <summary>
  183 + /// 扣款合计
  184 + /// </summary>
  185 + [SugarColumn(ColumnName = "F_TotalDeduction")]
  186 + public decimal TotalDeduction { get; set; }
  187 +
  188 + /// <summary>
  189 + /// 发奖金
  190 + /// </summary>
  191 + [SugarColumn(ColumnName = "F_Bonus")]
  192 + public decimal Bonus { get; set; }
  193 +
  194 + /// <summary>
  195 + /// 退手机押金
  196 + /// </summary>
  197 + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")]
  198 + public decimal ReturnPhoneDeposit { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 退住宿押金
  202 + /// </summary>
  203 + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")]
  204 + public decimal ReturnAccommodationDeposit { get; set; }
  205 +
  206 + /// <summary>
  207 + /// 实发工资
  208 + /// </summary>
  209 + [SugarColumn(ColumnName = "F_ActualSalary")]
  210 + public decimal ActualSalary { get; set; }
  211 +
  212 + /// <summary>
  213 + /// 当月是否发放
  214 + /// </summary>
  215 + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")]
  216 + public string MonthlyPaymentStatus { get; set; }
  217 +
  218 + /// <summary>
  219 + /// 支付金额
  220 + /// </summary>
  221 + [SugarColumn(ColumnName = "F_PaidAmount")]
  222 + public decimal PaidAmount { get; set; }
  223 +
  224 + /// <summary>
  225 + /// 待支付金额
  226 + /// </summary>
  227 + [SugarColumn(ColumnName = "F_PendingAmount")]
  228 + public decimal PendingAmount { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 补发上月
  232 + /// </summary>
  233 + [SugarColumn(ColumnName = "F_LastMonthSupplement")]
  234 + public decimal LastMonthSupplement { get; set; }
  235 +
  236 + /// <summary>
  237 + /// 当月支付总额
  238 + /// </summary>
  239 + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")]
  240 + public decimal MonthlyTotalPayment { get; set; }
  241 +
  242 + /// <summary>
  243 + /// 是否锁定(0=未锁定,1=已锁定)
  244 + /// </summary>
  245 + [SugarColumn(ColumnName = "F_IsLocked")]
  246 + public int IsLocked { get; set; }
  247 +
  248 + /// <summary>
  249 + /// 创建时间
  250 + /// </summary>
  251 + [SugarColumn(ColumnName = "F_CreateTime")]
  252 + public DateTime CreateTime { get; set; }
  253 +
  254 + /// <summary>
  255 + /// 更新时间
  256 + /// </summary>
  257 + [SugarColumn(ColumnName = "F_UpdateTime")]
  258 + public DateTime UpdateTime { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 创建人
  262 + /// </summary>
  263 + [SugarColumn(ColumnName = "F_CreateUser")]
  264 + public string CreateUser { get; set; }
  265 +
  266 + /// <summary>
  267 + /// 更新人
  268 + /// </summary>
  269 + [SugarColumn(ColumnName = "F_UpdateUser")]
  270 + public string UpdateUser { get; set; }
  271 + }
  272 +}
  273 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_jksyj/LqHytkJksyjEntity.cs
... ... @@ -143,5 +143,11 @@ namespace NCC.Extend.Entitys.lq_hytk_jksyj
143 143 /// </summary>
144 144 [SugarColumn(ColumnName = "F_PerformanceType")]
145 145 public string PerformanceType { get; set; }
  146 +
  147 + /// <summary>
  148 + /// 科美类型(来源:lq_xmzl.F_BeautyType)
  149 + /// </summary>
  150 + [SugarColumn(ColumnName = "F_BeautyType")]
  151 + public string BeautyType { get; set; }
146 152 }
147 153 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_kjbsyj/LqHytkKjbsyjEntity.cs
... ... @@ -138,5 +138,11 @@ namespace NCC.Extend.Entitys.lq_hytk_kjbsyj
138 138 /// </summary>
139 139 [SugarColumn(ColumnName = "F_PerformanceType")]
140 140 public string PerformanceType { get; set; }
  141 +
  142 + /// <summary>
  143 + /// 科美类型(来源:lq_xmzl.F_BeautyType)
  144 + /// </summary>
  145 + [SugarColumn(ColumnName = "F_BeautyType")]
  146 + public string BeautyType { get; set; }
141 147 }
142 148 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_mx/LqHytkMxEntity.cs
... ... @@ -126,5 +126,11 @@ namespace NCC.Extend.Entitys.lq_hytk_mx
126 126 /// </summary>
127 127 [SugarColumn(ColumnName = "F_PerformanceType")]
128 128 public string PerformanceType { get; set; }
  129 +
  130 + /// <summary>
  131 + /// 科美类型(来源:lq_xmzl.F_BeautyType)
  132 + /// </summary>
  133 + [SugarColumn(ColumnName = "F_BeautyType")]
  134 + public string BeautyType { get; set; }
129 135 }
130 136 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs
... ... @@ -60,3 +60,7 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_application_node
60 60  
61 61  
62 62  
  63 +
  64 +
  65 +
  66 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs
... ... @@ -78,3 +78,7 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_approval_record
78 78  
79 79  
80 80  
  81 +
  82 +
  83 +
  84 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_jksyj/LqKdJksyjEntity.cs
... ... @@ -106,5 +106,11 @@ namespace NCC.Extend.Entitys.lq_kd_jksyj
106 106 /// </summary>
107 107 [SugarColumn(ColumnName = "F_PerformanceType")]
108 108 public string PerformanceType { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 科美类型(来源:lq_xmzl.F_BeautyType)
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_BeautyType")]
  114 + public string BeautyType { get; set; }
109 115 }
110 116 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_kjbsyj/LqKdKjbsyjEntity.cs
... ... @@ -106,5 +106,11 @@ namespace NCC.Extend.Entitys.lq_kd_kjbsyj
106 106 /// </summary>
107 107 [SugarColumn(ColumnName = "F_PerformanceType")]
108 108 public string PerformanceType { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 科美类型(来源:lq_xmzl.F_BeautyType)
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_BeautyType")]
  114 + public string BeautyType { get; set; }
109 115 }
110 116 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_pxmx/LqKdPxmxEntity.cs
... ... @@ -119,5 +119,12 @@ namespace NCC.Extend.Entitys.lq_kd_pxmx
119 119 /// </summary>
120 120 [SugarColumn(ColumnName = "F_PerformanceType")]
121 121 public string PerformanceType { get; set; }
  122 +
  123 + /// <summary>
  124 + /// 科美类型(来源:lq_xmzl.F_BeautyType)
  125 + /// </summary>
  126 + [SugarColumn(ColumnName = "F_BeautyType")]
  127 + public string BeautyType { get; set; }
  128 +
122 129 }
123 130 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_major_project_teacher_salary_statistics/LqMajorProjectTeacherSalaryStatisticsEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_major_project_teacher_salary_statistics
  6 +{
  7 + /// <summary>
  8 + /// 大项目部老师工资统计表
  9 + /// </summary>
  10 + [SugarTable("lq_major_project_teacher_salary_statistics")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqMajorProjectTeacherSalaryStatisticsEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 统计月份(YYYYMM)
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_StatisticsMonth", Length = 6)]
  24 + public string StatisticsMonth { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 门店ID
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_StoreId")]
  30 + public string StoreId { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 门店名称
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_StoreName")]
  36 + public string StoreName { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 核算岗位
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_Position")]
  42 + public string Position { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 员工姓名
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_EmployeeName")]
  48 + public string EmployeeName { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 员工ID
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_EmployeeId")]
  54 + public string EmployeeId { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 员工账号
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_EmployeeAccount")]
  60 + public string EmployeeAccount { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 开单业绩
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_OrderAchievement", DecimalDigits = 2)]
  66 + public decimal OrderAchievement { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 消耗业绩
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_ConsumeAchievement", DecimalDigits = 2)]
  72 + public decimal ConsumeAchievement { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 退卡业绩
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_RefundAchievement", DecimalDigits = 2)]
  78 + public decimal RefundAchievement { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 总业绩(开单业绩 + 消耗业绩 + 退卡业绩)
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_TotalPerformance", DecimalDigits = 2)]
  84 + public decimal TotalPerformance { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 底薪金额(固定3000元)
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_BaseSalary", DecimalDigits = 2)]
  90 + public decimal BaseSalary { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 业绩提成比例(百分比,如2.00表示2%,0.00表示无提成)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_PerformanceCommissionRate", DecimalDigits = 4)]
  96 + public decimal PerformanceCommissionRate { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 业绩提成金额(总业绩 × 业绩提成比例,阶梯式计算)
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_PerformanceCommissionAmount", DecimalDigits = 2)]
  102 + public decimal PerformanceCommissionAmount { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 提成合计(业绩提成金额)
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_TotalCommission", DecimalDigits = 2)]
  108 + public decimal TotalCommission { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 手工费
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_HandworkFee", DecimalDigits = 2)]
  114 + public decimal HandworkFee { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 车补
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_TransportationAllowance", DecimalDigits = 2)]
  120 + public decimal TransportationAllowance { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 少休费
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_LessRest", DecimalDigits = 2)]
  126 + public decimal LessRest { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 全勤奖
  130 + /// </summary>
  131 + [SugarColumn(ColumnName = "F_FullAttendance", DecimalDigits = 2)]
  132 + public decimal FullAttendance { get; set; }
  133 +
  134 + /// <summary>
  135 + /// 在店天数
  136 + /// </summary>
  137 + [SugarColumn(ColumnName = "F_WorkingDays", DecimalDigits = 2)]
  138 + public decimal WorkingDays { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 请假天数
  142 + /// </summary>
  143 + [SugarColumn(ColumnName = "F_LeaveDays", DecimalDigits = 2)]
  144 + public decimal LeaveDays { get; set; }
  145 +
  146 + /// <summary>
  147 + /// 核算应发工资(底薪 + 提成合计 + 其他收入)
  148 + /// </summary>
  149 + [SugarColumn(ColumnName = "F_CalculatedGrossSalary", DecimalDigits = 2)]
  150 + public decimal CalculatedGrossSalary { get; set; }
  151 +
  152 + /// <summary>
  153 + /// 保底工资
  154 + /// </summary>
  155 + [SugarColumn(ColumnName = "F_GuaranteedSalary", DecimalDigits = 2)]
  156 + public decimal GuaranteedSalary { get; set; }
  157 +
  158 + /// <summary>
  159 + /// 保底请假扣款
  160 + /// </summary>
  161 + [SugarColumn(ColumnName = "F_GuaranteedLeaveDeduction", DecimalDigits = 2)]
  162 + public decimal GuaranteedLeaveDeduction { get; set; }
  163 +
  164 + /// <summary>
  165 + /// 保底底薪
  166 + /// </summary>
  167 + [SugarColumn(ColumnName = "F_GuaranteedBaseSalary", DecimalDigits = 2)]
  168 + public decimal GuaranteedBaseSalary { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 保底补差
  172 + /// </summary>
  173 + [SugarColumn(ColumnName = "F_GuaranteedSupplement", DecimalDigits = 2)]
  174 + public decimal GuaranteedSupplement { get; set; }
  175 +
  176 + /// <summary>
  177 + /// 最终应发工资(取核算应发工资和保底工资的较大值)
  178 + /// </summary>
  179 + [SugarColumn(ColumnName = "F_FinalGrossSalary", DecimalDigits = 2)]
  180 + public decimal FinalGrossSalary { get; set; }
  181 +
  182 + /// <summary>
  183 + /// 当月培训补贴
  184 + /// </summary>
  185 + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy", DecimalDigits = 2)]
  186 + public decimal MonthlyTrainingSubsidy { get; set; }
  187 +
  188 + /// <summary>
  189 + /// 当月交通补贴
  190 + /// </summary>
  191 + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy", DecimalDigits = 2)]
  192 + public decimal MonthlyTransportSubsidy { get; set; }
  193 +
  194 + /// <summary>
  195 + /// 上月培训补贴
  196 + /// </summary>
  197 + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy", DecimalDigits = 2)]
  198 + public decimal LastMonthTrainingSubsidy { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 上月交通补贴
  202 + /// </summary>
  203 + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy", DecimalDigits = 2)]
  204 + public decimal LastMonthTransportSubsidy { get; set; }
  205 +
  206 + /// <summary>
  207 + /// 补贴合计
  208 + /// </summary>
  209 + [SugarColumn(ColumnName = "F_TotalSubsidy", DecimalDigits = 2)]
  210 + public decimal TotalSubsidy { get; set; }
  211 +
  212 + /// <summary>
  213 + /// 缺卡扣款
  214 + /// </summary>
  215 + [SugarColumn(ColumnName = "F_MissingCard", DecimalDigits = 2)]
  216 + public decimal MissingCard { get; set; }
  217 +
  218 + /// <summary>
  219 + /// 迟到扣款
  220 + /// </summary>
  221 + [SugarColumn(ColumnName = "F_LateArrival", DecimalDigits = 2)]
  222 + public decimal LateArrival { get; set; }
  223 +
  224 + /// <summary>
  225 + /// 请假扣款
  226 + /// </summary>
  227 + [SugarColumn(ColumnName = "F_LeaveDeduction", DecimalDigits = 2)]
  228 + public decimal LeaveDeduction { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 扣社保
  232 + /// </summary>
  233 + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction", DecimalDigits = 2)]
  234 + public decimal SocialInsuranceDeduction { get; set; }
  235 +
  236 + /// <summary>
  237 + /// 扣除奖励
  238 + /// </summary>
  239 + [SugarColumn(ColumnName = "F_RewardDeduction", DecimalDigits = 2)]
  240 + public decimal RewardDeduction { get; set; }
  241 +
  242 + /// <summary>
  243 + /// 扣住宿费
  244 + /// </summary>
  245 + [SugarColumn(ColumnName = "F_AccommodationDeduction", DecimalDigits = 2)]
  246 + public decimal AccommodationDeduction { get; set; }
  247 +
  248 + /// <summary>
  249 + /// 扣学习期费用
  250 + /// </summary>
  251 + [SugarColumn(ColumnName = "F_StudyPeriodDeduction", DecimalDigits = 2)]
  252 + public decimal StudyPeriodDeduction { get; set; }
  253 +
  254 + /// <summary>
  255 + /// 扣工作服费用
  256 + /// </summary>
  257 + [SugarColumn(ColumnName = "F_WorkClothesDeduction", DecimalDigits = 2)]
  258 + public decimal WorkClothesDeduction { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 扣款合计
  262 + /// </summary>
  263 + [SugarColumn(ColumnName = "F_TotalDeduction", DecimalDigits = 2)]
  264 + public decimal TotalDeduction { get; set; }
  265 +
  266 + /// <summary>
  267 + /// 发奖金
  268 + /// </summary>
  269 + [SugarColumn(ColumnName = "F_Bonus", DecimalDigits = 2)]
  270 + public decimal Bonus { get; set; }
  271 +
  272 + /// <summary>
  273 + /// 退手机押金
  274 + /// </summary>
  275 + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit", DecimalDigits = 2)]
  276 + public decimal ReturnPhoneDeposit { get; set; }
  277 +
  278 + /// <summary>
  279 + /// 退住宿押金
  280 + /// </summary>
  281 + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit", DecimalDigits = 2)]
  282 + public decimal ReturnAccommodationDeposit { get; set; }
  283 +
  284 + /// <summary>
  285 + /// 实发工资(最终应发工资 - 扣款合计 + 补贴合计 + 奖金)
  286 + /// </summary>
  287 + [SugarColumn(ColumnName = "F_ActualSalary", DecimalDigits = 2)]
  288 + public decimal ActualSalary { get; set; }
  289 +
  290 + /// <summary>
  291 + /// 当月是否发放
  292 + /// </summary>
  293 + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")]
  294 + public string MonthlyPaymentStatus { get; set; }
  295 +
  296 + /// <summary>
  297 + /// 支付金额
  298 + /// </summary>
  299 + [SugarColumn(ColumnName = "F_PaidAmount", DecimalDigits = 2)]
  300 + public decimal PaidAmount { get; set; }
  301 +
  302 + /// <summary>
  303 + /// 待支付金额
  304 + /// </summary>
  305 + [SugarColumn(ColumnName = "F_PendingAmount", DecimalDigits = 2)]
  306 + public decimal PendingAmount { get; set; }
  307 +
  308 + /// <summary>
  309 + /// 补发上月
  310 + /// </summary>
  311 + [SugarColumn(ColumnName = "F_LastMonthSupplement", DecimalDigits = 2)]
  312 + public decimal LastMonthSupplement { get; set; }
  313 +
  314 + /// <summary>
  315 + /// 当月支付总额
  316 + /// </summary>
  317 + [SugarColumn(ColumnName = "F_MonthlyTotalPayment", DecimalDigits = 2)]
  318 + public decimal MonthlyTotalPayment { get; set; }
  319 +
  320 + /// <summary>
  321 + /// 是否锁定(0未锁定,1已锁定)
  322 + /// </summary>
  323 + [SugarColumn(ColumnName = "F_IsLocked")]
  324 + public int IsLocked { get; set; }
  325 +
  326 + /// <summary>
  327 + /// 是否离职(0=在职,1=离职)
  328 + /// </summary>
  329 + [SugarColumn(ColumnName = "F_IsTerminated")]
  330 + public int IsTerminated { get; set; }
  331 +
  332 + /// <summary>
  333 + /// 创建时间
  334 + /// </summary>
  335 + [SugarColumn(ColumnName = "F_CreateTime")]
  336 + public DateTime CreateTime { get; set; }
  337 +
  338 + /// <summary>
  339 + /// 更新时间
  340 + /// </summary>
  341 + [SugarColumn(ColumnName = "F_UpdateTime")]
  342 + public DateTime UpdateTime { get; set; }
  343 +
  344 + /// <summary>
  345 + /// 创建人
  346 + /// </summary>
  347 + [SugarColumn(ColumnName = "F_CreateUser")]
  348 + public string CreateUser { get; set; }
  349 +
  350 + /// <summary>
  351 + /// 更新人
  352 + /// </summary>
  353 + [SugarColumn(ColumnName = "F_UpdateUser")]
  354 + public string UpdateUser { get; set; }
  355 +
  356 + /// <summary>
  357 + /// 是否新店
  358 + /// </summary>
  359 + [SugarColumn(ColumnName = "F_IsNewStore")]
  360 + public string IsNewStore { get; set; }
  361 +
  362 + /// <summary>
  363 + /// 新店保护阶段
  364 + /// </summary>
  365 + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")]
  366 + public int NewStoreProtectionStage { get; set; }
  367 +
  368 + /// <summary>
  369 + /// 门店类型
  370 + /// </summary>
  371 + [SugarColumn(ColumnName = "F_StoreType")]
  372 + public int? StoreType { get; set; }
  373 +
  374 + /// <summary>
  375 + /// 门店类别
  376 + /// </summary>
  377 + [SugarColumn(ColumnName = "F_StoreCategory")]
  378 + public int? StoreCategory { get; set; }
  379 + }
  380 +}
  381 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_md_major_project_teacher_assignment/LqMdMajorProjectTeacherAssignmentEntity.cs
... ... @@ -42,6 +42,12 @@ namespace NCC.Extend.Entitys.lq_md_major_project_teacher_assignment
42 42 public string TeacherId { get; set; }
43 43  
44 44 /// <summary>
  45 + /// 教育部老师用户ID
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_EducationTeacherId")]
  48 + public string EducationTeacherId { get; set; }
  49 +
  50 + /// <summary>
45 51 /// 备注说明
46 52 /// </summary>
47 53 [SugarColumn(ColumnName = "F_Remark")]
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_jksyj/LqXhJksyjEntity.cs
... ... @@ -154,5 +154,11 @@ namespace NCC.Extend.Entitys.lq_xh_jksyj
154 154 /// </summary>
155 155 [SugarColumn(ColumnName = "F_PerformanceType")]
156 156 public string PerformanceType { get; set; }
  157 +
  158 + /// <summary>
  159 + /// 科美类型(来源:lq_xmzl.F_BeautyType)
  160 + /// </summary>
  161 + [SugarColumn(ColumnName = "F_BeautyType")]
  162 + public string BeautyType { get; set; }
157 163 }
158 164 }
159 165 \ No newline at end of file
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_kjbsyj/LqXhKjbsyjEntity.cs
... ... @@ -136,5 +136,11 @@ namespace NCC.Extend.Entitys.lq_xh_kjbsyj
136 136 /// </summary>
137 137 [SugarColumn(ColumnName = "F_PerformanceType")]
138 138 public string PerformanceType { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 科美类型(来源:lq_xmzl.F_BeautyType)
  142 + /// </summary>
  143 + [SugarColumn(ColumnName = "F_BeautyType")]
  144 + public string BeautyType { get; set; }
139 145 }
140 146 }
141 147 \ No newline at end of file
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_pxmx/LqXhPxmxEntity.cs
... ... @@ -112,5 +112,11 @@ namespace NCC.Extend.Entitys.lq_xh_pxmx
112 112 /// </summary>
113 113 [SugarColumn(ColumnName = "F_PerformanceType")]
114 114 public string PerformanceType { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 科技部归类
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_BeautyType")]
  120 + public string BeautyType { get; set; }
115 121 }
116 122 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/FileUrlMigrationService.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +using System.Linq;
  4 +using System.Text.RegularExpressions;
  5 +using System.Threading.Tasks;
  6 +using Microsoft.AspNetCore.Mvc;
  7 +using Microsoft.Extensions.Logging;
  8 +using NCC.Common.Extension;
  9 +using NCC.Dependency;
  10 +using NCC.DynamicApiController;
  11 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  12 +using NCC.FriendlyException;
  13 +using Newtonsoft.Json;
  14 +using Newtonsoft.Json.Linq;
  15 +using SqlSugar;
  16 +
  17 +namespace NCC.Extend
  18 +{
  19 + /// <summary>
  20 + /// 文件URL迁移服务 - 将本地文件路径转换为OSS路径
  21 + /// </summary>
  22 + [ApiDescriptionSettings(Tag = "数据迁移", Name = "FileUrlMigration", Order = 999)]
  23 + [Route("api/Extend/[controller]")]
  24 + public class FileUrlMigrationService : IDynamicApiController, ITransient
  25 + {
  26 + private readonly ISqlSugarRepository<LqKdKdjlbEntity> _repository;
  27 + private readonly SqlSugarScope _db;
  28 + private readonly ILogger<FileUrlMigrationService> _logger;
  29 +
  30 + private const string OSS_BASE_URL = "http://oss.lvqianmeiye.com/Files/SystemFile";
  31 + private const string OLD_URL_PREFIX = "/api/File/Image/annexpic/";
  32 + private const string OLD_OSS_URL = "http://oss.lvqianmeiye.com";
  33 + private const string NEW_OSS_URL = "https://lvqian-erip.oss-cn-chengdu.aliyuncs.com";
  34 +
  35 + /// <summary>
  36 + /// 初始化一个<see cref="FileUrlMigrationService"/>类型的新实例
  37 + /// </summary>
  38 + /// <param name="repository">SqlSugar仓储</param>
  39 + /// <param name="logger">日志记录器</param>
  40 + public FileUrlMigrationService(ISqlSugarRepository<LqKdKdjlbEntity> repository, ILogger<FileUrlMigrationService> logger)
  41 + {
  42 + _repository = repository;
  43 + _db = _repository.Context;
  44 + _logger = logger;
  45 + }
  46 +
  47 + /// <summary>
  48 + /// 迁移所有表的文件URL路径
  49 + /// </summary>
  50 + /// <remarks>
  51 + /// 将数据库中的文件URL从本地路径格式转换为OSS路径格式
  52 + ///
  53 + /// 处理的表和字段:
  54 + /// 1. lq_kd_kdjlb: scwj, hyqz, F_FIleUrl
  55 + /// 2. lq_xh_feedback: F_BeforeImage, F_AfterImage
  56 + /// 3. lq_hytk_hytk: F_FileUrl, F_SignatureFile
  57 + /// 4. lq_purchase_records: F_Attachment
  58 + ///
  59 + /// URL转换规则:
  60 + /// 原格式:/api/File/Image/annexpic/文件名
  61 + /// 新格式:http://oss.lvqianmeiye.com/Files/SystemFile/文件名
  62 + /// </remarks>
  63 + /// <returns>迁移结果统计</returns>
  64 + [HttpPost("MigrateAllFileUrls")]
  65 + public async Task<dynamic> MigrateAllFileUrls()
  66 + {
  67 + var startTime = DateTime.Now;
  68 + _logger.LogInformation("开始迁移文件URL路径...");
  69 +
  70 + var totalResults = new
  71 + {
  72 + lq_kd_kdjlb_scwj = await MigrateTableField("lq_kd_kdjlb", "scwj", "F_Id"),
  73 + lq_kd_kdjlb_hyqz = await MigrateTableField("lq_kd_kdjlb", "hyqz", "F_Id"),
  74 + lq_kd_kdjlb_F_FIleUrl = await MigrateTableField("lq_kd_kdjlb", "F_FIleUrl", "F_Id"),
  75 + lq_xh_feedback_F_BeforeImage = await MigrateTableField("lq_xh_feedback", "F_BeforeImage", "F_Id"),
  76 + lq_xh_feedback_F_AfterImage = await MigrateTableField("lq_xh_feedback", "F_AfterImage", "F_Id"),
  77 + lq_hytk_hytk_F_FileUrl = await MigrateTableField("lq_hytk_hytk", "F_FileUrl", "F_Id"),
  78 + lq_hytk_hytk_F_SignatureFile = await MigrateTableField("lq_hytk_hytk", "F_SignatureFile", "F_Id"),
  79 + lq_purchase_records_F_Attachment = await MigrateTableField("lq_purchase_records", "F_Attachment", "F_Id")
  80 + };
  81 +
  82 + var endTime = DateTime.Now;
  83 + var duration = (endTime - startTime).TotalSeconds;
  84 +
  85 + // 统计总数
  86 + var totalProcessed = 0;
  87 + var totalUpdated = 0;
  88 + var totalErrors = 0;
  89 +
  90 + var resultsList = new List<MigrationResult>
  91 + {
  92 + totalResults.lq_kd_kdjlb_scwj,
  93 + totalResults.lq_kd_kdjlb_hyqz,
  94 + totalResults.lq_kd_kdjlb_F_FIleUrl,
  95 + totalResults.lq_xh_feedback_F_BeforeImage,
  96 + totalResults.lq_xh_feedback_F_AfterImage,
  97 + totalResults.lq_hytk_hytk_F_FileUrl,
  98 + totalResults.lq_hytk_hytk_F_SignatureFile,
  99 + totalResults.lq_purchase_records_F_Attachment
  100 + };
  101 +
  102 + foreach (var result in resultsList)
  103 + {
  104 + totalProcessed += result.TotalProcessed;
  105 + totalUpdated += result.TotalUpdated;
  106 + totalErrors += result.TotalErrors;
  107 + }
  108 +
  109 + _logger.LogInformation($"文件URL迁移完成,耗时:{duration:F2}秒,总处理:{totalProcessed},总更新:{totalUpdated},总错误:{totalErrors}");
  110 +
  111 + return new
  112 + {
  113 + success = true,
  114 + duration = $"{duration:F2}秒",
  115 + summary = new
  116 + {
  117 + totalProcessed,
  118 + totalUpdated,
  119 + totalErrors
  120 + },
  121 + details = totalResults
  122 + };
  123 + }
  124 +
  125 + /// <summary>
  126 + /// 迁移指定表的指定字段
  127 + /// </summary>
  128 + private async Task<MigrationResult> MigrateTableField(string tableName, string fieldName, string primaryKey)
  129 + {
  130 + var result = new MigrationResult
  131 + {
  132 + TableName = tableName,
  133 + FieldName = fieldName
  134 + };
  135 +
  136 + try
  137 + {
  138 + _logger.LogInformation($"开始处理表 {tableName}.{fieldName}");
  139 +
  140 + // 查询需要处理的数据
  141 + var sql = $@"
  142 + SELECT {primaryKey} as Id, {fieldName} as FieldValue
  143 + FROM {tableName}
  144 + WHERE {fieldName} IS NOT NULL
  145 + AND {fieldName} != ''
  146 + AND {fieldName} != '[]'
  147 + AND {fieldName} LIKE '%{OLD_URL_PREFIX}%'";
  148 +
  149 + var records = await _db.Ado.SqlQueryAsync<dynamic>(sql);
  150 + result.TotalProcessed = records.Count;
  151 +
  152 + if (records.Count == 0)
  153 + {
  154 + _logger.LogInformation($"表 {tableName}.{fieldName} 没有需要处理的数据");
  155 + return result;
  156 + }
  157 +
  158 + _logger.LogInformation($"表 {tableName}.{fieldName} 找到 {records.Count} 条需要处理的数据");
  159 +
  160 + // 分批处理,每批100条
  161 + const int batchSize = 100;
  162 + var totalBatches = (int)Math.Ceiling((double)records.Count / batchSize);
  163 +
  164 + for (int batchIndex = 0; batchIndex < totalBatches; batchIndex++)
  165 + {
  166 + var batch = records.Skip(batchIndex * batchSize).Take(batchSize).ToList();
  167 + var updateList = new List<(string id, string newValue)>();
  168 +
  169 + foreach (var record in batch)
  170 + {
  171 + try
  172 + {
  173 + var id = record.Id?.ToString();
  174 + var fieldValue = record.FieldValue?.ToString();
  175 +
  176 + if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(fieldValue))
  177 + continue;
  178 +
  179 + var newValue = ConvertFileUrlJson(fieldValue);
  180 + if (newValue != null && newValue != fieldValue)
  181 + {
  182 + updateList.Add((id, newValue));
  183 + }
  184 + }
  185 + catch (Exception ex)
  186 + {
  187 + result.TotalErrors++;
  188 + _logger.LogError(ex, $"处理记录时出错,表:{tableName},字段:{fieldName},记录ID:{record.Id}");
  189 + }
  190 + }
  191 +
  192 + // 批量更新
  193 + if (updateList.Any())
  194 + {
  195 + try
  196 + {
  197 + _db.BeginTran();
  198 + foreach (var (id, newValue) in updateList)
  199 + {
  200 + // 使用参数化查询防止SQL注入
  201 + var updateSql = $"UPDATE `{tableName}` SET `{fieldName}` = @Value WHERE `{primaryKey}` = @Id";
  202 + await _db.Ado.ExecuteCommandAsync(updateSql, new { Value = newValue, Id = id });
  203 + }
  204 + _db.CommitTran();
  205 + result.TotalUpdated += updateList.Count;
  206 + _logger.LogInformation($"表 {tableName}.{fieldName} 批次 {batchIndex + 1} 成功更新 {updateList.Count} 条记录");
  207 + }
  208 + catch (Exception ex)
  209 + {
  210 + _db.RollbackTran();
  211 + result.TotalErrors += updateList.Count;
  212 + _logger.LogError(ex, $"批量更新时出错,表:{tableName},字段:{fieldName},批次:{batchIndex + 1}");
  213 + }
  214 + }
  215 +
  216 + _logger.LogInformation($"表 {tableName}.{fieldName} 批次 {batchIndex + 1}/{totalBatches} 处理完成");
  217 + }
  218 +
  219 + _logger.LogInformation($"表 {tableName}.{fieldName} 处理完成,处理:{result.TotalProcessed},更新:{result.TotalUpdated},错误:{result.TotalErrors}");
  220 + }
  221 + catch (Exception ex)
  222 + {
  223 + _logger.LogError(ex, $"处理表 {tableName}.{fieldName} 时发生错误");
  224 + result.TotalErrors = result.TotalProcessed;
  225 + }
  226 +
  227 + return result;
  228 + }
  229 +
  230 + /// <summary>
  231 + /// 转换文件URL JSON字符串
  232 + /// </summary>
  233 + private string ConvertFileUrlJson(string jsonString)
  234 + {
  235 + try
  236 + {
  237 + if (string.IsNullOrWhiteSpace(jsonString) || jsonString.Trim() == "[]")
  238 + return jsonString;
  239 +
  240 + // 解析JSON数组
  241 + var jsonArray = JArray.Parse(jsonString);
  242 + bool hasChanges = false;
  243 +
  244 + foreach (var item in jsonArray)
  245 + {
  246 + if (item is JObject obj && obj["url"] != null)
  247 + {
  248 + var url = obj["url"].ToString();
  249 + if (url.StartsWith(OLD_URL_PREFIX))
  250 + {
  251 + // 提取文件名
  252 + var fileName = url.Substring(OLD_URL_PREFIX.Length);
  253 + // 构建新的OSS URL
  254 + var newUrl = $"{OSS_BASE_URL}/{fileName}";
  255 + obj["url"] = newUrl;
  256 + hasChanges = true;
  257 + }
  258 + }
  259 + }
  260 +
  261 + return hasChanges ? jsonArray.ToString(Formatting.None) : jsonString;
  262 + }
  263 + catch (Exception ex)
  264 + {
  265 + _logger.LogError(ex, $"转换JSON时出错:{jsonString}");
  266 + return jsonString; // 转换失败时返回原值
  267 + }
  268 + }
  269 +
  270 + /// <summary>
  271 + /// 迁移所有表的OSS URL(将旧OSS地址替换为新OSS地址)
  272 + /// </summary>
  273 + /// <remarks>
  274 + /// 将数据库中的文件URL从旧OSS地址格式转换为新OSS地址格式
  275 + ///
  276 + /// 处理的表和字段:
  277 + /// 1. lq_kd_kdjlb: scwj, hyqz, F_FIleUrl
  278 + /// 2. lq_xh_feedback: F_BeforeImage, F_AfterImage
  279 + /// 3. lq_hytk_hytk: F_FileUrl, F_SignatureFile
  280 + /// 4. lq_purchase_records: F_Attachment
  281 + ///
  282 + /// URL转换规则:
  283 + /// 原格式:http://oss.lvqianmeiye.com/...
  284 + /// 新格式:https://lvqian-erip.oss-cn-chengdu.aliyuncs.com/...
  285 + /// </remarks>
  286 + /// <returns>迁移结果统计</returns>
  287 + [HttpPost("MigrateOssUrls")]
  288 + public async Task<dynamic> MigrateOssUrls()
  289 + {
  290 + var startTime = DateTime.Now;
  291 + _logger.LogInformation("开始迁移OSS URL路径...");
  292 +
  293 + var totalResults = new
  294 + {
  295 + lq_kd_kdjlb_scwj = await MigrateOssUrlInTableField("lq_kd_kdjlb", "scwj", "F_Id"),
  296 + lq_kd_kdjlb_hyqz = await MigrateOssUrlInTableField("lq_kd_kdjlb", "hyqz", "F_Id"),
  297 + lq_kd_kdjlb_F_FIleUrl = await MigrateOssUrlInTableField("lq_kd_kdjlb", "F_FIleUrl", "F_Id"),
  298 + lq_xh_feedback_F_BeforeImage = await MigrateOssUrlInTableField("lq_xh_feedback", "F_BeforeImage", "F_Id"),
  299 + lq_xh_feedback_F_AfterImage = await MigrateOssUrlInTableField("lq_xh_feedback", "F_AfterImage", "F_Id"),
  300 + lq_hytk_hytk_F_FileUrl = await MigrateOssUrlInTableField("lq_hytk_hytk", "F_FileUrl", "F_Id"),
  301 + lq_hytk_hytk_F_SignatureFile = await MigrateOssUrlInTableField("lq_hytk_hytk", "F_SignatureFile", "F_Id"),
  302 + lq_purchase_records_F_Attachment = await MigrateOssUrlInTableField("lq_purchase_records", "F_Attachment", "F_Id")
  303 + };
  304 +
  305 + var endTime = DateTime.Now;
  306 + var duration = (endTime - startTime).TotalSeconds;
  307 +
  308 + // 统计总数
  309 + var totalProcessed = 0;
  310 + var totalUpdated = 0;
  311 + var totalErrors = 0;
  312 +
  313 + var resultsList = new List<MigrationResult>
  314 + {
  315 + totalResults.lq_kd_kdjlb_scwj,
  316 + totalResults.lq_kd_kdjlb_hyqz,
  317 + totalResults.lq_kd_kdjlb_F_FIleUrl,
  318 + totalResults.lq_xh_feedback_F_BeforeImage,
  319 + totalResults.lq_xh_feedback_F_AfterImage,
  320 + totalResults.lq_hytk_hytk_F_FileUrl,
  321 + totalResults.lq_hytk_hytk_F_SignatureFile,
  322 + totalResults.lq_purchase_records_F_Attachment
  323 + };
  324 +
  325 + foreach (var result in resultsList)
  326 + {
  327 + totalProcessed += result.TotalProcessed;
  328 + totalUpdated += result.TotalUpdated;
  329 + totalErrors += result.TotalErrors;
  330 + }
  331 +
  332 + _logger.LogInformation($"OSS URL迁移完成,耗时:{duration:F2}秒,总处理:{totalProcessed},总更新:{totalUpdated},总错误:{totalErrors}");
  333 +
  334 + return new
  335 + {
  336 + success = true,
  337 + duration = $"{duration:F2}秒",
  338 + summary = new
  339 + {
  340 + totalProcessed,
  341 + totalUpdated,
  342 + totalErrors
  343 + },
  344 + details = totalResults
  345 + };
  346 + }
  347 +
  348 + /// <summary>
  349 + /// 迁移指定表的指定字段中的OSS URL
  350 + /// </summary>
  351 + private async Task<MigrationResult> MigrateOssUrlInTableField(string tableName, string fieldName, string primaryKey)
  352 + {
  353 + var result = new MigrationResult
  354 + {
  355 + TableName = tableName,
  356 + FieldName = fieldName
  357 + };
  358 +
  359 + try
  360 + {
  361 + _logger.LogInformation($"开始处理表 {tableName}.{fieldName} 的OSS URL迁移");
  362 +
  363 + // 查询需要处理的数据(包含旧OSS URL的记录)
  364 + var sql = $@"
  365 + SELECT {primaryKey} as Id, {fieldName} as FieldValue
  366 + FROM {tableName}
  367 + WHERE {fieldName} IS NOT NULL
  368 + AND {fieldName} != ''
  369 + AND {fieldName} != '[]'
  370 + AND {fieldName} LIKE '%{OLD_OSS_URL}%'";
  371 +
  372 + var records = await _db.Ado.SqlQueryAsync<dynamic>(sql);
  373 + result.TotalProcessed = records.Count;
  374 +
  375 + if (records.Count == 0)
  376 + {
  377 + _logger.LogInformation($"表 {tableName}.{fieldName} 没有需要处理的数据");
  378 + return result;
  379 + }
  380 +
  381 + _logger.LogInformation($"表 {tableName}.{fieldName} 找到 {records.Count} 条需要处理的数据");
  382 +
  383 + // 分批处理,每批100条
  384 + const int batchSize = 100;
  385 + var totalBatches = (int)Math.Ceiling((double)records.Count / batchSize);
  386 +
  387 + for (int batchIndex = 0; batchIndex < totalBatches; batchIndex++)
  388 + {
  389 + var batch = records.Skip(batchIndex * batchSize).Take(batchSize).ToList();
  390 + var updateList = new List<(string id, string newValue)>();
  391 +
  392 + foreach (var record in batch)
  393 + {
  394 + try
  395 + {
  396 + var id = record.Id?.ToString();
  397 + var fieldValue = record.FieldValue?.ToString();
  398 +
  399 + if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(fieldValue))
  400 + continue;
  401 +
  402 + var newValue = ReplaceOssUrlInJson(fieldValue);
  403 + if (newValue != null && newValue != fieldValue)
  404 + {
  405 + updateList.Add((id, newValue));
  406 + }
  407 + }
  408 + catch (Exception ex)
  409 + {
  410 + result.TotalErrors++;
  411 + _logger.LogError(ex, $"处理记录时出错,表:{tableName},字段:{fieldName},记录ID:{record.Id}");
  412 + }
  413 + }
  414 +
  415 + // 批量更新
  416 + if (updateList.Any())
  417 + {
  418 + try
  419 + {
  420 + _db.BeginTran();
  421 + foreach (var (id, newValue) in updateList)
  422 + {
  423 + // 使用参数化查询防止SQL注入
  424 + var updateSql = $"UPDATE `{tableName}` SET `{fieldName}` = @Value WHERE `{primaryKey}` = @Id";
  425 + await _db.Ado.ExecuteCommandAsync(updateSql, new { Value = newValue, Id = id });
  426 + }
  427 + _db.CommitTran();
  428 + result.TotalUpdated += updateList.Count;
  429 + _logger.LogInformation($"表 {tableName}.{fieldName} 批次 {batchIndex + 1} 成功更新 {updateList.Count} 条记录");
  430 + }
  431 + catch (Exception ex)
  432 + {
  433 + _db.RollbackTran();
  434 + result.TotalErrors += updateList.Count;
  435 + _logger.LogError(ex, $"批量更新时出错,表:{tableName},字段:{fieldName},批次:{batchIndex + 1}");
  436 + }
  437 + }
  438 +
  439 + _logger.LogInformation($"表 {tableName}.{fieldName} 批次 {batchIndex + 1}/{totalBatches} 处理完成");
  440 + }
  441 +
  442 + _logger.LogInformation($"表 {tableName}.{fieldName} 处理完成,处理:{result.TotalProcessed},更新:{result.TotalUpdated},错误:{result.TotalErrors}");
  443 + }
  444 + catch (Exception ex)
  445 + {
  446 + _logger.LogError(ex, $"处理表 {tableName}.{fieldName} 时发生错误");
  447 + result.TotalErrors = result.TotalProcessed;
  448 + }
  449 +
  450 + return result;
  451 + }
  452 +
  453 + /// <summary>
  454 + /// 替换JSON字符串中的OSS URL
  455 + /// </summary>
  456 + private string ReplaceOssUrlInJson(string jsonString)
  457 + {
  458 + try
  459 + {
  460 + if (string.IsNullOrWhiteSpace(jsonString) || jsonString.Trim() == "[]")
  461 + return jsonString;
  462 +
  463 + // 如果包含旧OSS URL,直接替换
  464 + if (jsonString.Contains(OLD_OSS_URL))
  465 + {
  466 + var newJsonString = jsonString.Replace(OLD_OSS_URL, NEW_OSS_URL);
  467 +
  468 + // 如果是JSON数组格式,需要解析并替换
  469 + try
  470 + {
  471 + var jsonArray = JArray.Parse(jsonString);
  472 + bool hasChanges = false;
  473 +
  474 + foreach (var item in jsonArray)
  475 + {
  476 + if (item is JObject obj && obj["url"] != null)
  477 + {
  478 + var url = obj["url"].ToString();
  479 + if (url.Contains(OLD_OSS_URL))
  480 + {
  481 + obj["url"] = url.Replace(OLD_OSS_URL, NEW_OSS_URL);
  482 + hasChanges = true;
  483 + }
  484 + }
  485 + }
  486 +
  487 + return hasChanges ? jsonArray.ToString(Formatting.None) : jsonString;
  488 + }
  489 + catch
  490 + {
  491 + // 如果不是JSON格式,直接替换字符串
  492 + return newJsonString;
  493 + }
  494 + }
  495 +
  496 + return jsonString;
  497 + }
  498 + catch (Exception ex)
  499 + {
  500 + _logger.LogError(ex, $"替换OSS URL时出错:{jsonString}");
  501 + return jsonString; // 转换失败时返回原值
  502 + }
  503 + }
  504 +
  505 + /// <summary>
  506 + /// 迁移结果
  507 + /// </summary>
  508 + private class MigrationResult
  509 + {
  510 + public string TableName { get; set; }
  511 + public string FieldName { get; set; }
  512 + public int TotalProcessed { get; set; }
  513 + public int TotalUpdated { get; set; }
  514 + public int TotalErrors { get; set; }
  515 + }
  516 + }
  517 +}
  518 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqBusinessUnitManagerSalaryService.cs 0 → 100644
  1 +using Microsoft.AspNetCore.Authorization;
  2 +using Microsoft.AspNetCore.Mvc;
  3 +using NCC.Common.Filter;
  4 +using NCC.Common.Helper;
  5 +using NCC.Dependency;
  6 +using NCC.DynamicApiController;
  7 +using NCC.Extend.Entitys.Dto.LqBusinessUnitManagerSalary;
  8 +using NCC.Extend.Entitys.lq_attendance_summary;
  9 +using NCC.Extend.Entitys.lq_hytk_hytk;
  10 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  11 +using NCC.Extend.Entitys.lq_md_general_manager_lifeline;
  12 +using NCC.Extend.Entitys.lq_md_target;
  13 +using NCC.Extend.Entitys.lq_mdxx;
  14 +using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics;
  15 +using NCC.System.Entitys.Permission;
  16 +using SqlSugar;
  17 +using System;
  18 +using System.Collections.Generic;
  19 +using System.Linq;
  20 +using System.Threading.Tasks;
  21 +using Yitter.IdGenerator;
  22 +
  23 +namespace NCC.Extend
  24 +{
  25 + /// <summary>
  26 + /// 事业部总经理/经理薪酬服务
  27 + /// </summary>
  28 + [ApiDescriptionSettings(Tag = "事业部总经理/经理薪酬服务", Name = "LqBusinessUnitManagerSalary", Order = 304)]
  29 + [Route("api/Extend/[controller]")]
  30 + public class LqBusinessUnitManagerSalaryService : IDynamicApiController, ITransient
  31 + {
  32 + private readonly ISqlSugarClient _db;
  33 +
  34 + /// <summary>
  35 + /// 初始化一个<see cref="LqBusinessUnitManagerSalaryService"/>类型的新实例
  36 + /// </summary>
  37 + public LqBusinessUnitManagerSalaryService(ISqlSugarClient db)
  38 + {
  39 + _db = db;
  40 + }
  41 +
  42 + /// <summary>
  43 + /// 获取事业部总经理/经理工资列表
  44 + /// </summary>
  45 + /// <param name="input">查询参数</param>
  46 + /// <returns>事业部总经理/经理工资分页列表</returns>
  47 + [HttpGet("business-unit-manager")]
  48 + public async Task<dynamic> GetBusinessUnitManagerSalaryList([FromQuery] BusinessUnitManagerSalaryInput input)
  49 + {
  50 + var monthStr = $"{input.Year}{input.Month:D2}";
  51 +
  52 + // 1. 检查当月是否已生成工资数据
  53 + var exists = await _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>()
  54 + .AnyAsync(x => x.StatisticsMonth == monthStr);
  55 +
  56 + // 2. 如果没有数据,则进行计算
  57 + if (!exists)
  58 + {
  59 + await CalculateBusinessUnitManagerSalary(input.Year, input.Month);
  60 + }
  61 +
  62 + // 3. 查询数据
  63 + var query = _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>()
  64 + .Where(x => x.StatisticsMonth == monthStr);
  65 +
  66 + if (input.ManagerType.HasValue)
  67 + {
  68 + query = query.Where(x => x.ManagerType == input.ManagerType.Value);
  69 + }
  70 +
  71 + if (!string.IsNullOrEmpty(input.Keyword))
  72 + {
  73 + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeAccount.Contains(input.Keyword));
  74 + }
  75 +
  76 + var list = await query.Select(x => new BusinessUnitManagerSalaryOutput
  77 + {
  78 + Id = x.Id,
  79 + StatisticsMonth = x.StatisticsMonth,
  80 + Position = x.Position,
  81 + EmployeeName = x.EmployeeName,
  82 + EmployeeId = x.EmployeeId,
  83 + EmployeeAccount = x.EmployeeAccount,
  84 + ManagerType = x.ManagerType,
  85 + IsTerminated = x.IsTerminated,
  86 + StorePerformanceDetail = x.StorePerformanceDetail,
  87 + BaseSalary = x.BaseSalary,
  88 + TotalCommission = x.TotalCommission,
  89 + WorkingDays = x.WorkingDays,
  90 + LeaveDays = x.LeaveDays,
  91 + CalculatedGrossSalary = x.CalculatedGrossSalary,
  92 + FinalGrossSalary = x.FinalGrossSalary,
  93 + MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy,
  94 + MonthlyTransportSubsidy = x.MonthlyTransportSubsidy,
  95 + LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy,
  96 + LastMonthTransportSubsidy = x.LastMonthTransportSubsidy,
  97 + TotalSubsidy = x.TotalSubsidy,
  98 + MissingCard = x.MissingCard,
  99 + LateArrival = x.LateArrival,
  100 + LeaveDeduction = x.LeaveDeduction,
  101 + SocialInsuranceDeduction = x.SocialInsuranceDeduction,
  102 + RewardDeduction = x.RewardDeduction,
  103 + AccommodationDeduction = x.AccommodationDeduction,
  104 + StudyPeriodDeduction = x.StudyPeriodDeduction,
  105 + WorkClothesDeduction = x.WorkClothesDeduction,
  106 + TotalDeduction = x.TotalDeduction,
  107 + Bonus = x.Bonus,
  108 + ReturnPhoneDeposit = x.ReturnPhoneDeposit,
  109 + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit,
  110 + ActualSalary = x.ActualSalary,
  111 + MonthlyPaymentStatus = x.MonthlyPaymentStatus,
  112 + PaidAmount = x.PaidAmount,
  113 + PendingAmount = x.PendingAmount,
  114 + LastMonthSupplement = x.LastMonthSupplement,
  115 + MonthlyTotalPayment = x.MonthlyTotalPayment,
  116 + IsLocked = x.IsLocked,
  117 + UpdateTime = x.UpdateTime
  118 + })
  119 + .ToPagedListAsync(input.currentPage, input.pageSize);
  120 +
  121 + return PageResult<BusinessUnitManagerSalaryOutput>.SqlSugarPageResult(list);
  122 + }
  123 +
  124 + /// <summary>
  125 + /// 计算事业部总经理/经理工资
  126 + /// </summary>
  127 + /// <param name="year">年份</param>
  128 + /// <param name="month">月份</param>
  129 + /// <returns></returns>
  130 + [HttpPost("calculate/business-unit-manager")]
  131 + public async Task CalculateBusinessUnitManagerSalary(int year, int month)
  132 + {
  133 + var startDate = new DateTime(year, month, 1);
  134 + var endDate = startDate.AddMonths(1).AddDays(-1);
  135 + var monthStr = $"{year}{month:D2}";
  136 +
  137 + // 1. 获取基础数据
  138 +
  139 + // 1.1 获取总经理/经理归属信息(从lq_md_general_manager_lifeline表)
  140 + var lifelineList = await _db.Queryable<LqMdGeneralManagerLifelineEntity>()
  141 + .Where(x => x.Month == monthStr)
  142 + .ToListAsync();
  143 +
  144 + if (!lifelineList.Any())
  145 + {
  146 + // 如果没有归属信息,直接返回
  147 + return;
  148 + }
  149 +
  150 + // 1.2 获取所有不重复的总经理/经理ID(确保所有总经理/经理都被计算)
  151 + var allManagerIds = lifelineList
  152 + .Where(x => !string.IsNullOrEmpty(x.GeneralManagerId))
  153 + .Select(x => x.GeneralManagerId)
  154 + .Distinct()
  155 + .ToList();
  156 +
  157 + // 1.3 按总经理/经理ID分组,获取每个总经理/经理管理的门店
  158 + var managerStoreDict = lifelineList
  159 + .Where(x => !string.IsNullOrEmpty(x.GeneralManagerId) && !string.IsNullOrEmpty(x.StoreId))
  160 + .GroupBy(x => x.GeneralManagerId)
  161 + .ToDictionary(g => g.Key, g => g.Select(x => x.StoreId).Distinct().ToList());
  162 +
  163 + // 1.4 门店信息 (lq_mdxx)
  164 + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
  165 + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x);
  166 +
  167 + // 1.5 门店生命线信息 (lq_md_target)
  168 + var targetList = await _db.Queryable<LqMdTargetEntity>()
  169 + .Where(x => x.Month == monthStr)
  170 + .ToListAsync();
  171 + var storeLifelineDict = targetList
  172 + .Where(x => !string.IsNullOrEmpty(x.StoreId))
  173 + .ToDictionary(x => x.StoreId, x => x.StoreLifeline);
  174 +
  175 + // 1.6 门店总业绩计算 (开单实付 - 退卡金额)
  176 + // 开单实付(从lq_kd_kdjlb表统计sfyj字段)
  177 + var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>()
  178 + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1)
  179 + .Select(x => new { x.Djmd, x.Sfyj })
  180 + .ToListAsync();
  181 + var storeBillingDict = storeBillingList
  182 + .Where(x => !string.IsNullOrEmpty(x.Djmd))
  183 + .GroupBy(x => x.Djmd)
  184 + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj));
  185 +
  186 + // 退卡金额(从lq_hytk_hytk表统计,使用F_ActualRefundAmount,如果没有则使用tkje)
  187 + var storeRefundList = await _db.Queryable<LqHytkHytkEntity>()
  188 + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
  189 + .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje })
  190 + .ToListAsync();
  191 + var storeRefundDict = storeRefundList
  192 + .Where(x => !string.IsNullOrEmpty(x.Md))
  193 + .GroupBy(x => x.Md)
  194 + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0));
  195 +
  196 + // 1.7 考勤数据 (lq_attendance_summary)
  197 + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
  198 + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
  199 + .ToListAsync();
  200 + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x);
  201 +
  202 + // 1.8 获取员工信息 (BASE_USER)
  203 + var userList = await _db.Queryable<UserEntity>()
  204 + .Where(x => allManagerIds.Contains(x.Id))
  205 + .Select(x => new { x.Id, x.RealName, x.Account, x.IsOnJob })
  206 + .ToListAsync();
  207 + var userDict = userList.ToDictionary(x => x.Id, x => x);
  208 +
  209 + // 2. 按总经理/经理聚合数据
  210 + var managerStats = new Dictionary<string, LqBusinessUnitManagerSalaryStatisticsEntity>();
  211 +
  212 + foreach (var managerId in allManagerIds)
  213 + {
  214 + if (string.IsNullOrEmpty(managerId))
  215 + {
  216 + continue;
  217 + }
  218 +
  219 + // 获取该总经理/经理的信息
  220 + var managerLifeline = lifelineList.FirstOrDefault(x => x.GeneralManagerId == managerId);
  221 + if (managerLifeline == null)
  222 + {
  223 + continue;
  224 + }
  225 +
  226 + // 2.1 创建工资统计对象
  227 + var salary = new LqBusinessUnitManagerSalaryStatisticsEntity
  228 + {
  229 + Id = YitIdHelper.NextId().ToString(),
  230 + StatisticsMonth = monthStr,
  231 + EmployeeId = managerId,
  232 + ManagerType = managerLifeline.ManagerType,
  233 + Position = managerLifeline.ManagerType == 1 ? "总经理" : "经理",
  234 + IsTerminated = 0,
  235 + CreateTime = DateTime.Now,
  236 + UpdateTime = DateTime.Now,
  237 + IsLocked = 0
  238 + };
  239 +
  240 + // 2.2 填充员工信息
  241 + if (userDict.ContainsKey(managerId))
  242 + {
  243 + var user = userDict[managerId];
  244 + salary.EmployeeName = user.RealName ?? "";
  245 + salary.EmployeeAccount = user.Account ?? "";
  246 + salary.IsTerminated = user.IsOnJob == 0 ? 1 : 0;
  247 + }
  248 +
  249 + // 2.3 考勤数据
  250 + var attendance = attendanceDict.ContainsKey(managerId) ? attendanceDict[managerId] : null;
  251 + salary.WorkingDays = attendance?.WorkDays ?? 0;
  252 + salary.LeaveDays = attendance?.LeaveDays ?? 0;
  253 +
  254 + // 2.4 计算底薪(固定4000元)
  255 + salary.BaseSalary = 4000m;
  256 +
  257 + // 2.5 遍历该总经理/经理管理的每个门店,计算提成
  258 + var storePerformanceDetails = new List<StorePerformanceDetail>();
  259 + decimal totalCommission = 0m;
  260 +
  261 + // 获取该总经理/经理管理的门店列表(如果没有管理的门店,则为空列表)
  262 + var managedStores = managerStoreDict.ContainsKey(managerId) ? managerStoreDict[managerId] : new List<string>();
  263 + foreach (var storeId in managedStores)
  264 + {
  265 + if (string.IsNullOrEmpty(storeId))
  266 + {
  267 + continue;
  268 + }
  269 +
  270 + // 获取该门店的提成阶梯设置
  271 + var storeLifelineSetting = lifelineList.FirstOrDefault(x => x.StoreId == storeId && x.GeneralManagerId == managerId);
  272 + if (storeLifelineSetting == null)
  273 + {
  274 + continue;
  275 + }
  276 +
  277 + // 获取门店信息
  278 + var storeName = storeDict.ContainsKey(storeId) ? storeDict[storeId].Dm ?? "" : "";
  279 +
  280 + // 获取门店生命线(提成门槛)
  281 + if (!storeLifelineDict.ContainsKey(storeId))
  282 + {
  283 + // 门店生命线未设置,跳过该门店
  284 + storePerformanceDetails.Add(new StorePerformanceDetail
  285 + {
  286 + StoreId = storeId,
  287 + StoreName = storeName,
  288 + StoreLifeline = 0,
  289 + BillingPerformance = 0,
  290 + RefundPerformance = 0,
  291 + StorePerformance = 0,
  292 + ReachedLifeline = false,
  293 + CommissionAmount = 0,
  294 + CalculationDetail = "门店生命线未设置,无法计算提成"
  295 + });
  296 + continue;
  297 + }
  298 +
  299 + var storeLifeline = storeLifelineDict[storeId];
  300 + if (storeLifeline <= 0)
  301 + {
  302 + // 门店生命线未设置或为0,跳过该门店
  303 + storePerformanceDetails.Add(new StorePerformanceDetail
  304 + {
  305 + StoreId = storeId,
  306 + StoreName = storeName,
  307 + StoreLifeline = 0,
  308 + BillingPerformance = 0,
  309 + RefundPerformance = 0,
  310 + StorePerformance = 0,
  311 + ReachedLifeline = false,
  312 + CommissionAmount = 0,
  313 + CalculationDetail = "门店生命线未设置或为0,无法计算提成"
  314 + });
  315 + continue;
  316 + }
  317 +
  318 + // 获取门店业绩
  319 + var billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0;
  320 + var refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0;
  321 + var storePerformance = billing - refund;
  322 +
  323 + // 判断是否达到门店生命线
  324 + var reachedLifeline = storePerformance >= storeLifeline;
  325 +
  326 + // 计算提成
  327 + decimal commissionAmount = 0m;
  328 + string calculationDetail = "";
  329 +
  330 + if (reachedLifeline)
  331 + {
  332 + // 达到门店生命线,使用提成阶梯计算提成(分段累进)
  333 + var commissionResult = CalculateStoreCommission(storePerformance, storeLifelineSetting);
  334 + commissionAmount = commissionResult.Amount;
  335 + calculationDetail = commissionResult.Detail;
  336 +
  337 + totalCommission += commissionAmount;
  338 + }
  339 + else
  340 + {
  341 + calculationDetail = $"业绩{storePerformance:N2}元,未达到门店生命线{storeLifeline:N2}元,无提成";
  342 + }
  343 +
  344 + // 添加到门店业绩明细
  345 + storePerformanceDetails.Add(new StorePerformanceDetail
  346 + {
  347 + StoreId = storeId,
  348 + StoreName = storeName,
  349 + StoreLifeline = storeLifeline,
  350 + BillingPerformance = billing,
  351 + RefundPerformance = refund,
  352 + StorePerformance = storePerformance,
  353 + ReachedLifeline = reachedLifeline,
  354 + Lifeline1 = storeLifelineSetting.Lifeline1,
  355 + CommissionRate1 = storeLifelineSetting.CommissionRate1,
  356 + Lifeline2 = storeLifelineSetting.Lifeline2,
  357 + CommissionRate2 = storeLifelineSetting.CommissionRate2,
  358 + Lifeline3 = storeLifelineSetting.Lifeline3,
  359 + CommissionRate3 = storeLifelineSetting.CommissionRate3,
  360 + CommissionAmount = commissionAmount,
  361 + CalculationDetail = calculationDetail
  362 + });
  363 + }
  364 +
  365 + // 2.6 保存门店业绩明细(JSON格式)
  366 + salary.StorePerformanceDetail = storePerformanceDetails.ToJson();
  367 +
  368 + // 2.7 提成合计
  369 + salary.TotalCommission = totalCommission;
  370 +
  371 + // 2.8 计算应发工资
  372 + salary.CalculatedGrossSalary = salary.BaseSalary + salary.TotalCommission;
  373 + salary.FinalGrossSalary = salary.CalculatedGrossSalary;
  374 +
  375 + // 2.9 初始化其他字段(默认值为0)
  376 + salary.MonthlyTrainingSubsidy = 0;
  377 + salary.MonthlyTransportSubsidy = 0;
  378 + salary.LastMonthTrainingSubsidy = 0;
  379 + salary.LastMonthTransportSubsidy = 0;
  380 + salary.TotalSubsidy = 0;
  381 + salary.MissingCard = 0;
  382 + salary.LateArrival = 0;
  383 + salary.LeaveDeduction = 0;
  384 + salary.SocialInsuranceDeduction = 0;
  385 + salary.RewardDeduction = 0;
  386 + salary.AccommodationDeduction = 0;
  387 + salary.StudyPeriodDeduction = 0;
  388 + salary.WorkClothesDeduction = 0;
  389 + salary.TotalDeduction = 0;
  390 + salary.Bonus = 0;
  391 + salary.ReturnPhoneDeposit = 0;
  392 + salary.ReturnAccommodationDeposit = 0;
  393 + salary.ActualSalary = salary.FinalGrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus;
  394 + salary.MonthlyPaymentStatus = "未发放";
  395 + salary.PaidAmount = 0;
  396 + salary.PendingAmount = salary.ActualSalary;
  397 + salary.LastMonthSupplement = 0;
  398 + salary.MonthlyTotalPayment = 0;
  399 +
  400 + managerStats[managerId] = salary;
  401 + }
  402 +
  403 + // 3. 保存数据
  404 + if (managerStats.Any())
  405 + {
  406 + // 先删除当月旧数据 (防止重复)
  407 + await _db.Deleteable<LqBusinessUnitManagerSalaryStatisticsEntity>()
  408 + .Where(x => x.StatisticsMonth == monthStr)
  409 + .ExecuteCommandAsync();
  410 +
  411 + await _db.Insertable(managerStats.Values.ToList()).ExecuteCommandAsync();
  412 + }
  413 + }
  414 +
  415 + /// <summary>
  416 + /// 计算门店提成(分段累进)
  417 + /// </summary>
  418 + /// <param name="storePerformance">门店业绩</param>
  419 + /// <param name="lifelineSetting">提成阶梯设置</param>
  420 + /// <returns>提成金额和计算说明</returns>
  421 + private (decimal Amount, string Detail) CalculateStoreCommission(decimal storePerformance, LqMdGeneralManagerLifelineEntity lifelineSetting)
  422 + {
  423 + // 验证提成阶梯1和提成比例1必须设置
  424 + if (lifelineSetting.Lifeline1 <= 0 || lifelineSetting.CommissionRate1 <= 0)
  425 + {
  426 + return (0m, "提成阶梯1或提成比例1未设置,无法计算提成");
  427 + }
  428 +
  429 + decimal commissionAmount = 0m;
  430 + string detail = "";
  431 +
  432 + var lifeline1 = lifelineSetting.Lifeline1;
  433 + var rate1 = lifelineSetting.CommissionRate1;
  434 + var lifeline2 = lifelineSetting.Lifeline2 ?? 0;
  435 + var rate2 = lifelineSetting.CommissionRate2 ?? 0;
  436 + var lifeline3 = lifelineSetting.Lifeline3 ?? 0;
  437 + var rate3 = lifelineSetting.CommissionRate3 ?? 0;
  438 +
  439 + // 分段累进计算
  440 + if (storePerformance <= lifeline1)
  441 + {
  442 + // 业绩 ≤ 提成阶梯1
  443 + commissionAmount = storePerformance * (rate1 / 100m);
  444 + detail = $"业绩{storePerformance:N2}元,≤ 提成阶梯1({lifeline1:N2}元),提成 = {storePerformance:N2} × {rate1}% = {commissionAmount:N2}元";
  445 + }
  446 + else if (lifeline2 > 0 && storePerformance <= lifeline2)
  447 + {
  448 + // 提成阶梯1 < 业绩 ≤ 提成阶梯2
  449 + var part1 = lifeline1 * (rate1 / 100m);
  450 + var part2 = (storePerformance - lifeline1) * (rate2 / 100m);
  451 + commissionAmount = part1 + part2;
  452 + detail = $"业绩{storePerformance:N2}元,> 提成阶梯1({lifeline1:N2}元) 且 ≤ 提成阶梯2({lifeline2:N2}元),提成 = {lifeline1:N2} × {rate1}% + ({storePerformance:N2} - {lifeline1:N2}) × {rate2}% = {part1:N2} + {part2:N2} = {commissionAmount:N2}元";
  453 + }
  454 + else if (lifeline3 > 0 && storePerformance <= lifeline3)
  455 + {
  456 + // 提成阶梯2 < 业绩 ≤ 提成阶梯3
  457 + var part1 = lifeline1 * (rate1 / 100m);
  458 + var part2 = (lifeline2 - lifeline1) * (rate2 / 100m);
  459 + var part3 = (storePerformance - lifeline2) * (rate3 / 100m);
  460 + commissionAmount = part1 + part2 + part3;
  461 + detail = $"业绩{storePerformance:N2}元,> 提成阶梯2({lifeline2:N2}元) 且 ≤ 提成阶梯3({lifeline3:N2}元),提成 = {lifeline1:N2} × {rate1}% + ({lifeline2:N2} - {lifeline1:N2}) × {rate2}% + ({storePerformance:N2} - {lifeline2:N2}) × {rate3}% = {part1:N2} + {part2:N2} + {part3:N2} = {commissionAmount:N2}元";
  462 + }
  463 + else if (lifeline3 > 0)
  464 + {
  465 + // 业绩 > 提成阶梯3
  466 + var part1 = lifeline1 * (rate1 / 100m);
  467 + var part2 = (lifeline2 - lifeline1) * (rate2 / 100m);
  468 + var part3 = (lifeline3 - lifeline2) * (rate3 / 100m);
  469 + var part4 = (storePerformance - lifeline3) * (rate3 / 100m);
  470 + commissionAmount = part1 + part2 + part3 + part4;
  471 + detail = $"业绩{storePerformance:N2}元,> 提成阶梯3({lifeline3:N2}元),提成 = {lifeline1:N2} × {rate1}% + ({lifeline2:N2} - {lifeline1:N2}) × {rate2}% + ({lifeline3:N2} - {lifeline2:N2}) × {rate3}% + ({storePerformance:N2} - {lifeline3:N2}) × {rate3}% = {part1:N2} + {part2:N2} + {part3:N2} + {part4:N2} = {commissionAmount:N2}元";
  472 + }
  473 + else if (lifeline2 > 0)
  474 + {
  475 + // 提成阶梯3未设置,业绩 > 提成阶梯2,按提成比例2计算超出部分
  476 + var part1 = lifeline1 * (rate1 / 100m);
  477 + var part2 = (storePerformance - lifeline1) * (rate2 / 100m);
  478 + commissionAmount = part1 + part2;
  479 + detail = $"业绩{storePerformance:N2}元,> 提成阶梯2({lifeline2:N2}元),提成阶梯3未设置,提成 = {lifeline1:N2} × {rate1}% + ({storePerformance:N2} - {lifeline1:N2}) × {rate2}% = {part1:N2} + {part2:N2} = {commissionAmount:N2}元";
  480 + }
  481 + else
  482 + {
  483 + // 只有提成阶梯1,业绩 > 提成阶梯1,按提成比例1计算
  484 + commissionAmount = storePerformance * (rate1 / 100m);
  485 + detail = $"业绩{storePerformance:N2}元,> 提成阶梯1({lifeline1:N2}元),提成阶梯2未设置,提成 = {storePerformance:N2} × {rate1}% = {commissionAmount:N2}元";
  486 + }
  487 +
  488 + return (commissionAmount, detail);
  489 + }
  490 +
  491 + /// <summary>
  492 + /// 门店业绩明细(用于JSON序列化)
  493 + /// </summary>
  494 + private class StorePerformanceDetail
  495 + {
  496 + public string StoreId { get; set; }
  497 + public string StoreName { get; set; }
  498 + public decimal StoreLifeline { get; set; }
  499 + public decimal BillingPerformance { get; set; }
  500 + public decimal RefundPerformance { get; set; }
  501 + public decimal StorePerformance { get; set; }
  502 + public bool ReachedLifeline { get; set; }
  503 + public decimal Lifeline1 { get; set; }
  504 + public decimal CommissionRate1 { get; set; }
  505 + public decimal? Lifeline2 { get; set; }
  506 + public decimal? CommissionRate2 { get; set; }
  507 + public decimal? Lifeline3 { get; set; }
  508 + public decimal? CommissionRate3 { get; set; }
  509 + public decimal CommissionAmount { get; set; }
  510 + public string CalculationDetail { get; set; }
  511 + }
  512 + }
  513 +}
  514 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs
... ... @@ -355,6 +355,7 @@ namespace NCC.Extend.LqHytkHytk
355 355 IsEffective = StatusEnum.有效.GetHashCode(),
356 356 ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
357 357 PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "",
  358 + BeautyType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.BeautyType).FirstAsync(),
358 359 };
359 360 allMxEntities.Add(lqHytkMxEntity);
360 361  
... ... @@ -384,7 +385,8 @@ namespace NCC.Extend.LqHytkHytk
384 385 ItemId = lqHytkMxEntity.Px,
385 386 StoreId = newEntity.Md,
386 387 ItemName = lqHytkMxEntity.Pxmc,
387   - PerformanceType = lqHytkMxEntity.PerformanceType
  388 + PerformanceType = lqHytkMxEntity.PerformanceType,
  389 + BeautyType = lqHytkMxEntity.BeautyType,
388 390 }
389 391 );
390 392 }
... ... @@ -416,7 +418,8 @@ namespace NCC.Extend.LqHytkHytk
416 418 ItemId = lqHytkMxEntity.Px,
417 419 StoreId = newEntity.Md,
418 420 ItemName = lqHytkMxEntity.Pxmc,
419   - PerformanceType = lqHytkMxEntity.PerformanceType
  421 + PerformanceType = lqHytkMxEntity.PerformanceType,
  422 + BeautyType = lqHytkMxEntity.BeautyType,
420 423 }
421 424 );
422 425 }
... ... @@ -517,6 +520,7 @@ namespace NCC.Extend.LqHytkHytk
517 520 TotalPrice = item.F_TotalPrice ?? (item.pxjg * (item.F_ProjectNumber ?? 1)),
518 521 ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
519 522 PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "",
  523 + BeautyType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.BeautyType).FirstAsync(),
520 524 };
521 525 allMxEntities.Add(lqHytkMxEntity);
522 526  
... ... @@ -545,7 +549,8 @@ namespace NCC.Extend.LqHytkHytk
545 549 ItemId = lqHytkMxEntity.Px,
546 550 StoreId = entity.Md,
547 551 ItemName = lqHytkMxEntity.Pxmc,
548   - PerformanceType = lqHytkMxEntity.PerformanceType
  552 + PerformanceType = lqHytkMxEntity.PerformanceType,
  553 + BeautyType = lqHytkMxEntity.BeautyType,
549 554 }
550 555 );
551 556 }
... ... @@ -575,7 +580,8 @@ namespace NCC.Extend.LqHytkHytk
575 580 ItemId = lqHytkMxEntity.Px,
576 581 StoreId = entity.Md,
577 582 ItemName = lqHytkMxEntity.Pxmc,
578   - PerformanceType = lqHytkMxEntity.PerformanceType
  583 + PerformanceType = lqHytkMxEntity.PerformanceType,
  584 + BeautyType = lqHytkMxEntity.BeautyType,
579 585 }
580 586 );
581 587 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
... ... @@ -538,7 +538,7 @@ namespace NCC.Extend
538 538 {
539 539 var sidx = input.sidx == null ? "id" : input.sidx;
540 540  
541   - // 查询使用记录信息,关联产品表
  541 + // 查询使用记录信息,关联产品表(使用Queryable<T1, T2>语法,与GetBatchInfoAsync保持一致)
542 542 var data = await _db.Queryable<LqInventoryUsageEntity, LqProductEntity>((u, product) => u.ProductId == product.Id)
543 543 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product) => u.ProductId == input.ProductId)
544 544 .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product) => u.StoreId == input.StoreId)
... ... @@ -562,6 +562,8 @@ namespace NCC.Extend
562 562 totalAmount = u.TotalAmount, // 总价(单价 × 数量)
563 563 relatedConsumeId = u.RelatedConsumeId,
564 564 usageBatchId = u.UsageBatchId,
  565 + approvalStatus = null, // 审批状态(稍后在内存中补充)
  566 + isReceived = null, // 是否已领取(稍后在内存中补充)
565 567 createUser = u.CreateUser,
566 568 createUserName = "",
567 569 createTime = u.CreateTime,
... ... @@ -574,6 +576,35 @@ namespace NCC.Extend
574 576 .OrderBy(sidx + " " + input.sort)
575 577 .ToPagedListAsync(input.currentPage, input.pageSize);
576 578  
  579 + // 补充审批状态和是否已领取信息(通过usageBatchId关联申请表)
  580 + if (data.list.Any())
  581 + {
  582 + var batchIds = data.list.Where(x => !string.IsNullOrEmpty(x.usageBatchId))
  583 + .Select(x => x.usageBatchId)
  584 + .Distinct()
  585 + .ToList();
  586 +
  587 + if (batchIds.Any())
  588 + {
  589 + var applicationDict = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  590 + .Where(x => batchIds.Contains(x.UsageBatchId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  591 + .Select(x => new { x.UsageBatchId, x.ApprovalStatus, x.IsReceived })
  592 + .ToListAsync();
  593 +
  594 + var applicationLookup = applicationDict.ToDictionary(x => x.UsageBatchId, x => new { x.ApprovalStatus, x.IsReceived });
  595 +
  596 + foreach (var item in data.list)
  597 + {
  598 + if (!string.IsNullOrEmpty(item.usageBatchId) && applicationLookup.ContainsKey(item.usageBatchId))
  599 + {
  600 + var appInfo = applicationLookup[item.usageBatchId];
  601 + item.approvalStatus = appInfo.ApprovalStatus;
  602 + item.isReceived = appInfo.IsReceived;
  603 + }
  604 + }
  605 + }
  606 + }
  607 +
577 608 // 补充用户信息
578 609 var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser })
579 610 .Where(x => !string.IsNullOrEmpty(x))
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -934,6 +934,7 @@ namespace NCC.Extend.LqKdKdjlb
934 934 ActivityId = input.activityId,
935 935 ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
936 936 PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "",
  937 + BeautyType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.BeautyType).FirstAsync(),
937 938 };
938 939 allPxmxEntities.Add(lqKdPxmxEntity);
939 940 // 收集该品项关联的健康师业绩
... ... @@ -960,7 +961,8 @@ namespace NCC.Extend.LqKdKdjlb
960 961 ItemId = lqKdPxmxEntity.Px,
961 962 ItemName = lqKdPxmxEntity.Pxmc,
962 963 StoreId = entity.Djmd,
963   - PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "",
  964 + PerformanceType = lqKdPxmxEntity.PerformanceType,
  965 + BeautyType = lqKdPxmxEntity.BeautyType,
964 966 });
965 967 }
966 968 }
... ... @@ -986,7 +988,8 @@ namespace NCC.Extend.LqKdKdjlb
986 988 ItemId = lqKdPxmxEntity.Px,
987 989 ItemName = lqKdPxmxEntity.Pxmc,
988 990 StoreId = entity.Djmd,
989   - PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "",
  991 + PerformanceType = lqKdPxmxEntity.PerformanceType,
  992 + BeautyType = lqKdPxmxEntity.BeautyType,
990 993 }
991 994 );
992 995 }
... ... @@ -1249,7 +1252,7 @@ namespace NCC.Extend.LqKdKdjlb
1249 1252 ItemId = item.ItemId,
1250 1253 IsEffective = StatusEnum.有效.GetHashCode(), // 设置为有效
1251 1254 CreateTime = DateTime.Now, // 设置创建时间
1252   - ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.DeductId).Select(x => x.Qt2).FirstAsync(),
  1255 + ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.DeductId).Select(x => x.Qt2).FirstAsync()
1253 1256 };
1254 1257 allDeductEntities.Add(lqKdDeductEntity);
1255 1258 }
... ... @@ -1277,6 +1280,7 @@ namespace NCC.Extend.LqKdKdjlb
1277 1280 ActivityId = input.activityId,
1278 1281 ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
1279 1282 PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "",
  1283 + BeautyType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.BeautyType).FirstAsync(),
1280 1284 };
1281 1285 allPxmxEntities.Add(lqKdPxmxEntity);
1282 1286  
... ... @@ -1304,6 +1308,7 @@ namespace NCC.Extend.LqKdKdjlb
1304 1308 StoreId = entity.Djmd,
1305 1309 ItemName = lqKdPxmxEntity.Pxmc,
1306 1310 PerformanceType = lqKdPxmxEntity.PerformanceType,
  1311 + BeautyType = lqKdPxmxEntity.BeautyType,
1307 1312 });
1308 1313 }
1309 1314 }
... ... @@ -1330,6 +1335,7 @@ namespace NCC.Extend.LqKdKdjlb
1330 1335 StoreId = entity.Djmd,
1331 1336 ItemName = lqKdPxmxEntity.Pxmc,
1332 1337 PerformanceType = lqKdPxmxEntity.PerformanceType,
  1338 + BeautyType = lqKdPxmxEntity.BeautyType,
1333 1339 });
1334 1340 }
1335 1341 }
... ... @@ -2878,6 +2884,7 @@ namespace NCC.Extend.LqKdKdjlb
2878 2884 IsEffective = StatusEnum.有效.GetHashCode(),
2879 2885 ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == refundPxmxEntity.Px).Select(x => x.Qt2).FirstAsync(),
2880 2886 PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == refundPxmxEntity.Px).Select(x => x.Fl3).FirstAsync() ?? "",
  2887 + BeautyType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == refundPxmxEntity.Px).Select(x => x.BeautyType).FirstAsync(),
2881 2888 };
2882 2889 refundMxEntities.Add(refundMxEntity);
2883 2890 var refundKdyjEntities = _db.Queryable<LqKdJksyjEntity>().Where(p => p.Kdpxid == item.BillingItemId).ToList();
... ... @@ -2913,7 +2920,8 @@ namespace NCC.Extend.LqKdKdjlb
2913 2920 ItemId = refundMxEntity.Px,
2914 2921 StoreId = refundEntity.Md,
2915 2922 ItemName = refundMxEntity.Pxmc,
2916   - PerformanceType = refundMxEntity.PerformanceType
  2923 + PerformanceType = refundMxEntity.PerformanceType,
  2924 + BeautyType = refundMxEntity.BeautyType,
2917 2925 });
2918 2926 }
2919 2927 //查询科技部老师的业绩
... ... @@ -2941,7 +2949,8 @@ namespace NCC.Extend.LqKdKdjlb
2941 2949 ItemId = refundMxEntity.Px,
2942 2950 StoreId = refundEntity.Md,
2943 2951 ItemName = refundMxEntity.Pxmc,
2944   - PerformanceType = refundMxEntity.PerformanceType
  2952 + PerformanceType = refundMxEntity.PerformanceType,
  2953 + BeautyType = refundMxEntity.BeautyType,
2945 2954 });
2946 2955 }
2947 2956 }
... ... @@ -3025,6 +3034,7 @@ namespace NCC.Extend.LqKdKdjlb
3025 3034 Remark = $"从会员 {fromMember.Khmc} 转入",
3026 3035 ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == refundPxmxEntity.Px).Select(x => x.Qt2).FirstAsync(),
3027 3036 PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == refundPxmxEntity.Px).Select(x => x.Fl3).FirstAsync() ?? "",
  3037 + BeautyType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == refundPxmxEntity.Px).Select(x => x.BeautyType).FirstAsync(),
3028 3038 };
3029 3039 billingPxmxEntities.Add(billingPxmxEntity);
3030 3040  
... ... @@ -3056,7 +3066,8 @@ namespace NCC.Extend.LqKdKdjlb
3056 3066 ItemId = billingPxmxEntity.Px,
3057 3067 StoreId = billingEntity.Djmd,
3058 3068 ItemName = billingPxmxEntity.Pxmc,
3059   - PerformanceType = billingPxmxEntity.PerformanceType
  3069 + PerformanceType = billingPxmxEntity.PerformanceType,
  3070 + BeautyType = billingPxmxEntity.BeautyType,
3060 3071 });
3061 3072 }
3062 3073 }
... ... @@ -3087,7 +3098,8 @@ namespace NCC.Extend.LqKdKdjlb
3087 3098 ItemId = billingPxmxEntity.Px,
3088 3099 StoreId = billingEntity.Djmd,
3089 3100 ItemName = billingPxmxEntity.Pxmc,
3090   - PerformanceType = billingPxmxEntity.PerformanceType
  3101 + PerformanceType = billingPxmxEntity.PerformanceType,
  3102 + BeautyType = billingPxmxEntity.BeautyType,
3091 3103 });
3092 3104 }
3093 3105 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs 0 → 100644
  1 +using Microsoft.AspNetCore.Authorization;
  2 +using Microsoft.AspNetCore.Mvc;
  3 +using NCC.Common.Filter;
  4 +using NCC.Common.Helper;
  5 +using NCC.Dependency;
  6 +using NCC.DynamicApiController;
  7 +using NCC.Extend.Entitys.Dto.LqMajorProjectTeacherSalary;
  8 +using NCC.Extend.Entitys.lq_attendance_summary;
  9 +using NCC.Extend.Entitys.lq_hytk_hytk;
  10 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  11 +using NCC.Extend.Entitys.lq_md_major_project_teacher_assignment;
  12 +using NCC.Extend.Entitys.lq_md_xdbhsj;
  13 +using NCC.Extend.Entitys.lq_mdxx;
  14 +using NCC.Extend.Entitys.lq_major_project_teacher_salary_statistics;
  15 +using NCC.System.Entitys.Permission;
  16 +using SqlSugar;
  17 +using System;
  18 +using System.Collections.Generic;
  19 +using System.Linq;
  20 +using System.Threading.Tasks;
  21 +using Yitter.IdGenerator;
  22 +
  23 +namespace NCC.Extend
  24 +{
  25 + /// <summary>
  26 + /// 大项目部老师薪酬服务
  27 + /// </summary>
  28 + [ApiDescriptionSettings(Tag = "大项目部老师薪酬服务", Name = "LqMajorProjectTeacherSalary", Order = 303)]
  29 + [Route("api/Extend/[controller]")]
  30 + public class LqMajorProjectTeacherSalaryService : IDynamicApiController, ITransient
  31 + {
  32 + private readonly ISqlSugarClient _db;
  33 +
  34 + /// <summary>
  35 + /// 初始化一个<see cref="LqMajorProjectTeacherSalaryService"/>类型的新实例
  36 + /// </summary>
  37 + public LqMajorProjectTeacherSalaryService(ISqlSugarClient db)
  38 + {
  39 + _db = db;
  40 + }
  41 +
  42 + /// <summary>
  43 + /// 获取大项目部老师工资列表
  44 + /// </summary>
  45 + /// <param name="input">查询参数</param>
  46 + /// <returns>大项目部老师工资分页列表</returns>
  47 + [HttpGet("major-project-teacher")]
  48 + public async Task<dynamic> GetMajorProjectTeacherSalaryList([FromQuery] MajorProjectTeacherSalaryInput input)
  49 + {
  50 + var monthStr = $"{input.Year}{input.Month:D2}";
  51 +
  52 + // 1. 检查当月是否已生成工资数据
  53 + var exists = await _db.Queryable<LqMajorProjectTeacherSalaryStatisticsEntity>()
  54 + .AnyAsync(x => x.StatisticsMonth == monthStr);
  55 +
  56 + // 2. 如果没有数据,则进行计算
  57 + if (!exists)
  58 + {
  59 + await CalculateMajorProjectTeacherSalary(input.Year, input.Month);
  60 + }
  61 +
  62 + // 3. 查询数据
  63 + var query = _db.Queryable<LqMajorProjectTeacherSalaryStatisticsEntity>()
  64 + .Where(x => x.StatisticsMonth == monthStr);
  65 +
  66 + if (!string.IsNullOrEmpty(input.StoreId))
  67 + {
  68 + query = query.Where(x => x.StoreId == input.StoreId);
  69 + }
  70 +
  71 + if (!string.IsNullOrEmpty(input.Keyword))
  72 + {
  73 + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeAccount.Contains(input.Keyword));
  74 + }
  75 +
  76 + var list = await query.Select(x => new MajorProjectTeacherSalaryOutput
  77 + {
  78 + Id = x.Id,
  79 + StatisticsMonth = x.StatisticsMonth,
  80 + StoreId = x.StoreId,
  81 + StoreName = x.StoreName,
  82 + Position = x.Position,
  83 + EmployeeName = x.EmployeeName,
  84 + EmployeeId = x.EmployeeId,
  85 + EmployeeAccount = x.EmployeeAccount,
  86 + OrderAchievement = x.OrderAchievement,
  87 + ConsumeAchievement = x.ConsumeAchievement,
  88 + RefundAchievement = x.RefundAchievement,
  89 + TotalPerformance = x.TotalPerformance,
  90 + BaseSalary = x.BaseSalary,
  91 + PerformanceCommissionRate = x.PerformanceCommissionRate,
  92 + PerformanceCommissionAmount = x.PerformanceCommissionAmount,
  93 + TotalCommission = x.TotalCommission,
  94 + HandworkFee = x.HandworkFee,
  95 + WorkingDays = x.WorkingDays,
  96 + LeaveDays = x.LeaveDays,
  97 + TransportationAllowance = x.TransportationAllowance,
  98 + LessRest = x.LessRest,
  99 + FullAttendance = x.FullAttendance,
  100 + CalculatedGrossSalary = x.CalculatedGrossSalary,
  101 + GuaranteedSalary = x.GuaranteedSalary,
  102 + GuaranteedLeaveDeduction = x.GuaranteedLeaveDeduction,
  103 + GuaranteedBaseSalary = x.GuaranteedBaseSalary,
  104 + GuaranteedSupplement = x.GuaranteedSupplement,
  105 + FinalGrossSalary = x.FinalGrossSalary,
  106 + MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy,
  107 + MonthlyTransportSubsidy = x.MonthlyTransportSubsidy,
  108 + LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy,
  109 + LastMonthTransportSubsidy = x.LastMonthTransportSubsidy,
  110 + TotalSubsidy = x.TotalSubsidy,
  111 + MissingCard = x.MissingCard,
  112 + LateArrival = x.LateArrival,
  113 + LeaveDeduction = x.LeaveDeduction,
  114 + SocialInsuranceDeduction = x.SocialInsuranceDeduction,
  115 + RewardDeduction = x.RewardDeduction,
  116 + AccommodationDeduction = x.AccommodationDeduction,
  117 + StudyPeriodDeduction = x.StudyPeriodDeduction,
  118 + WorkClothesDeduction = x.WorkClothesDeduction,
  119 + TotalDeduction = x.TotalDeduction,
  120 + Bonus = x.Bonus,
  121 + ReturnPhoneDeposit = x.ReturnPhoneDeposit,
  122 + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit,
  123 + ActualSalary = x.ActualSalary,
  124 + MonthlyPaymentStatus = x.MonthlyPaymentStatus,
  125 + PaidAmount = x.PaidAmount,
  126 + PendingAmount = x.PendingAmount,
  127 + LastMonthSupplement = x.LastMonthSupplement,
  128 + MonthlyTotalPayment = x.MonthlyTotalPayment,
  129 + IsLocked = x.IsLocked,
  130 + IsTerminated = x.IsTerminated,
  131 + UpdateTime = x.UpdateTime,
  132 + StoreType = x.StoreType,
  133 + StoreCategory = x.StoreCategory,
  134 + IsNewStore = x.IsNewStore,
  135 + NewStoreProtectionStage = x.NewStoreProtectionStage
  136 + })
  137 + .ToPagedListAsync(input.currentPage, input.pageSize);
  138 +
  139 + return PageResult<MajorProjectTeacherSalaryOutput>.SqlSugarPageResult(list);
  140 + }
  141 +
  142 + /// <summary>
  143 + /// 计算大项目部老师工资
  144 + /// </summary>
  145 + /// <param name="year">年份</param>
  146 + /// <param name="month">月份</param>
  147 + /// <returns></returns>
  148 + [HttpPost("calculate/major-project-teacher")]
  149 + public async Task CalculateMajorProjectTeacherSalary(int year, int month)
  150 + {
  151 + var startDate = new DateTime(year, month, 1);
  152 + var endDate = startDate.AddMonths(1).AddDays(-1);
  153 + var monthStr = $"{year}{month:D2}";
  154 + var yearStr = year.ToString();
  155 + var monthStr2 = month.ToString("D2");
  156 +
  157 + // 1. 获取基础数据
  158 +
  159 + // 1.1 获取大项目部老师归属信息(从lq_md_major_project_teacher_assignment表)
  160 + var assignmentList = await _db.Queryable<LqMdMajorProjectTeacherAssignmentEntity>()
  161 + .Where(x => x.Year == yearStr && x.Month == monthStr2)
  162 + .ToListAsync();
  163 +
  164 + if (!assignmentList.Any())
  165 + {
  166 + // 如果没有归属信息,直接返回
  167 + return;
  168 + }
  169 +
  170 + // 1.2 门店信息 (lq_mdxx)
  171 + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
  172 + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x);
  173 +
  174 + // 1.3 门店新店保护信息 (lq_md_xdbhsj)
  175 + var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>()
  176 + .Where(x => x.Sfqy == 1)
  177 + .ToListAsync();
  178 +
  179 + var newStoreProtectionDict = newStoreProtectionList
  180 + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate)
  181 + .GroupBy(x => x.Mdid)
  182 + .ToDictionary(g => g.Key, g => g.First());
  183 +
  184 + // 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 })
  189 + .ToListAsync();
  190 + var storeBillingDict = storeBillingList
  191 + .Where(x => !string.IsNullOrEmpty(x.Djmd))
  192 + .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 })
  199 + .ToListAsync();
  200 + var storeRefundDict = storeRefundList
  201 + .Where(x => !string.IsNullOrEmpty(x.Md))
  202 + .GroupBy(x => x.Md)
  203 + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0));
  204 +
  205 + // 1.5 考勤数据 (lq_attendance_summary)
  206 + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
  207 + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
  208 + .ToListAsync();
  209 + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x);
  210 +
  211 + // 1.6 获取员工信息 (BASE_USER)
  212 + var teacherIds = assignmentList.Select(x => x.TeacherId).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
  213 + var userList = await _db.Queryable<UserEntity>()
  214 + .Where(x => teacherIds.Contains(x.Id))
  215 + .Select(x => new { x.Id, x.RealName, x.Account, x.IsOnJob })
  216 + .ToListAsync();
  217 + var userDict = userList.ToDictionary(x => x.Id, x => x);
  218 +
  219 + // 2. 按大项目部老师聚合数据
  220 + var teacherStats = new Dictionary<string, LqMajorProjectTeacherSalaryStatisticsEntity>();
  221 +
  222 + foreach (var assignment in assignmentList)
  223 + {
  224 + if (string.IsNullOrEmpty(assignment.TeacherId) || string.IsNullOrEmpty(assignment.StoreId))
  225 + {
  226 + continue;
  227 + }
  228 +
  229 + var teacherId = assignment.TeacherId;
  230 + var storeId = assignment.StoreId;
  231 +
  232 + // 如果该老师已经处理过(可能有多条归属记录),则合并数据
  233 + if (teacherStats.ContainsKey(teacherId))
  234 + {
  235 + // 如果同一个老师归属于多个门店,需要合并业绩
  236 + var existingSalary = teacherStats[teacherId];
  237 +
  238 + // 获取该门店的业绩
  239 + var additionalBilling = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0;
  240 + var additionalRefund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0;
  241 + var additionalStorePerformance = additionalBilling - additionalRefund;
  242 +
  243 + // 累加业绩
  244 + existingSalary.OrderAchievement += additionalBilling;
  245 + existingSalary.RefundAchievement += additionalRefund;
  246 + existingSalary.TotalPerformance += additionalStorePerformance;
  247 +
  248 + continue;
  249 + }
  250 +
  251 + // 2.1 创建工资统计对象
  252 + var salary = new LqMajorProjectTeacherSalaryStatisticsEntity
  253 + {
  254 + Id = YitIdHelper.NextId().ToString(),
  255 + StatisticsMonth = monthStr,
  256 + EmployeeId = teacherId,
  257 + Position = "大项目部老师",
  258 + IsTerminated = 0,
  259 + CreateTime = DateTime.Now,
  260 + UpdateTime = DateTime.Now,
  261 + IsLocked = 0
  262 + };
  263 +
  264 + // 2.2 填充员工信息
  265 + if (userDict.ContainsKey(teacherId))
  266 + {
  267 + var user = userDict[teacherId];
  268 + salary.EmployeeName = user.RealName ?? "";
  269 + salary.EmployeeAccount = user.Account ?? "";
  270 + salary.IsTerminated = user.IsOnJob == 0 ? 1 : 0;
  271 + }
  272 +
  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;
  281 + }
  282 + else
  283 + {
  284 + salary.StoreId = storeId;
  285 + salary.StoreName = "";
  286 + }
  287 +
  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 + }
  300 +
  301 + // 2.5 统计门店总业绩(开单业绩 - 退卡业绩)
  302 + var billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0;
  303 + var refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0;
  304 + var storeTotalPerformance = billing - refund;
  305 +
  306 + salary.OrderAchievement = billing; // 开单业绩(门店开单实付)
  307 + salary.RefundAchievement = refund; // 退卡业绩(门店退卡金额)
  308 + salary.ConsumeAchievement = 0; // 大项目部老师不统计消耗业绩
  309 + salary.TotalPerformance = storeTotalPerformance; // 门店总业绩(开单 - 退卡)
  310 +
  311 + // 2.6 考勤数据
  312 + var attendance = attendanceDict.ContainsKey(teacherId) ? attendanceDict[teacherId] : null;
  313 + salary.WorkingDays = attendance?.WorkDays ?? 0;
  314 + salary.LeaveDays = attendance?.LeaveDays ?? 0;
  315 +
  316 + // 3. 工资计算
  317 + if (salary.IsTerminated == 1)
  318 + {
  319 + // 离职员工特殊处理(待确认规则)
  320 + salary.BaseSalary = 0;
  321 + salary.PerformanceCommissionRate = 0;
  322 + salary.PerformanceCommissionAmount = 0;
  323 + salary.TotalCommission = 0;
  324 + }
  325 + else
  326 + {
  327 + // 在职员工正常计算
  328 +
  329 + // 3.1 计算底薪(固定3000元)
  330 + salary.BaseSalary = 3000m;
  331 +
  332 + // 3.2 计算业绩提成(阶梯式)
  333 + var performanceCommissionResult = CalculatePerformanceCommission(salary.TotalPerformance);
  334 + salary.PerformanceCommissionRate = performanceCommissionResult.Rate;
  335 + salary.PerformanceCommissionAmount = performanceCommissionResult.Amount;
  336 +
  337 + // 3.3 提成合计(等于业绩提成金额)
  338 + salary.TotalCommission = salary.PerformanceCommissionAmount;
  339 + }
  340 +
  341 + // 3.4 初始化其他字段(默认值为0)
  342 + salary.HandworkFee = 0;
  343 + salary.TransportationAllowance = 0;
  344 + salary.LessRest = 0;
  345 + salary.FullAttendance = 0;
  346 + salary.CalculatedGrossSalary = salary.BaseSalary + salary.TotalCommission + salary.HandworkFee;
  347 + salary.GuaranteedSalary = 0;
  348 + salary.GuaranteedLeaveDeduction = 0;
  349 + salary.GuaranteedBaseSalary = 0;
  350 + salary.GuaranteedSupplement = 0;
  351 + salary.FinalGrossSalary = salary.CalculatedGrossSalary;
  352 + salary.MonthlyTrainingSubsidy = 0;
  353 + salary.MonthlyTransportSubsidy = 0;
  354 + salary.LastMonthTrainingSubsidy = 0;
  355 + salary.LastMonthTransportSubsidy = 0;
  356 + salary.TotalSubsidy = 0;
  357 + salary.MissingCard = 0;
  358 + salary.LateArrival = 0;
  359 + salary.LeaveDeduction = 0;
  360 + salary.SocialInsuranceDeduction = 0;
  361 + salary.RewardDeduction = 0;
  362 + salary.AccommodationDeduction = 0;
  363 + salary.StudyPeriodDeduction = 0;
  364 + salary.WorkClothesDeduction = 0;
  365 + salary.TotalDeduction = 0;
  366 + salary.Bonus = 0;
  367 + salary.ReturnPhoneDeposit = 0;
  368 + salary.ReturnAccommodationDeposit = 0;
  369 + salary.ActualSalary = salary.FinalGrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus;
  370 + salary.MonthlyPaymentStatus = "";
  371 + salary.PaidAmount = 0;
  372 + salary.PendingAmount = salary.ActualSalary;
  373 + salary.LastMonthSupplement = 0;
  374 + salary.MonthlyTotalPayment = 0;
  375 +
  376 + teacherStats[teacherId] = salary;
  377 + }
  378 +
  379 + // 4. 重新计算合并后的业绩提成(如果同一个老师归属于多个门店)
  380 + foreach (var salary in teacherStats.Values)
  381 + {
  382 + if (salary.IsTerminated == 0)
  383 + {
  384 + // 重新计算业绩提成(基于合并后的总业绩)
  385 + var performanceCommissionResult = CalculatePerformanceCommission(salary.TotalPerformance);
  386 + salary.PerformanceCommissionRate = performanceCommissionResult.Rate;
  387 + salary.PerformanceCommissionAmount = performanceCommissionResult.Amount;
  388 + salary.TotalCommission = salary.PerformanceCommissionAmount;
  389 +
  390 + // 重新计算应发工资
  391 + salary.CalculatedGrossSalary = salary.BaseSalary + salary.TotalCommission + salary.HandworkFee;
  392 + salary.FinalGrossSalary = salary.CalculatedGrossSalary;
  393 + salary.ActualSalary = salary.FinalGrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus;
  394 + salary.PendingAmount = salary.ActualSalary;
  395 + }
  396 + }
  397 +
  398 + // 5. 保存数据
  399 + if (teacherStats.Any())
  400 + {
  401 + // 先删除当月旧数据 (防止重复)
  402 + await _db.Deleteable<LqMajorProjectTeacherSalaryStatisticsEntity>()
  403 + .Where(x => x.StatisticsMonth == monthStr)
  404 + .ExecuteCommandAsync();
  405 +
  406 + await _db.Insertable(teacherStats.Values.ToList()).ExecuteCommandAsync();
  407 + }
  408 + }
  409 +
  410 + /// <summary>
  411 + /// 计算业绩提成(阶梯式)
  412 + /// </summary>
  413 + /// <param name="totalPerformance">总业绩(门店总业绩)</param>
  414 + /// <returns>提成比例和金额</returns>
  415 + private (decimal Rate, decimal Amount) CalculatePerformanceCommission(decimal totalPerformance)
  416 + {
  417 + if (totalPerformance <= 200000m)
  418 + {
  419 + // ≤ 20万 → 0%(无提成)
  420 + return (0m, 0m);
  421 + }
  422 + else if (totalPerformance <= 1000000m)
  423 + {
  424 + // > 20万 且 ≤ 100万 → 2%
  425 + return (2m, totalPerformance * 0.02m);
  426 + }
  427 + else
  428 + {
  429 + // > 100万 → 2.5%
  430 + return (2.5m, totalPerformance * 0.025m);
  431 + }
  432 + }
  433 + }
  434 +}
  435 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqMdMajorProjectTeacherAssignmentService.cs
... ... @@ -63,6 +63,8 @@ namespace NCC.Extend
63 63 month = it.Month,
64 64 teacherId = it.TeacherId,
65 65 teacherName = SqlFunc.Subqueryable<UserEntity>().Where(x => x.Id == it.TeacherId).Select(x => x.RealName),
  66 + educationTeacherId = it.EducationTeacherId,
  67 + educationTeacherName = SqlFunc.Subqueryable<UserEntity>().Where(x => x.Id == it.EducationTeacherId).Select(x => x.RealName),
66 68 remark = it.Remark,
67 69 createTime = it.CreateTime,
68 70 createUserId = it.CreateUserId,
... ... @@ -91,6 +93,7 @@ namespace NCC.Extend
91 93 .WhereIF(!string.IsNullOrEmpty(input.year), p => p.Year == input.year)
92 94 .WhereIF(!string.IsNullOrEmpty(input.month), p => p.Month == input.month)
93 95 .WhereIF(!string.IsNullOrEmpty(input.teacherId), p => p.TeacherId == input.teacherId)
  96 + .WhereIF(!string.IsNullOrEmpty(input.educationTeacherId), p => p.EducationTeacherId == input.educationTeacherId)
94 97 .Select(it => new LqMdMajorProjectTeacherAssignmentListOutput
95 98 {
96 99 id = it.Id,
... ... @@ -100,6 +103,8 @@ namespace NCC.Extend
100 103 month = it.Month,
101 104 teacherId = it.TeacherId,
102 105 teacherName = SqlFunc.Subqueryable<UserEntity>().Where(x => x.Id == it.TeacherId).Select(x => x.RealName),
  106 + educationTeacherId = it.EducationTeacherId,
  107 + educationTeacherName = SqlFunc.Subqueryable<UserEntity>().Where(x => x.Id == it.EducationTeacherId).Select(x => x.RealName),
103 108 remark = it.Remark,
104 109 createTime = it.CreateTime,
105 110 createUserId = it.CreateUserId,
... ... @@ -212,6 +217,7 @@ namespace NCC.Extend
212 217 entity.Year = input.year;
213 218 entity.Month = input.month;
214 219 entity.TeacherId = input.teacherId;
  220 + entity.EducationTeacherId = input.educationTeacherId;
215 221 entity.Remark = input.remark;
216 222 entity.UpdateTime = DateTime.Now;
217 223 entity.UpdateUserId = userInfo.userId;
... ... @@ -327,6 +333,7 @@ namespace NCC.Extend
327 333 Year = targetYearStr,
328 334 Month = targetMonthStr,
329 335 TeacherId = x.TeacherId,
  336 + EducationTeacherId = x.EducationTeacherId,
330 337 Remark = x.Remark,
331 338 CreateTime = DateTime.Now,
332 339 CreateUserId = _userManager.UserId,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
... ... @@ -152,20 +152,30 @@ namespace NCC.Extend
152 152 try
153 153 {
154 154 var row = dataTable.Rows[i];
155   - var id = row[0]?.ToString()?.Trim();
156   - var employeeName = row[1]?.ToString()?.Trim();
157   - var employeePhone = row[2]?.ToString()?.Trim();
158   - var yearText = row[3]?.ToString()?.Trim();
159   - var monthText = row[4]?.ToString()?.Trim();
160   - var baseRewardPerformanceText = row[5]?.ToString()?.Trim();
161   - var cooperationRewardPerformanceText = row[6]?.ToString()?.Trim();
162   - var newCustomerPerformanceText = row[7]?.ToString()?.Trim();
163   - var newCustomerConversionRateText = row[8]?.ToString()?.Trim();
164   - var upgradePerformanceText = row[9]?.ToString()?.Trim();
165   - var upgradeConversionRateText = row[10]?.ToString()?.Trim();
166   - var upgradeCustomerCountText = row[11]?.ToString()?.Trim();
167   - var otherPerformanceAddText = row[12]?.ToString()?.Trim();
168   - var otherPerformanceSubtractText = row[13]?.ToString()?.Trim();
  155 + // 安全获取列值,如果列不存在则返回空字符串
  156 + var GetColumnValue = new Func<int, string>((colIndex) =>
  157 + {
  158 + if (colIndex < row.ItemArray.Length)
  159 + {
  160 + return row[colIndex]?.ToString()?.Trim() ?? "";
  161 + }
  162 + return "";
  163 + });
  164 +
  165 + var id = GetColumnValue(0);
  166 + var employeeName = GetColumnValue(1);
  167 + var employeePhone = GetColumnValue(2);
  168 + var yearText = GetColumnValue(3);
  169 + var monthText = GetColumnValue(4);
  170 + var baseRewardPerformanceText = GetColumnValue(5);
  171 + var cooperationRewardPerformanceText = GetColumnValue(6);
  172 + var newCustomerPerformanceText = GetColumnValue(7);
  173 + var newCustomerConversionRateText = GetColumnValue(8);
  174 + var upgradePerformanceText = GetColumnValue(9);
  175 + var upgradeConversionRateText = GetColumnValue(10);
  176 + var upgradeCustomerCountText = GetColumnValue(11);
  177 + var otherPerformanceAddText = GetColumnValue(12);
  178 + var otherPerformanceSubtractText = GetColumnValue(13);
169 179  
170 180 // 跳过空行
171 181 if (string.IsNullOrEmpty(employeeName) && string.IsNullOrEmpty(employeePhone))
... ... @@ -279,30 +289,67 @@ namespace NCC.Extend
279 289 {
280 290 try
281 291 {
282   - // 1. 根据健康师姓名和电话查找用户ID
283   - var user = await _db.Queryable<UserEntity>()
284   - .Where(u => u.RealName == item.EmployeeName && u.MobilePhone == item.EmployeePhone)
285   - .FirstAsync();
286   -
287   - if (user == null)
288   - {
289   - errorMessages.Add($"健康师 {item.EmployeeName}({item.EmployeePhone}) 不存在");
290   - failCount++;
291   - continue;
292   - }
293   -
294   - // 2. 检查是否已存在相同记录(根据健康师ID、年份、月份)
  292 + // 1. 查找用户ID(优先使用ID,否则通过姓名和电话查找)
  293 + UserEntity user = null;
295 294 LqSalaryExtraCalculationEntity existingRecord = null;
296 295  
297   - // 如果提供了ID,先尝试根据ID查找
  296 + // 如果提供了ID,先尝试根据ID查找现有记录,获取EmployeeId
298 297 if (!string.IsNullOrEmpty(item.Id))
299 298 {
300 299 existingRecord = await _db.Queryable<LqSalaryExtraCalculationEntity>()
301 300 .Where(x => x.Id == item.Id)
302 301 .FirstAsync();
  302 +
  303 + if (existingRecord != null)
  304 + {
  305 + // 通过EmployeeId查找用户
  306 + user = await _db.Queryable<UserEntity>()
  307 + .Where(u => u.Id == existingRecord.EmployeeId)
  308 + .FirstAsync();
  309 + }
  310 + }
  311 +
  312 + // 如果还没有找到用户,通过姓名和电话查找
  313 + if (user == null)
  314 + {
  315 + // 处理姓名:去除"A"前缀
  316 + var cleanName = item.EmployeeName;
  317 + if (cleanName.StartsWith("A"))
  318 + {
  319 + cleanName = cleanName.Substring(1).Trim();
  320 + }
  321 +
  322 + // 处理电话:如果电话是"无"或空,只按姓名查找
  323 + var phoneIsEmpty = string.IsNullOrWhiteSpace(item.EmployeePhone) ||
  324 + item.EmployeePhone.Trim() == "无" ||
  325 + item.EmployeePhone.Trim() == "00000000" ||
  326 + item.EmployeePhone.Trim().Length < 8; // 排除明显无效的电话
  327 +
  328 + if (phoneIsEmpty)
  329 + {
  330 + // 只按姓名查找(支持原姓名和去除前缀后的姓名)
  331 + user = await _db.Queryable<UserEntity>()
  332 + .Where(u => u.RealName == item.EmployeeName || u.RealName == cleanName)
  333 + .FirstAsync();
  334 + }
  335 + else
  336 + {
  337 + // 同时匹配姓名和电话(支持原姓名和去除前缀后的姓名)
  338 + user = await _db.Queryable<UserEntity>()
  339 + .Where(u => (u.RealName == item.EmployeeName || u.RealName == cleanName) &&
  340 + u.MobilePhone == item.EmployeePhone)
  341 + .FirstAsync();
  342 + }
  343 + }
  344 +
  345 + if (user == null)
  346 + {
  347 + errorMessages.Add($"健康师 {item.EmployeeName}({item.EmployeePhone}) 不存在");
  348 + failCount++;
  349 + continue;
303 350 }
304 351  
305   - // 如果没有找到,则根据健康师ID、年份、月份查找
  352 + // 2. 如果还没有找到现有记录,则根据健康师ID、年份、月份查找
306 353 if (existingRecord == null)
307 354 {
308 355 existingRecord = await _db.Queryable<LqSalaryExtraCalculationEntity>()
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -939,6 +939,7 @@ namespace NCC.Extend.LqXhHyhk
939 939 IsEffective = StatusEnum.有效.GetHashCode(),
940 940 ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
941 941 PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "",
  942 + BeautyType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.BeautyType).FirstAsync(),
942 943 };
943 944 allPxmxEntities.Add(lqXhPxmxEntity);
944 945  
... ... @@ -973,7 +974,8 @@ namespace NCC.Extend.LqXhHyhk
973 974 ItemId = lqXhPxmxEntity.Px,
974 975 StoreId = entity.Md,
975 976 ItemName = lqXhPxmxEntity.Pxmc,
976   - PerformanceType = lqXhPxmxEntity.PerformanceType
  977 + PerformanceType = lqXhPxmxEntity.PerformanceType,
  978 + BeautyType = lqXhPxmxEntity.BeautyType,
977 979 }
978 980 );
979 981 }
... ... @@ -1013,7 +1015,8 @@ namespace NCC.Extend.LqXhHyhk
1013 1015 ItemId = lqXhPxmxEntity.Px,
1014 1016 StoreId = entity.Md,
1015 1017 ItemName = lqXhPxmxEntity.Pxmc,
1016   - PerformanceType = lqXhPxmxEntity.PerformanceType
  1018 + PerformanceType = lqXhPxmxEntity.PerformanceType,
  1019 + BeautyType = lqXhPxmxEntity.BeautyType,
1017 1020 }
1018 1021 );
1019 1022 }
... ... @@ -1317,6 +1320,7 @@ namespace NCC.Extend.LqXhHyhk
1317 1320 ProjectNumber = (decimal)((item.projectNumber ?? 0) + (entity.OvertimeCoefficient * (item.projectNumber ?? 0))),
1318 1321 ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
1319 1322 PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "",
  1323 + BeautyType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.BeautyType).FirstAsync(),
1320 1324 };
1321 1325 allPxmxEntities.Add(lqXhPxmxEntity);
1322 1326  
... ... @@ -1351,7 +1355,8 @@ namespace NCC.Extend.LqXhHyhk
1351 1355 ItemId = lqXhPxmxEntity.Px,
1352 1356 StoreId = entity.Md,
1353 1357 ItemName = lqXhPxmxEntity.Pxmc,
1354   - PerformanceType = lqXhPxmxEntity.PerformanceType
  1358 + PerformanceType = lqXhPxmxEntity.PerformanceType,
  1359 + BeautyType = lqXhPxmxEntity.BeautyType,
1355 1360 }
1356 1361 );
1357 1362 }
... ... @@ -1390,7 +1395,8 @@ namespace NCC.Extend.LqXhHyhk
1390 1395 ItemId = lqXhPxmxEntity.Px,
1391 1396 StoreId = entity.Md,
1392 1397 ItemName = lqXhPxmxEntity.Pxmc,
1393   - PerformanceType = lqXhPxmxEntity.PerformanceType
  1398 + PerformanceType = lqXhPxmxEntity.PerformanceType,
  1399 + BeautyType = lqXhPxmxEntity.BeautyType,
1394 1400 });
1395 1401 }
1396 1402 }
... ...
netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs
1 1 using System;
  2 +using System.Globalization;
2 3 using System.IO;
3 4 using System.Linq;
4 5 using System.Text;
... ... @@ -64,24 +65,33 @@ namespace NCC.System.Service.Common
64 65 if (!this.AllowFileType(fileType, type))
65 66 throw NCCException.Oh(ErrorCode.D1800);
66 67 var _filePath = GetPathByType(type);
67   - var _fileName = DateTime.Now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString() + Path.GetExtension(file.FileName);
  68 + var now = DateTime.Now;
  69 + var _fileName = now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString() + Path.GetExtension(file.FileName);
68 70  
69   - // annexpic 类型强制使用阿里云OSS存储
  71 + // annexpic 类型强制使用阿里云OSS存储,并按天生成文件夹(不包含Files/SystemFile前缀)
70 72 string forceStoreType = type == "annexpic" ? "aliyun-oss" : null;
71   - await UploadFileByType(file, _filePath, _fileName, forceStoreType);
  73 + string uploadFilePath = _filePath;
  74 + if (type == "annexpic")
  75 + {
  76 + // 按天生成文件夹:yyyy/MM/dd(直接使用日期文件夹,不包含Files/SystemFile前缀)
  77 + var dateFolder = now.ToString("yyyy/MM/dd");
  78 + uploadFilePath = dateFolder;
  79 + }
  80 + await UploadFileByType(file, uploadFilePath, _fileName, forceStoreType);
72 81  
73 82 // 如果是annexpic类型且使用阿里云OSS,返回OSS的完整访问地址
74 83 string fileUrl;
75 84 if (type == "annexpic")
76 85 {
77   - fileUrl = await GetOSSAccessUrl(_filePath, _fileName);
  86 + fileUrl = await GetOSSAccessUrl(uploadFilePath, _fileName);
78 87 }
79 88 else
80 89 {
81 90 fileUrl = string.Format("/api/File/Image/{0}/{1}", type, _fileName);
82 91 }
83 92  
84   - return new { name = _fileName, url = fileUrl };
  93 + // 返回格式与数据库保存格式一致:name(原始文件名), fileId(生成的文件名), url(OSS地址)
  94 + return new { name = file.FileName, fileId = _fileName, url = fileUrl };
85 95 }
86 96  
87 97 /// <summary>
... ... @@ -94,7 +104,40 @@ namespace NCC.System.Service.Common
94 104 [AllowAnonymous]
95 105 public async Task<IActionResult> GetImg(string type, string fileName)
96 106 {
97   - var filePath = Path.Combine(GetPathByType(type), fileName.Replace("@", "."));
  107 + var basePath = GetPathByType(type);
  108 + var actualFileName = fileName.Replace("@", ".");
  109 +
  110 + // annexpic 类型需要从文件名中提取日期来构建路径(不包含Files/SystemFile前缀)
  111 + string filePath;
  112 + if (type == "annexpic")
  113 + {
  114 + // 文件名格式:yyyyMMdd_xxx.ext,提取日期部分
  115 + if (actualFileName.Length >= 8)
  116 + {
  117 + var datePart = actualFileName.Substring(0, 8); // 前8位是日期
  118 + if (DateTime.TryParseExact(datePart, "yyyyMMdd", null, DateTimeStyles.None, out DateTime fileDate))
  119 + {
  120 + // 构建日期文件夹路径:yyyy/MM/dd(直接使用日期文件夹,不包含Files/SystemFile前缀)
  121 + var dateFolder = fileDate.ToString("yyyy/MM/dd");
  122 + filePath = $"{dateFolder}/{actualFileName}";
  123 + }
  124 + else
  125 + {
  126 + // 如果无法解析日期,使用原路径(兼容旧文件)
  127 + filePath = Path.Combine(basePath, actualFileName);
  128 + }
  129 + }
  130 + else
  131 + {
  132 + // 文件名长度不足,使用原路径(兼容旧文件)
  133 + filePath = Path.Combine(basePath, actualFileName);
  134 + }
  135 + }
  136 + else
  137 + {
  138 + filePath = Path.Combine(basePath, actualFileName);
  139 + }
  140 +
98 141 // annexpic 类型强制使用阿里云OSS存储
99 142 string forceStoreType = type == "annexpic" ? "aliyun-oss" : null;
100 143 return await DownloadFileByType(filePath, fileName, forceStoreType);
... ... @@ -193,12 +236,46 @@ namespace NCC.System.Service.Common
193 236 var fileName = paramsList.Count > 1 ? paramsList[1] : "";
194 237 string type = paramsList.Count > 2 ? paramsList[2] : "";
195 238 string exname = paramsList.Count > 3 ? paramsList[3] : "";
196   - var filePath = Path.Combine(GetPathByType(type), fileName.Replace("@", "."));
  239 +
  240 + var basePath = GetPathByType(type);
  241 + var actualFileName = fileName.Replace("@", ".");
  242 +
  243 + // annexpic 类型需要从文件名中提取日期来构建路径(不包含Files/SystemFile前缀)
  244 + string filePath;
  245 + if (type == "annexpic")
  246 + {
  247 + // 文件名格式:yyyyMMdd_xxx.ext,提取日期部分
  248 + if (actualFileName.Length >= 8)
  249 + {
  250 + var datePart = actualFileName.Substring(0, 8); // 前8位是日期
  251 + if (DateTime.TryParseExact(datePart, "yyyyMMdd", null, DateTimeStyles.None, out DateTime fileDate))
  252 + {
  253 + // 构建日期文件夹路径:yyyy/MM/dd(直接使用日期文件夹,不包含Files/SystemFile前缀)
  254 + var dateFolder = fileDate.ToString("yyyy/MM/dd");
  255 + filePath = $"{dateFolder}/{actualFileName}";
  256 + }
  257 + else
  258 + {
  259 + // 如果无法解析日期,使用原路径(兼容旧文件)
  260 + filePath = Path.Combine(basePath, actualFileName);
  261 + }
  262 + }
  263 + else
  264 + {
  265 + // 文件名长度不足,使用原路径(兼容旧文件)
  266 + filePath = Path.Combine(basePath, actualFileName);
  267 + }
  268 + }
  269 + else
  270 + {
  271 + filePath = Path.Combine(basePath, actualFileName);
  272 + }
  273 +
197 274 var fileDownloadName = exname;
198 275 if (fileDownloadName.IsNullOrWhiteSpace() || fileDownloadName.Split('.').Length < 2)
199 276 fileDownloadName = Path.GetFileName(filePath);
200 277 if (fileDownloadName.IsNullOrWhiteSpace())
201   - fileDownloadName = fileName.Replace(GetPathByType(type), "");
  278 + fileDownloadName = fileName.Replace(basePath, "");
202 279 // annexpic 类型强制使用阿里云OSS存储
203 280 string forceStoreType = type == "annexpic" ? "aliyun-oss" : null;
204 281 return await DownloadFileByType(filePath, fileDownloadName, forceStoreType);
... ... @@ -365,10 +442,21 @@ namespace NCC.System.Service.Common
365 442  
366 443 // 使用Uri对象来解析和替换域名,保留查询参数(签名信息)
367 444 var originalUri = new Uri(urlString);
  445 +
  446 + // 提取域名(去掉协议和端口号)
  447 + var domainHost = domain.Replace("https://", "").Replace("http://", "").TrimEnd('/');
  448 + // 如果域名中包含端口号,去掉端口号
  449 + var colonIndex = domainHost.IndexOf(':');
  450 + if (colonIndex > 0)
  451 + {
  452 + domainHost = domainHost.Substring(0, colonIndex);
  453 + }
  454 +
368 455 var customUri = new UriBuilder(originalUri)
369 456 {
370 457 Scheme = domain.StartsWith("https://") ? "https" : "http",
371   - Host = domain.Replace("https://", "").Replace("http://", "").TrimEnd('/'),
  458 + Host = domainHost,
  459 + Port = -1, // 使用默认端口(http:80, https:443),不显示在URL中
372 460 // 确保保留查询参数(签名信息)
373 461 Query = originalUri.Query
374 462 };
... ... @@ -626,29 +714,54 @@ namespace NCC.System.Service.Common
626 714 throw NCCException.Oh($"不支持的图片格式: {imageFormat}");
627 715 }
628 716  
629   - // 生成文件名
630   - var fileName = GenerateImageFileName(input.FileName, imageFormat);
631   -
632 717 // 获取存储路径
633 718 var imageType = string.IsNullOrEmpty(input.ImageType) ? "temporary" : input.ImageType;
634   - var filePath = GetPathByType(imageType);
635 719  
636   - // 确保目录存在
637   - if (!Directory.Exists(filePath))
  720 + // 所有类型都上传到阿里云OSS存储
  721 + string uploadFilePath;
  722 + string fileName;
  723 + var now = DateTime.Now;
  724 +
  725 + if (imageType == "annexpic")
  726 + {
  727 + // 生成文件名(格式与 Uploader 一致:yyyyMMdd_xxx.ext)
  728 + fileName = now.ToString("yyyyMMdd") + "_" + YitIdHelper.NextId().ToString() + "." + imageFormat;
  729 + // 按天生成文件夹:yyyy/MM/dd(直接使用日期文件夹,不包含Files/SystemFile前缀)
  730 + var dateFolder = now.ToString("yyyy/MM/dd");
  731 + uploadFilePath = dateFolder;
  732 + }
  733 + else
  734 + {
  735 + // 生成文件名
  736 + fileName = GenerateImageFileName(input.FileName, imageFormat);
  737 + // 获取原始路径,用于OSS存储
  738 + var originalPath = GetPathByType(imageType).TrimEnd('/').TrimEnd('\\');
  739 + // 按天生成文件夹:yyyy/MM/dd,并保留原始路径结构
  740 + var dateFolder = now.ToString("yyyy/MM/dd");
  741 + uploadFilePath = $"{originalPath}/{dateFolder}";
  742 + }
  743 +
  744 + // 上传到OSS
  745 + var bucketName = KeyVariable.BucketName;
  746 + var ossPath = $"{uploadFilePath.TrimEnd('/').TrimEnd('\\')}/{fileName}";
  747 + using (var stream = new MemoryStream(imageData))
638 748 {
639   - Directory.CreateDirectory(filePath);
  749 + await _oSSServiceFactory.Create("aliyun").PutObjectAsync(bucketName, ossPath, stream);
640 750 }
641 751  
642   - // 保存图片文件
643   - var fullPath = Path.Combine(filePath, fileName);
644   - await File.WriteAllBytesAsync(fullPath, imageData);
  752 + // 获取OSS的完整访问地址
  753 + string accessUrl = await GetOSSAccessUrl(uploadFilePath, fileName);
645 754  
646   - // 生成访问URL
647   - var accessUrl = $"/api/File/Image/{imageType}/{fileName}";
  755 + // 返回格式与数据库保存格式一致:name(原始文件名), fileId(生成的文件名), url(OSS地址)
  756 + // 对于Base64上传,如果没有提供原始文件名,使用生成的文件名作为name
  757 + var originalFileName = string.IsNullOrEmpty(input.FileName)
  758 + ? fileName
  759 + : $"{input.FileName}.{imageFormat}";
648 760  
649 761 return new
650 762 {
651   - name = fileName,
  763 + name = originalFileName,
  764 + fileId = fileName,
652 765 url = accessUrl,
653 766 fileSize = imageData.Length,
654 767 imageFormat = imageFormat.ToUpper(),
... ...
sql/创建事业部总经理经理工资统计表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 创建事业部总经理/经理工资统计表
  3 +-- 功能:存储事业部总经理/经理每月的工资计算数据,包括底薪、业绩提成、扣款、补贴、奖金、支付等信息
  4 +-- 创建时间:2025年
  5 +-- ============================================
  6 +
  7 +-- 删除表(如果存在)
  8 +DROP TABLE IF EXISTS lq_business_unit_manager_salary_statistics;
  9 +
  10 +-- ============================================
  11 +-- 创建事业部总经理/经理工资统计表
  12 +-- ============================================
  13 +CREATE TABLE lq_business_unit_manager_salary_statistics (
  14 + -- 主键
  15 + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID',
  16 +
  17 + -- 一、基础信息字段
  18 + F_StatisticsMonth VARCHAR(6) NOT NULL COMMENT '统计月份(YYYYMM格式)',
  19 + F_Position VARCHAR(50) NOT NULL COMMENT '核算岗位(总经理/经理)',
  20 + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名',
  21 + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID',
  22 + F_EmployeeAccount VARCHAR(100) NULL COMMENT '员工账号',
  23 + F_ManagerType INT NOT NULL COMMENT '经理类型(0=经理,1=总经理)',
  24 + F_IsTerminated INT NOT NULL DEFAULT 0 COMMENT '是否离职(0=在职,1=离职)',
  25 +
  26 + -- 二、管理的门店信息(JSON格式)
  27 + F_StorePerformanceDetail TEXT NULL COMMENT '门店业绩明细(JSON格式,记录每个门店的业绩和提成详情)',
  28 +
  29 + -- 三、底薪相关字段
  30 + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 4000.00 COMMENT '底薪金额(固定4000元)',
  31 +
  32 + -- 四、提成相关字段
  33 + F_TotalCommission DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成合计(所有门店提成金额汇总)',
  34 +
  35 + -- 五、考勤相关字段
  36 + F_WorkingDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '在店天数',
  37 + F_LeaveDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假天数',
  38 +
  39 + -- 六、工资计算字段
  40 + F_CalculatedGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '核算应发工资(底薪 + 提成合计)',
  41 + F_FinalGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '最终应发工资(等于核算应发工资)',
  42 +
  43 + -- 七、补贴相关字段
  44 + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴',
  45 + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴',
  46 + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴',
  47 + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴',
  48 + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计',
  49 +
  50 + -- 八、扣款相关字段
  51 + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款',
  52 + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款',
  53 + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款',
  54 + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保',
  55 + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励',
  56 + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费',
  57 + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用',
  58 + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用',
  59 + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计',
  60 +
  61 + -- 九、奖金相关字段
  62 + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金',
  63 + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金',
  64 + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金',
  65 +
  66 + -- 十、支付相关字段
  67 + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(最终应发工资 - 扣款合计 + 补贴合计 + 奖金)',
  68 + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)',
  69 + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额',
  70 + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额',
  71 + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月',
  72 + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额',
  73 +
  74 + -- 十一、系统字段
  75 + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)',
  76 + F_CreateTime DATETIME NOT NULL COMMENT '创建时间',
  77 + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间',
  78 + F_CreateUser VARCHAR(50) NULL COMMENT '创建人',
  79 + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人',
  80 +
  81 + -- 主键约束
  82 + PRIMARY KEY (F_Id),
  83 +
  84 + -- 唯一索引:确保同一员工同一月份只有一条记录
  85 + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth),
  86 +
  87 + -- 普通索引
  88 + KEY `idx_statistics_month` (F_StatisticsMonth),
  89 + KEY `idx_employee_id` (F_EmployeeId),
  90 + KEY `idx_employee_account` (F_EmployeeAccount),
  91 + KEY `idx_manager_type` (F_ManagerType),
  92 + KEY `idx_is_terminated` (F_IsTerminated),
  93 + KEY `idx_create_time` (F_CreateTime)
  94 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='事业部总经理/经理工资统计表';
  95 +
  96 +-- ============================================
  97 +-- 表结构说明
  98 +-- ============================================
  99 +/*
  100 +表名:lq_business_unit_manager_salary_statistics(事业部总经理/经理工资统计表)
  101 +
  102 +功能说明:
  103 +1. 存储事业部总经理/经理每月的工资计算数据
  104 +2. 包括底薪、业绩提成、扣款、补贴、奖金、支付等信息
  105 +3. 支持按员工、月份查询
  106 +4. 记录管理的门店汇总信息
  107 +
  108 +主要字段说明:
  109 +- F_BaseSalary:底薪(固定4000元)
  110 +- F_StorePerformanceDetail:门店业绩明细(JSON格式,记录每个门店的业绩和提成详情)
  111 +- F_TotalCommission:提成合计(所有门店提成金额汇总)
  112 +
  113 +数据来源:
  114 +- 总经理/经理归属:lq_md_general_manager_lifeline 表(通过F_GeneralManagerId和F_Month获取)
  115 +- 门店生命线:lq_md_target 表的 F_StoreLifeline 字段
  116 +- 提成阶梯:lq_md_general_manager_lifeline 表的 F_Lifeline1/2/3 和 F_CommissionRate1/2/3
  117 +- 开单业绩:lq_kd_kdjlb 表的 sfyj 字段(按门店统计)
  118 +- 退卡业绩:lq_hytk_hytk 表的 F_ActualRefundAmount 或 tkje 字段(按门店统计)
  119 +
  120 +计算公式:
  121 +- 门店总业绩 = 开单业绩 - 退卡业绩
  122 +- 提成计算逻辑:
  123 + 1. 判断是否达到提成门槛:门店业绩 ≥ 门店生命线?
  124 + - 如果否 → 该门店提成 = 0
  125 + - 如果是 → 继续计算提成
  126 + 2. 如果达到门槛,使用提成阶梯计算提成(分段累进):
  127 + - 业绩 ≤ 提成阶梯1:提成 = 业绩 × 提成比例1
  128 + - 提成阶梯1 < 业绩 ≤ 提成阶梯2:提成 = 提成阶梯1 × 提成比例1 + (业绩 - 提成阶梯1) × 提成比例2
  129 + - 提成阶梯2 < 业绩 ≤ 提成阶梯3:提成 = 提成阶梯1 × 提成比例1 + (提成阶梯2 - 提成阶梯1) × 提成比例2 + (业绩 - 提成阶梯2) × 提成比例3
  130 + - 业绩 > 提成阶梯3:提成 = 提成阶梯1 × 提成比例1 + (提成阶梯2 - 提成阶梯1) × 提成比例2 + (提成阶梯3 - 提成阶梯2) × 提成比例3 + (业绩 - 提成阶梯3) × 提成比例3
  131 +- 总提成 = SUM(各门店提成金额)
  132 +- 核算应发工资 = 底薪(4000) + 总提成
  133 +- 最终应发工资 = 核算应发工资
  134 +- 实发工资 = 最终应发工资 - 扣款合计 + 补贴合计 + 奖金
  135 +
  136 +门店业绩明细JSON格式示例:
  137 +[
  138 + {
  139 + "storeId": "门店ID",
  140 + "storeName": "门店名称",
  141 + "storeLifeline": 300000.00,
  142 + "billingPerformance": 400000.00,
  143 + "refundPerformance": 50000.00,
  144 + "storePerformance": 350000.00,
  145 + "reachedLifeline": true,
  146 + "lifeline1": 350000.00,
  147 + "commissionRate1": 1.00,
  148 + "lifeline2": 400000.00,
  149 + "commissionRate2": 1.50,
  150 + "lifeline3": 450000.00,
  151 + "commissionRate3": 2.00,
  152 + "commissionAmount": 3500.00,
  153 + "calculationDetail": "业绩350000元,达到门店生命线300000元,使用提成阶梯1计算:350000 × 1.0% = 3500元"
  154 + },
  155 + {
  156 + "storeId": "门店ID2",
  157 + "storeName": "门店名称2",
  158 + "storeLifeline": 250000.00,
  159 + "billingPerformance": 220000.00,
  160 + "refundPerformance": 20000.00,
  161 + "storePerformance": 200000.00,
  162 + "reachedLifeline": false,
  163 + "commissionAmount": 0.00,
  164 + "calculationDetail": "业绩200000元,未达到门店生命线250000元,无提成"
  165 + }
  166 +]
  167 +
  168 +JSON字段说明:
  169 +- storeId:门店ID
  170 +- storeName:门店名称
  171 +- storeLifeline:门店生命线(从lq_md_target表获取)
  172 +- billingPerformance:门店开单业绩
  173 +- refundPerformance:门店退卡业绩
  174 +- storePerformance:门店总业绩(开单业绩 - 退卡业绩)
  175 +- reachedLifeline:是否达到门店生命线(true/false)
  176 +- lifeline1/2/3:提成阶梯1/2/3(从lq_md_general_manager_lifeline表获取)
  177 +- commissionRate1/2/3:提成比例1/2/3(%)
  178 +- commissionAmount:该门店的提成金额
  179 +- calculationDetail:计算说明(文字描述)
  180 +
  181 +索引说明:
  182 +- 主键索引:F_Id
  183 +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录)
  184 +- 普通索引:
  185 + - F_StatisticsMonth:按月份查询
  186 + - F_EmployeeId:按员工查询
  187 + - F_ManagerType:按经理类型查询(总经理/经理)
  188 + - F_CreateTime:按创建时间查询
  189 +
  190 +数据校验要求:
  191 +1. 底薪固定为4000元
  192 +2. 门店生命线必须设置,未设置应报错
  193 +3. 提成阶梯1和提成比例1必须设置,未设置应报错
  194 +4. 提成必须按照阶梯式计算(分段累进)
  195 +5. 如果门店业绩 < 门店生命线,则该门店提成为0
  196 +6. 总经理和经理的计算规则相同
  197 +*/
  198 +
... ...
sql/创建大项目部老师工资统计表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 创建大项目部老师工资统计表
  3 +-- 功能:存储大项目部老师每月的工资计算数据,包括底薪、业绩提成、扣款、补贴、奖金、支付等信息
  4 +-- 创建时间:2025年
  5 +-- ============================================
  6 +
  7 +-- 删除表(如果存在)
  8 +DROP TABLE IF EXISTS lq_major_project_teacher_salary_statistics;
  9 +
  10 +-- ============================================
  11 +-- 创建大项目部老师工资统计表
  12 +-- ============================================
  13 +CREATE TABLE lq_major_project_teacher_salary_statistics (
  14 + -- 主键
  15 + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID',
  16 +
  17 + -- 一、基础信息字段
  18 + F_StatisticsMonth VARCHAR(6) NOT NULL COMMENT '统计月份(YYYYMM格式)',
  19 + F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID',
  20 + F_StoreName VARCHAR(200) NOT NULL COMMENT '门店名称',
  21 + F_Position VARCHAR(50) NOT NULL DEFAULT '大项目部老师' COMMENT '核算岗位(固定为"大项目部老师")',
  22 + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名',
  23 + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID',
  24 + F_EmployeeAccount VARCHAR(100) NULL COMMENT '员工账号',
  25 + F_StoreType INT NULL COMMENT '门店类型(200平/旗舰店)',
  26 + F_StoreCategory INT NULL COMMENT '门店分类(1=A类,2=B类,3=C类)',
  27 + F_IsNewStore VARCHAR(10) NULL COMMENT '是否新店(是/否)',
  28 + F_NewStoreProtectionStage INT NOT NULL DEFAULT 0 COMMENT '新店保护阶段(0/1/2)',
  29 +
  30 + -- 二、业绩相关字段
  31 + F_OrderAchievement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '开单业绩(从lq_kd_kjbsyj表统计,根据归属表关联)',
  32 + F_ConsumeAchievement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '消耗业绩(从lq_xh_kjbsyj表统计,根据归属表关联)',
  33 + F_RefundAchievement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退卡业绩(从lq_hytk_kjbsyj表统计,根据归属表关联)',
  34 + F_TotalPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '总业绩(开单业绩 + 消耗业绩 + 退卡业绩,用于业绩提成计算)',
  35 +
  36 + -- 三、底薪相关字段
  37 + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 3000.00 COMMENT '底薪金额(固定3000元)',
  38 +
  39 + -- 四、业绩提成相关字段
  40 + F_PerformanceCommissionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '业绩提成比例(百分比,如2.00表示2%,0.00表示无提成)',
  41 + F_PerformanceCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '业绩提成金额(总业绩 × 业绩提成比例,阶梯式计算)',
  42 + F_TotalCommission DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成合计(业绩提成金额)',
  43 +
  44 + -- 五、其他收入字段
  45 + F_HandworkFee DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '手工费',
  46 + F_TransportationAllowance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '车补',
  47 + F_LessRest DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '少休费',
  48 + F_FullAttendance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '全勤奖',
  49 +
  50 + -- 六、考勤相关字段
  51 + F_WorkingDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '在店天数',
  52 + F_LeaveDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假天数',
  53 +
  54 + -- 七、工资计算字段
  55 + F_CalculatedGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '核算应发工资(底薪 + 提成合计 + 其他收入)',
  56 + F_GuaranteedSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底工资',
  57 + F_GuaranteedLeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底请假扣款',
  58 + F_GuaranteedBaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底底薪',
  59 + F_GuaranteedSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底补差',
  60 + F_FinalGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '最终应发工资(取核算应发工资和保底工资的较大值)',
  61 +
  62 + -- 八、补贴相关字段
  63 + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴',
  64 + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴',
  65 + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴',
  66 + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴',
  67 + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计',
  68 +
  69 + -- 九、扣款相关字段
  70 + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款',
  71 + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款',
  72 + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款',
  73 + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保',
  74 + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励',
  75 + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费',
  76 + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用',
  77 + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用',
  78 + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计',
  79 +
  80 + -- 十、奖金相关字段
  81 + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金',
  82 + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金',
  83 + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金',
  84 +
  85 + -- 十一、支付相关字段
  86 + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(最终应发工资 - 扣款合计 + 补贴合计 + 奖金)',
  87 + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)',
  88 + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额',
  89 + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额',
  90 + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月',
  91 + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额',
  92 +
  93 + -- 十二、系统字段
  94 + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)',
  95 + F_IsTerminated INT NOT NULL DEFAULT 0 COMMENT '是否离职(0=在职,1=离职)',
  96 + F_CreateTime DATETIME NOT NULL COMMENT '创建时间',
  97 + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间',
  98 + F_CreateUser VARCHAR(50) NULL COMMENT '创建人',
  99 + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人',
  100 +
  101 + -- 主键约束
  102 + PRIMARY KEY (F_Id),
  103 +
  104 + -- 唯一索引:确保同一员工同一月份只有一条记录
  105 + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth),
  106 +
  107 + -- 普通索引
  108 + KEY `idx_store_id` (F_StoreId),
  109 + KEY `idx_statistics_month` (F_StatisticsMonth),
  110 + KEY `idx_employee_id` (F_EmployeeId),
  111 + KEY `idx_employee_account` (F_EmployeeAccount),
  112 + KEY `idx_store_category` (F_StoreCategory),
  113 + KEY `idx_is_terminated` (F_IsTerminated),
  114 + KEY `idx_create_time` (F_CreateTime)
  115 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='大项目部老师工资统计表';
  116 +
  117 +-- ============================================
  118 +-- 表结构说明
  119 +-- ============================================
  120 +/*
  121 +表名:lq_major_project_teacher_salary_statistics(大项目部老师工资统计表)
  122 +
  123 +功能说明:
  124 +1. 存储大项目部老师每月的工资计算数据
  125 +2. 包括底薪、业绩提成、扣款、补贴、奖金、支付等信息
  126 +3. 支持按门店、员工、月份查询
  127 +
  128 +主要字段说明:
  129 +- F_BaseSalary:底薪(固定3000元)
  130 +- F_TotalPerformance:总业绩(开单业绩 + 消耗业绩 + 退卡业绩),用于计算业绩提成
  131 +- F_PerformanceCommissionRate:业绩提成比例(阶梯式:≤20万无提成,>20万且≤100万为2%,>100万为2.5%)
  132 +- F_PerformanceCommissionAmount:业绩提成金额(总业绩 × 业绩提成比例)
  133 +- F_TotalCommission:提成合计(等于业绩提成金额)
  134 +
  135 +数据来源:
  136 +- 老师归属:lq_md_major_project_teacher_assignment 表(每个月归属不一样)
  137 +- 开单业绩:lq_kd_kjbsyj 表(根据归属表关联)
  138 +- 消耗业绩:lq_xh_kjbsyj 表(根据归属表关联)
  139 +- 退卡业绩:lq_hytk_kjbsyj 表(根据归属表关联)
  140 +
  141 +计算公式:
  142 +- 总业绩 = 开单业绩 + 消耗业绩 + 退卡业绩
  143 +- 业绩提成计算(阶梯式):
  144 + * 总业绩 ≤ 20万:无提成(0%)
  145 + * 总业绩 > 20万 且 ≤ 100万:2%
  146 + * 总业绩 > 100万:2.5%
  147 +- 提成金额 = 总业绩 × 对应提成比例
  148 +- 核算应发工资 = 底薪(3000) + 提成合计 + 其他收入
  149 +- 最终应发工资 = MAX(核算应发工资, 保底工资)
  150 +- 实发工资 = 最终应发工资 - 扣款合计 + 补贴合计 + 奖金
  151 +
  152 +索引说明:
  153 +- 主键索引:F_Id
  154 +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录)
  155 +- 普通索引:
  156 + - F_StoreId:按门店查询
  157 + - F_StatisticsMonth:按月份查询
  158 + - F_EmployeeId:按员工查询
  159 + - F_StoreCategory:按门店分类查询
  160 + - F_CreateTime:按创建时间查询
  161 +
  162 +数据校验要求:
  163 +1. 底薪固定为3000元
  164 +2. 业绩提成必须按照阶梯式计算
  165 +3. 总业绩必须从归属表关联的业绩数据中统计
  166 +*/
  167 +
... ...
sql/创建库存使用申请审批流程表.sql
... ... @@ -104,3 +104,7 @@ WHERE u.`F_UnitPrice` = 0 OR u.`F_UnitPrice` IS NULL;
104 104  
105 105  
106 106  
  107 +
  108 +
  109 +
  110 +
... ...
sql/同步BeautyType字段数据.sql 0 → 100644
  1 +-- ============================================
  2 +-- 同步 F_BeautyType 字段数据
  3 +-- ============================================
  4 +-- 说明:从 lq_xmzl 表的 F_BeautyType 字段同步数据到其他相关表
  5 +--
  6 +-- 关联关系:
  7 +-- 1. lq_kd_pxmx: 通过 px 字段关联到 lq_xmzl.F_Id
  8 +-- 2. lq_kd_jksyj: 通过 F_kdpxid 关联到 lq_kd_pxmx,或通过 F_ItemId 直接关联到 lq_xmzl
  9 +-- 3. lq_kd_kjbsyj: 通过 F_kdpxid 关联到 lq_kd_pxmx,或通过 F_ItemId 直接关联到 lq_xmzl
  10 +-- 4. lq_hytk_mx: 通过 px 字段关联到 lq_xmzl.F_Id
  11 +-- 5. lq_hytk_jksyj: 通过 F_tkpxid 关联到 lq_hytk_mx,或通过 F_ItemId 直接关联到 lq_xmzl
  12 +-- 6. lq_hytk_kjbsyj: 通过 F_tkpxid 关联到 lq_hytk_mx,或通过 F_ItemId 直接关联到 lq_xmzl
  13 +-- 7. lq_xh_pxmx: 通过 px 字段关联到 lq_xmzl.F_Id
  14 +-- 8. lq_xh_jksyj: 通过 F_kdpxid 关联到 lq_xh_pxmx,或通过 F_ItemId 直接关联到 lq_xmzl
  15 +-- 9. lq_xh_kjbsyj: 通过 F_hkpxid 关联到 lq_xh_pxmx,或通过 F_ItemId 直接关联到 lq_xmzl
  16 +--
  17 +-- 注意事项:
  18 +-- - 优先使用 F_ItemId 直接关联(如果存在且有效)
  19 +-- - 如果 F_ItemId 为空,则通过关联表间接关联
  20 +-- - 只更新 F_BeautyType 为 NULL 或空字符串的记录
  21 +
  22 +-- ============================================
  23 +-- 1. 同步 lq_kd_pxmx(开单品项明细表)
  24 +-- ============================================
  25 +UPDATE lq_kd_pxmx t1
  26 +INNER JOIN lq_xmzl t2 ON t1.px = t2.F_Id
  27 +SET t1.F_BeautyType = t2.F_BeautyType
  28 +WHERE t2.F_BeautyType IS NOT NULL
  29 + AND t2.F_BeautyType != ''
  30 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  31 +
  32 +-- ============================================
  33 +-- 2. 同步 lq_kd_jksyj(开单健康师业绩表)
  34 +-- ============================================
  35 +-- 方式1:通过 F_ItemId 直接关联(优先)
  36 +UPDATE lq_kd_jksyj t1
  37 +INNER JOIN lq_xmzl t2 ON t1.F_ItemId = t2.F_Id
  38 +SET t1.F_BeautyType = t2.F_BeautyType
  39 +WHERE t2.F_BeautyType IS NOT NULL
  40 + AND t2.F_BeautyType != ''
  41 + AND t1.F_ItemId IS NOT NULL
  42 + AND t1.F_ItemId != ''
  43 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  44 +
  45 +-- 方式2:通过 F_kdpxid 关联到 lq_kd_pxmx,再关联到 lq_xmzl
  46 +UPDATE lq_kd_jksyj t1
  47 +INNER JOIN lq_kd_pxmx t2 ON t1.F_kdpxid = t2.F_Id
  48 +INNER JOIN lq_xmzl t3 ON t2.px = t3.F_Id
  49 +SET t1.F_BeautyType = t3.F_BeautyType
  50 +WHERE t3.F_BeautyType IS NOT NULL
  51 + AND t3.F_BeautyType != ''
  52 + AND t1.F_kdpxid IS NOT NULL
  53 + AND t1.F_kdpxid != ''
  54 + AND (t1.F_ItemId IS NULL OR t1.F_ItemId = '')
  55 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  56 +
  57 +-- ============================================
  58 +-- 3. 同步 lq_kd_kjbsyj(开单科技部老师业绩表)
  59 +-- ============================================
  60 +-- 方式1:通过 F_ItemId 直接关联(优先)
  61 +UPDATE lq_kd_kjbsyj t1
  62 +INNER JOIN lq_xmzl t2 ON t1.F_ItemId = t2.F_Id
  63 +SET t1.F_BeautyType = t2.F_BeautyType
  64 +WHERE t2.F_BeautyType IS NOT NULL
  65 + AND t2.F_BeautyType != ''
  66 + AND t1.F_ItemId IS NOT NULL
  67 + AND t1.F_ItemId != ''
  68 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  69 +
  70 +-- 方式2:通过 F_kdpxid 关联到 lq_kd_pxmx,再关联到 lq_xmzl
  71 +UPDATE lq_kd_kjbsyj t1
  72 +INNER JOIN lq_kd_pxmx t2 ON t1.F_kdpxid = t2.F_Id
  73 +INNER JOIN lq_xmzl t3 ON t2.px = t3.F_Id
  74 +SET t1.F_BeautyType = t3.F_BeautyType
  75 +WHERE t3.F_BeautyType IS NOT NULL
  76 + AND t3.F_BeautyType != ''
  77 + AND t1.F_kdpxid IS NOT NULL
  78 + AND t1.F_kdpxid != ''
  79 + AND (t1.F_ItemId IS NULL OR t1.F_ItemId = '')
  80 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  81 +
  82 +-- ============================================
  83 +-- 4. 同步 lq_hytk_mx(退卡品项明细表)
  84 +-- ============================================
  85 +UPDATE lq_hytk_mx t1
  86 +INNER JOIN lq_xmzl t2 ON t1.px = t2.F_Id
  87 +SET t1.F_BeautyType = t2.F_BeautyType
  88 +WHERE t2.F_BeautyType IS NOT NULL
  89 + AND t2.F_BeautyType != ''
  90 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  91 +
  92 +-- ============================================
  93 +-- 5. 同步 lq_hytk_jksyj(退卡健康师业绩表)
  94 +-- ============================================
  95 +-- 方式1:通过 F_ItemId 直接关联(优先)
  96 +UPDATE lq_hytk_jksyj t1
  97 +INNER JOIN lq_xmzl t2 ON t1.F_ItemId = t2.F_Id
  98 +SET t1.F_BeautyType = t2.F_BeautyType
  99 +WHERE t2.F_BeautyType IS NOT NULL
  100 + AND t2.F_BeautyType != ''
  101 + AND t1.F_ItemId IS NOT NULL
  102 + AND t1.F_ItemId != ''
  103 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  104 +
  105 +-- 方式2:通过 F_tkpxid 关联到 lq_hytk_mx,再关联到 lq_xmzl
  106 +UPDATE lq_hytk_jksyj t1
  107 +INNER JOIN lq_hytk_mx t2 ON t1.F_tkpxid = t2.F_Id
  108 +INNER JOIN lq_xmzl t3 ON t2.px = t3.F_Id
  109 +SET t1.F_BeautyType = t3.F_BeautyType
  110 +WHERE t3.F_BeautyType IS NOT NULL
  111 + AND t3.F_BeautyType != ''
  112 + AND t1.F_tkpxid IS NOT NULL
  113 + AND t1.F_tkpxid != ''
  114 + AND (t1.F_ItemId IS NULL OR t1.F_ItemId = '')
  115 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  116 +
  117 +-- ============================================
  118 +-- 6. 同步 lq_hytk_kjbsyj(退卡科技部老师业绩表)
  119 +-- ============================================
  120 +-- 方式1:通过 F_ItemId 直接关联(优先)
  121 +UPDATE lq_hytk_kjbsyj t1
  122 +INNER JOIN lq_xmzl t2 ON t1.F_ItemId = t2.F_Id
  123 +SET t1.F_BeautyType = t2.F_BeautyType
  124 +WHERE t2.F_BeautyType IS NOT NULL
  125 + AND t2.F_BeautyType != ''
  126 + AND t1.F_ItemId IS NOT NULL
  127 + AND t1.F_ItemId != ''
  128 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  129 +
  130 +-- 方式2:通过 F_tkpxid 关联到 lq_hytk_mx,再关联到 lq_xmzl
  131 +UPDATE lq_hytk_kjbsyj t1
  132 +INNER JOIN lq_hytk_mx t2 ON t1.F_tkpxid = t2.F_Id
  133 +INNER JOIN lq_xmzl t3 ON t2.px = t3.F_Id
  134 +SET t1.F_BeautyType = t3.F_BeautyType
  135 +WHERE t3.F_BeautyType IS NOT NULL
  136 + AND t3.F_BeautyType != ''
  137 + AND t1.F_tkpxid IS NOT NULL
  138 + AND t1.F_tkpxid != ''
  139 + AND (t1.F_ItemId IS NULL OR t1.F_ItemId = '')
  140 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  141 +
  142 +-- ============================================
  143 +-- 7. 同步 lq_xh_pxmx(耗卡品项明细表)
  144 +-- ============================================
  145 +UPDATE lq_xh_pxmx t1
  146 +INNER JOIN lq_xmzl t2 ON t1.px = t2.F_Id
  147 +SET t1.F_BeautyType = t2.F_BeautyType
  148 +WHERE t2.F_BeautyType IS NOT NULL
  149 + AND t2.F_BeautyType != ''
  150 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  151 +
  152 +-- ============================================
  153 +-- 8. 同步 lq_xh_jksyj(耗卡健康师业绩表)
  154 +-- ============================================
  155 +-- 方式1:通过 F_ItemId 直接关联(优先)
  156 +UPDATE lq_xh_jksyj t1
  157 +INNER JOIN lq_xmzl t2 ON t1.F_ItemId = t2.F_Id
  158 +SET t1.F_BeautyType = t2.F_BeautyType
  159 +WHERE t2.F_BeautyType IS NOT NULL
  160 + AND t2.F_BeautyType != ''
  161 + AND t1.F_ItemId IS NOT NULL
  162 + AND t1.F_ItemId != ''
  163 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  164 +
  165 +-- 方式2:通过 F_kdpxid 关联到 lq_xh_pxmx,再关联到 lq_xmzl
  166 +UPDATE lq_xh_jksyj t1
  167 +INNER JOIN lq_xh_pxmx t2 ON t1.F_kdpxid = t2.F_Id
  168 +INNER JOIN lq_xmzl t3 ON t2.px = t3.F_Id
  169 +SET t1.F_BeautyType = t3.F_BeautyType
  170 +WHERE t3.F_BeautyType IS NOT NULL
  171 + AND t3.F_BeautyType != ''
  172 + AND t1.F_kdpxid IS NOT NULL
  173 + AND t1.F_kdpxid != ''
  174 + AND (t1.F_ItemId IS NULL OR t1.F_ItemId = '')
  175 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  176 +
  177 +-- ============================================
  178 +-- 9. 同步 lq_xh_kjbsyj(耗卡科技部老师业绩表)
  179 +-- ============================================
  180 +-- 方式1:通过 F_ItemId 直接关联(优先)
  181 +UPDATE lq_xh_kjbsyj t1
  182 +INNER JOIN lq_xmzl t2 ON t1.F_ItemId = t2.F_Id
  183 +SET t1.F_BeautyType = t2.F_BeautyType
  184 +WHERE t2.F_BeautyType IS NOT NULL
  185 + AND t2.F_BeautyType != ''
  186 + AND t1.F_ItemId IS NOT NULL
  187 + AND t1.F_ItemId != ''
  188 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  189 +
  190 +-- 方式2:通过 F_hkpxid 关联到 lq_xh_pxmx,再关联到 lq_xmzl
  191 +UPDATE lq_xh_kjbsyj t1
  192 +INNER JOIN lq_xh_pxmx t2 ON t1.F_hkpxid = t2.F_Id
  193 +INNER JOIN lq_xmzl t3 ON t2.px = t3.F_Id
  194 +SET t1.F_BeautyType = t3.F_BeautyType
  195 +WHERE t3.F_BeautyType IS NOT NULL
  196 + AND t3.F_BeautyType != ''
  197 + AND t1.F_hkpxid IS NOT NULL
  198 + AND t1.F_hkpxid != ''
  199 + AND (t1.F_ItemId IS NULL OR t1.F_ItemId = '')
  200 + AND (t1.F_BeautyType IS NULL OR t1.F_BeautyType = '');
  201 +
  202 +-- ============================================
  203 +-- 10. 验证同步结果
  204 +-- ============================================
  205 +-- 查看各表同步的数据统计
  206 +-- SELECT
  207 +-- 'lq_kd_pxmx' AS table_name,
  208 +-- COUNT(*) AS total_count,
  209 +-- COUNT(F_BeautyType) AS beauty_type_count,
  210 +-- COUNT(*) - COUNT(F_BeautyType) AS null_count
  211 +-- FROM lq_kd_pxmx
  212 +-- UNION ALL
  213 +-- SELECT
  214 +-- 'lq_kd_jksyj' AS table_name,
  215 +-- COUNT(*) AS total_count,
  216 +-- COUNT(F_BeautyType) AS beauty_type_count,
  217 +-- COUNT(*) - COUNT(F_BeautyType) AS null_count
  218 +-- FROM lq_kd_jksyj
  219 +-- UNION ALL
  220 +-- SELECT
  221 +-- 'lq_kd_kjbsyj' AS table_name,
  222 +-- COUNT(*) AS total_count,
  223 +-- COUNT(F_BeautyType) AS beauty_type_count,
  224 +-- COUNT(*) - COUNT(F_BeautyType) AS null_count
  225 +-- FROM lq_kd_kjbsyj
  226 +-- UNION ALL
  227 +-- SELECT
  228 +-- 'lq_hytk_mx' AS table_name,
  229 +-- COUNT(*) AS total_count,
  230 +-- COUNT(F_BeautyType) AS beauty_type_count,
  231 +-- COUNT(*) - COUNT(F_BeautyType) AS null_count
  232 +-- FROM lq_hytk_mx
  233 +-- UNION ALL
  234 +-- SELECT
  235 +-- 'lq_hytk_jksyj' AS table_name,
  236 +-- COUNT(*) AS total_count,
  237 +-- COUNT(F_BeautyType) AS beauty_type_count,
  238 +-- COUNT(*) - COUNT(F_BeautyType) AS null_count
  239 +-- FROM lq_hytk_jksyj
  240 +-- UNION ALL
  241 +-- SELECT
  242 +-- 'lq_hytk_kjbsyj' AS table_name,
  243 +-- COUNT(*) AS total_count,
  244 +-- COUNT(F_BeautyType) AS beauty_type_count,
  245 +-- COUNT(*) - COUNT(F_BeautyType) AS null_count
  246 +-- FROM lq_hytk_kjbsyj
  247 +-- UNION ALL
  248 +-- SELECT
  249 +-- 'lq_xh_pxmx' AS table_name,
  250 +-- COUNT(*) AS total_count,
  251 +-- COUNT(F_BeautyType) AS beauty_type_count,
  252 +-- COUNT(*) - COUNT(F_BeautyType) AS null_count
  253 +-- FROM lq_xh_pxmx
  254 +-- UNION ALL
  255 +-- SELECT
  256 +-- 'lq_xh_jksyj' AS table_name,
  257 +-- COUNT(*) AS total_count,
  258 +-- COUNT(F_BeautyType) AS beauty_type_count,
  259 +-- COUNT(*) - COUNT(F_BeautyType) AS null_count
  260 +-- FROM lq_xh_jksyj
  261 +-- UNION ALL
  262 +-- SELECT
  263 +-- 'lq_xh_kjbsyj' AS table_name,
  264 +-- COUNT(*) AS total_count,
  265 +-- COUNT(F_BeautyType) AS beauty_type_count,
  266 +-- COUNT(*) - COUNT(F_BeautyType) AS null_count
  267 +-- FROM lq_xh_kjbsyj;
  268 +
... ...
sql/添加教育部老师字段.sql 0 → 100644
  1 +-- ============================================
  2 +-- 为门店大项目部老师归属设置表添加教育部老师字段
  3 +-- ============================================
  4 +-- 说明:在 lq_md_major_project_teacher_assignment 表中添加教育部老师字段
  5 +--
  6 +-- 字段说明:
  7 +-- F_EducationTeacherId:教育部老师用户ID,用于存储教育部老师的归属设置
  8 +--
  9 +-- 业务含义:
  10 +-- - 一个门店在一个月份可以同时有大项目部老师和教育部老师
  11 +-- - 教育部老师ID关联BASE_USER.F_Id
  12 +--
  13 +-- 注意事项:
  14 +-- - 字段类型为VARCHAR(50),允许为NULL(因为历史数据可能没有教育部老师)
  15 +-- - 字段位置:放在 F_TeacherId 字段之后
  16 +
  17 +-- ============================================
  18 +-- 添加教育部老师字段
  19 +-- ============================================
  20 +ALTER TABLE lq_md_major_project_teacher_assignment
  21 +ADD COLUMN F_EducationTeacherId VARCHAR(50) NULL COMMENT '教育部老师用户ID' AFTER F_TeacherId;
  22 +
  23 +-- ============================================
  24 +-- 创建索引(可选,如果需要按教育部老师查询)
  25 +-- ============================================
  26 +CREATE INDEX idx_education_teacher_id ON lq_md_major_project_teacher_assignment(F_EducationTeacherId);
  27 +
  28 +-- ============================================
  29 +-- 验证字段创建
  30 +-- ============================================
  31 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  32 +-- FROM INFORMATION_SCHEMA.COLUMNS
  33 +-- WHERE TABLE_NAME = 'lq_md_major_project_teacher_assignment'
  34 +-- AND COLUMN_NAME = 'F_EducationTeacherId';
  35 +
... ...
sql/添加科技部归类字段.sql
... ... @@ -8,7 +8,7 @@
8 8 --
9 9 -- 业务含义:
10 10 -- - 科技部归类用于科技部对品项进行分类统计和分析
11   --- - 分类值来源于项目资料表(lq_xmzl)的 F_BeautyType 字段(科美类型)
  11 +-- - 分类值来源于项目资料表(lq_xmzl)的 F_BeautyType 字段
12 12 --
13 13 -- 注意事项:
14 14 -- - 字段类型为VARCHAR(50),可存储各种分类值
... ...
sql/重命名科技部归类字段为BeautyType.sql 0 → 100644
  1 +-- ============================================
  2 +-- 重命名科技部归类字段为 F_BeautyType
  3 +-- ============================================
  4 +-- 说明:将已创建的 F_KjbCategory 字段重命名为 F_BeautyType,以便与 lq_xmzl 表的字段名统一
  5 +--
  6 +-- 字段说明:
  7 +-- F_BeautyType:科美类型,用于存储品项的科美类型信息(与 lq_xmzl.F_BeautyType 统一)
  8 +--
  9 +-- 业务含义:
  10 +-- - 科美类型用于对品项进行分类统计和分析
  11 +-- - 分类值来源于项目资料表(lq_xmzl)的 F_BeautyType 字段
  12 +--
  13 +-- 注意事项:
  14 +-- - 此脚本用于重命名已创建的字段
  15 +-- - 字段类型保持不变:VARCHAR(50),允许为NULL
  16 +-- - 字段位置保持不变:在 F_PerformanceType 字段之后
  17 +
  18 +-- ============================================
  19 +-- 1. lq_kd_pxmx(开单品项明细表) - 重命名字段
  20 +-- ============================================
  21 +ALTER TABLE lq_kd_pxmx
  22 +CHANGE COLUMN F_KjbCategory F_BeautyType VARCHAR(50) NULL COMMENT '科美类型(来源:lq_xmzl.F_BeautyType)';
  23 +
  24 +-- ============================================
  25 +-- 2. lq_kd_jksyj(开单健康师业绩表) - 重命名字段
  26 +-- ============================================
  27 +ALTER TABLE lq_kd_jksyj
  28 +CHANGE COLUMN F_KjbCategory F_BeautyType VARCHAR(50) NULL COMMENT '科美类型(来源:lq_xmzl.F_BeautyType)';
  29 +
  30 +-- ============================================
  31 +-- 3. lq_kd_kjbsyj(开单科技部老师业绩表) - 重命名字段
  32 +-- ============================================
  33 +ALTER TABLE lq_kd_kjbsyj
  34 +CHANGE COLUMN F_KjbCategory F_BeautyType VARCHAR(50) NULL COMMENT '科美类型(来源:lq_xmzl.F_BeautyType)';
  35 +
  36 +-- ============================================
  37 +-- 4. lq_hytk_mx(退卡品项明细表) - 重命名字段
  38 +-- ============================================
  39 +ALTER TABLE lq_hytk_mx
  40 +CHANGE COLUMN F_KjbCategory F_BeautyType VARCHAR(50) NULL COMMENT '科美类型(来源:lq_xmzl.F_BeautyType)';
  41 +
  42 +-- ============================================
  43 +-- 5. lq_hytk_jksyj(退卡健康师业绩表) - 重命名字段
  44 +-- ============================================
  45 +ALTER TABLE lq_hytk_jksyj
  46 +CHANGE COLUMN F_KjbCategory F_BeautyType VARCHAR(50) NULL COMMENT '科美类型(来源:lq_xmzl.F_BeautyType)';
  47 +
  48 +-- ============================================
  49 +-- 6. lq_hytk_kjbsyj(退卡科技部老师业绩表) - 重命名字段
  50 +-- ============================================
  51 +ALTER TABLE lq_hytk_kjbsyj
  52 +CHANGE COLUMN F_KjbCategory F_BeautyType VARCHAR(50) NULL COMMENT '科美类型(来源:lq_xmzl.F_BeautyType)';
  53 +
  54 +-- ============================================
  55 +-- 7. lq_xh_pxmx(耗卡品项明细表) - 添加字段
  56 +-- ============================================
  57 +-- 注意:如果字段已存在会报错,可以忽略;如果存在 F_KjbCategory 需要先重命名
  58 +ALTER TABLE lq_xh_pxmx
  59 +ADD COLUMN F_BeautyType VARCHAR(50) NULL COMMENT '科美类型(来源:lq_xmzl.F_BeautyType)' AFTER F_PerformanceType;
  60 +
  61 +-- ============================================
  62 +-- 8. lq_xh_jksyj(耗卡健康师业绩表) - 添加字段
  63 +-- ============================================
  64 +-- 注意:如果字段已存在会报错,可以忽略;如果存在 F_KjbCategory 需要先重命名
  65 +ALTER TABLE lq_xh_jksyj
  66 +ADD COLUMN F_BeautyType VARCHAR(50) NULL COMMENT '科美类型(来源:lq_xmzl.F_BeautyType)' AFTER F_PerformanceType;
  67 +
  68 +-- ============================================
  69 +-- 9. lq_xh_kjbsyj(耗卡科技部老师业绩表) - 添加字段
  70 +-- ============================================
  71 +-- 注意:如果字段已存在会报错,可以忽略;如果存在 F_KjbCategory 需要先重命名
  72 +ALTER TABLE lq_xh_kjbsyj
  73 +ADD COLUMN F_BeautyType VARCHAR(50) NULL COMMENT '科美类型(来源:lq_xmzl.F_BeautyType)' AFTER F_PerformanceType;
  74 +
  75 +-- ============================================
  76 +-- 10. 验证字段重命名
  77 +-- ============================================
  78 +-- 验证 lq_kd_pxmx 表
  79 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  80 +-- FROM INFORMATION_SCHEMA.COLUMNS
  81 +-- WHERE TABLE_NAME = 'lq_kd_pxmx'
  82 +-- AND COLUMN_NAME = 'F_BeautyType';
  83 +
  84 +-- 验证 lq_kd_jksyj 表
  85 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  86 +-- FROM INFORMATION_SCHEMA.COLUMNS
  87 +-- WHERE TABLE_NAME = 'lq_kd_jksyj'
  88 +-- AND COLUMN_NAME = 'F_BeautyType';
  89 +
  90 +-- 验证 lq_kd_kjbsyj 表
  91 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  92 +-- FROM INFORMATION_SCHEMA.COLUMNS
  93 +-- WHERE TABLE_NAME = 'lq_kd_kjbsyj'
  94 +-- AND COLUMN_NAME = 'F_BeautyType';
  95 +
  96 +-- 验证 lq_hytk_mx 表
  97 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  98 +-- FROM INFORMATION_SCHEMA.COLUMNS
  99 +-- WHERE TABLE_NAME = 'lq_hytk_mx'
  100 +-- AND COLUMN_NAME = 'F_BeautyType';
  101 +
  102 +-- 验证 lq_hytk_jksyj 表
  103 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  104 +-- FROM INFORMATION_SCHEMA.COLUMNS
  105 +-- WHERE TABLE_NAME = 'lq_hytk_jksyj'
  106 +-- AND COLUMN_NAME = 'F_BeautyType';
  107 +
  108 +-- 验证 lq_hytk_kjbsyj 表
  109 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  110 +-- FROM INFORMATION_SCHEMA.COLUMNS
  111 +-- WHERE TABLE_NAME = 'lq_hytk_kjbsyj'
  112 +-- AND COLUMN_NAME = 'F_BeautyType';
  113 +
  114 +-- 验证 lq_xh_pxmx 表
  115 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  116 +-- FROM INFORMATION_SCHEMA.COLUMNS
  117 +-- WHERE TABLE_NAME = 'lq_xh_pxmx'
  118 +-- AND COLUMN_NAME = 'F_BeautyType';
  119 +
  120 +-- 验证 lq_xh_jksyj 表
  121 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  122 +-- FROM INFORMATION_SCHEMA.COLUMNS
  123 +-- WHERE TABLE_NAME = 'lq_xh_jksyj'
  124 +-- AND COLUMN_NAME = 'F_BeautyType';
  125 +
  126 +-- 验证 lq_xh_kjbsyj 表
  127 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT
  128 +-- FROM INFORMATION_SCHEMA.COLUMNS
  129 +-- WHERE TABLE_NAME = 'lq_xh_kjbsyj'
  130 +-- AND COLUMN_NAME = 'F_BeautyType';
  131 +
... ...
事业部总经理经理工资计算规则梳理.md 0 → 100644
  1 +# 事业部总经理/经理工资计算规则梳理
  2 +
  3 +## 📋 目录
  4 +- [概述](#概述)
  5 +- [计算规则](#计算规则)
  6 +- [数据来源](#数据来源)
  7 +- [归属规则](#归属规则)
  8 +- [计算流程](#计算流程)
  9 +
  10 +---
  11 +
  12 +## 📋 概述
  13 +
  14 +事业部总经理/经理工资由以下几个部分组成:
  15 +1. **底薪**:固定4000元
  16 +2. **提成**:根据管理的门店业绩,使用阶梯式提成计算(基于门店总业绩)
  17 +
  18 +**重要说明**:
  19 +- 每个总经理/经理都会管理多个门店
  20 +- 提成计算基于门店总业绩(开单业绩 - 退卡业绩)
  21 +- 总经理和经理的计算规则相同
  22 +- 必须先达到门店生命线才能计算提成
  23 +
  24 +---
  25 +
  26 +## 💰 计算规则
  27 +
  28 +### 1. 底薪规则
  29 +
  30 +**固定底薪**:4000元
  31 +
  32 +- 无论业绩多少,底薪固定为4000元
  33 +- 不设档位,不设条件
  34 +- 不设考核扣款(与店长、主任不同)
  35 +
  36 +---
  37 +
  38 +### 2. 提成规则(阶梯式)
  39 +
  40 +**提成计算方式**:根据管理的门店业绩,使用阶梯式提成计算
  41 +
  42 +#### 2.1 提成门槛:门店生命线
  43 +
  44 +**重要概念**:门店生命线是提成的**门槛条件**,不是提成阶梯
  45 +
  46 +- **数据来源**:`lq_md_target` 表的 `F_StoreLifeline` 字段
  47 +- **判断条件**:
  48 + - 如果门店业绩 < 门店生命线 → **无提成**
  49 + - 如果门店业绩 ≥ 门店生命线 → **可以计算提成**
  50 +
  51 +**重要说明**:
  52 +- 门店生命线是**必须设置的**,未设置应报错
  53 +- 门店生命线是门店级别的指标,用于判断是否达到提成门槛
  54 +
  55 +#### 2.2 提成阶梯设置
  56 +
  57 +**数据来源**:`lq_md_general_manager_lifeline` 表
  58 +
  59 +每个门店都有**三个等级的提成阶梯**,每个等级对应不同的提成比例:
  60 +
  61 +| 等级 | 生命线字段 | 提成比例字段 | 说明 |
  62 +|------|-----------|-------------|------|
  63 +| **等级1** | `F_Lifeline1` | `F_CommissionRate1` | 第一级提成阶梯(必填) |
  64 +| **等级2** | `F_Lifeline2` | `F_CommissionRate2` | 第二级提成阶梯(可选) |
  65 +| **等级3** | `F_Lifeline3` | `F_CommissionRate3` | 第三级提成阶梯(可选) |
  66 +
  67 +**重要说明**:
  68 +- 提成阶梯1和提成比例1为必填项
  69 +- 提成阶梯2、3和对应的提成比例为可选项
  70 +- 每个门店的提成阶梯设置可能不同
  71 +- 提成阶梯由总经理在系统中配置
  72 +
  73 +**注意**:提成阶梯(`lq_md_general_manager_lifeline`)和门店生命线(`lq_md_target.F_StoreLifeline`)是**两个不同的概念**:
  74 +- **门店生命线**:判断是否达到提成门槛
  75 +- **提成阶梯**:计算提成金额的阶梯
  76 +
  77 +#### 2.3 阶梯式提成计算规则
  78 +
  79 +**前提条件**:门店业绩必须 ≥ 门店生命线,否则无提成
  80 +
  81 +**计算逻辑**:根据门店业绩落在哪个提成阶梯区间,使用对应的提成比例计算(分段累进)
  82 +
  83 +**示例**(假设某门店的设置):
  84 +- 门店生命线(`lq_md_target.F_StoreLifeline`)= 300,000元
  85 +- 提成阶梯1 = 350,000元,提成比例1 = 1.0%
  86 +- 提成阶梯2 = 400,000元,提成比例2 = 1.5%
  87 +- 提成阶梯3 = 450,000元,提成比例3 = 2.0%
  88 +
  89 +**计算规则**(分段累进):
  90 +
  91 +1. **业绩 < 门店生命线**:
  92 + - 提成 = 0(无提成)
  93 + - 示例:业绩 = 280,000元 → 提成 = 0
  94 +
  95 +2. **门店生命线 ≤ 业绩 ≤ 提成阶梯1**:
  96 + - 提成 = 业绩 × 提成比例1
  97 + - 示例:业绩 = 320,000元 → 提成 = 320,000 × 1.0% = 3,200元
  98 +
  99 +3. **提成阶梯1 < 业绩 ≤ 提成阶梯2**:
  100 + - 提成 = 提成阶梯1 × 提成比例1 + (业绩 - 提成阶梯1) × 提成比例2
  101 + - 示例:业绩 = 380,000元 → 提成 = 350,000 × 1.0% + (380,000 - 350,000) × 1.5% = 3,500 + 450 = 3,950元
  102 +
  103 +4. **提成阶梯2 < 业绩 ≤ 提成阶梯3**:
  104 + - 提成 = 提成阶梯1 × 提成比例1 + (提成阶梯2 - 提成阶梯1) × 提成比例2 + (业绩 - 提成阶梯2) × 提成比例3
  105 + - 示例:业绩 = 420,000元 → 提成 = 350,000 × 1.0% + (400,000 - 350,000) × 1.5% + (420,000 - 400,000) × 2.0% = 3,500 + 750 + 400 = 4,650元
  106 +
  107 +5. **业绩 > 提成阶梯3**:
  108 + - 提成 = 提成阶梯1 × 提成比例1 + (提成阶梯2 - 提成阶梯1) × 提成比例2 + (提成阶梯3 - 提成阶梯2) × 提成比例3 + (业绩 - 提成阶梯3) × 提成比例3
  109 + - 示例:业绩 = 500,000元 → 提成 = 350,000 × 1.0% + (400,000 - 350,000) × 1.5% + (450,000 - 400,000) × 2.0% + (500,000 - 450,000) × 2.0% = 3,500 + 750 + 1,000 + 1,000 = 6,250元
  110 +
  111 +**注意**:
  112 +- 如果提成阶梯2或提成阶梯3未设置(为NULL或0),则只使用提成阶梯1计算
  113 +- 如果提成阶梯2设置但提成阶梯3未设置,则业绩超过提成阶梯2的部分按提成比例2计算
  114 +
  115 +#### 2.4 多门店提成汇总
  116 +
  117 +**计算方式**:
  118 +- 总经理/经理管理的所有门店的提成分别计算
  119 +- 将所有门店的提成金额汇总,得到总提成金额
  120 +
  121 +**计算公式**:
  122 +```
  123 +总提成 = SUM(各门店提成金额)
  124 +```
  125 +
  126 +---
  127 +
  128 +## 📊 数据来源
  129 +
  130 +### 1. 总经理/经理列表和门店归属
  131 +
  132 +**数据来源表**:`lq_md_general_manager_lifeline`(总经理门店生命线设置表)
  133 +
  134 +**关键字段**:
  135 +- `F_GeneralManagerId`:总经理/经理用户ID
  136 +- `F_ManagerType`:经理类型(0=经理,1=总经理)
  137 +- `F_StoreId`:门店ID
  138 +- `F_Month`:月份(YYYYMM格式)
  139 +
  140 +**查询方式**:
  141 +1. **获取当月所有的经理和总经理**:
  142 + ```sql
  143 + SELECT DISTINCT F_GeneralManagerId, F_ManagerType
  144 + FROM lq_md_general_manager_lifeline
  145 + WHERE F_Month = 'YYYYMM'
  146 + ```
  147 +
  148 +2. **获取总经理/经理管理的门店**:
  149 + ```sql
  150 + SELECT F_StoreId
  151 + FROM lq_md_general_manager_lifeline
  152 + WHERE F_Month = 'YYYYMM'
  153 + AND F_GeneralManagerId = '用户ID'
  154 + ```
  155 +
  156 +**重要说明**:
  157 +- 通过 `lq_md_general_manager_lifeline` 表可以获取当月所有的经理和总经理
  158 +- 通过 `lq_md_general_manager_lifeline` 表可以看到当月总经理和经理管理的门店
  159 +- 每个门店在每个月可以设置一个总经理/经理的提成阶梯
  160 +
  161 +### 2. 门店生命线(提成门槛)
  162 +
  163 +**数据来源表**:`lq_md_target`(门店目标表)
  164 +
  165 +**关键字段**:
  166 +- `F_StoreId`:门店ID
  167 +- `F_Month`:月份(YYYYMM格式)
  168 +- `F_StoreLifeline`:门店生命线(**必须设置**)
  169 +
  170 +**查询条件**:
  171 +- 按门店ID和月份查询:`F_StoreId = '门店ID' AND F_Month = 'YYYYMM'`
  172 +
  173 +**重要说明**:
  174 +- 门店生命线是**必须设置的**,未设置应报错
  175 +- 门店生命线用于判断是否达到提成门槛
  176 +- 如果门店业绩 < 门店生命线,则无提成
  177 +
  178 +### 3. 提成阶梯设置
  179 +
  180 +**数据来源表**:`lq_md_general_manager_lifeline`(总经理门店生命线设置表)
  181 +
  182 +**关键字段**:
  183 +- `F_StoreId`:门店ID
  184 +- `F_Month`:月份(YYYYMM格式)
  185 +- `F_GeneralManagerId`:总经理/经理用户ID
  186 +- `F_Lifeline1`:第一级提成阶梯(必填)
  187 +- `F_CommissionRate1`:第一级提成比例(%,必填)
  188 +- `F_Lifeline2`:第二级提成阶梯(可选)
  189 +- `F_CommissionRate2`:第二级提成比例(%,可选)
  190 +- `F_Lifeline3`:第三级提成阶梯(可选)
  191 +- `F_CommissionRate3`:第三级提成比例(%,可选)
  192 +
  193 +**查询条件**:
  194 +- 按门店ID、月份、总经理/经理ID查询
  195 +- 如果未找到提成阶梯设置,则无法计算提成(应报错或跳过该门店)
  196 +
  197 +### 4. 门店总业绩
  198 +
  199 +**定义**:门店总业绩 = 开单业绩 - 退卡业绩
  200 +
  201 +**数据来源表及字段**:
  202 +
  203 +| 业绩类型 | 数据表 | 字段 | 说明 |
  204 +|---------|--------|------|------|
  205 +| **开单业绩** | `lq_kd_kdjlb` | `sfyj` | 门店开单实付金额 |
  206 +| **退卡业绩** | `lq_hytk_hytk` | `F_ActualRefundAmount` 或 `tkje` | 门店退卡金额 |
  207 +
  208 +**计算方式**:
  209 +```sql
  210 +门店总业绩 = SUM(门店开单实付金额) - SUM(门店退卡金额)
  211 +```
  212 +
  213 +**过滤条件**:
  214 +- 所有表记录必须满足:`F_IsEffective = 1`(有效记录)
  215 +- 按统计月份(YYYYMM格式)过滤时间范围
  216 +- 按门店ID(`djmd` 或 `md`)过滤
  217 +
  218 +**重要说明**:
  219 +- **确认**:提成计算基于门店总业绩(开单 - 退卡)
  220 +
  221 +---
  222 +
  223 +## 👥 归属规则
  224 +
  225 +### 1. 总经理/经理与门店的关联
  226 +
  227 +**关联方式**:
  228 +- 通过 `lq_md_general_manager_lifeline` 表关联
  229 +- 每个门店在每个月可以设置一个总经理/经理的提成阶梯
  230 +- 同一个总经理/经理可以管理多个门店
  231 +
  232 +**重要说明**:
  233 +- 通过 `lq_md_general_manager_lifeline` 表可以获取当月所有的经理和总经理
  234 +- 通过 `lq_md_general_manager_lifeline` 表可以看到当月总经理和经理管理的门店
  235 +
  236 +### 2. 门店生命线和提成阶梯的关系
  237 +
  238 +**两个不同的概念**:
  239 +
  240 +1. **门店生命线**(`lq_md_target.F_StoreLifeline`):
  241 + - 用于判断是否达到提成门槛
  242 + - 如果门店业绩 < 门店生命线,则无提成
  243 + - 如果门店业绩 ≥ 门店生命线,则可以计算提成
  244 +
  245 +2. **提成阶梯**(`lq_md_general_manager_lifeline` 表的 Lifeline1/2/3):
  246 + - 用于计算提成金额的阶梯
  247 + - 只有在达到门店生命线的前提下,才使用提成阶梯计算提成
  248 +
  249 +**重要说明**:
  250 +- 门店生命线是**必须设置的**,未设置应报错
  251 +- 提成阶梯1和提成比例1是必填项,未设置应报错
  252 +- 提成阶梯2、3和对应的提成比例为可选项
  253 +
  254 +---
  255 +
  256 +## 🔄 计算流程
  257 +
  258 +### 1. 数据准备
  259 +
  260 +1. **获取总经理/经理列表和门店归属**:
  261 + - 从 `lq_md_general_manager_lifeline` 表获取当月所有的经理和总经理
  262 + - 按总经理/经理ID分组,得到每个总经理/经理管理的门店列表
  263 +
  264 +2. **获取门店生命线**:
  265 + - 从 `lq_md_target` 表获取每个门店的生命线(`F_StoreLifeline`)
  266 + - 门店生命线是必须设置的,未设置应报错
  267 +
  268 +3. **获取提成阶梯设置**:
  269 + - 从 `lq_md_general_manager_lifeline` 表获取每个门店的提成阶梯设置
  270 + - 提成阶梯1和提成比例1是必填项,未设置应报错
  271 +
  272 +4. **获取门店总业绩**:
  273 + - 从 `lq_kd_kdjlb` 表统计每个门店的开单业绩(`sfyj`)
  274 + - 从 `lq_hytk_hytk` 表统计每个门店的退卡业绩(`F_ActualRefundAmount` 或 `tkje`)
  275 + - 计算每个门店的总业绩 = 开单业绩 - 退卡业绩
  276 +
  277 +### 2. 工资计算
  278 +
  279 +**遍历每个总经理/经理**:
  280 +
  281 +1. **初始化工资统计对象**:
  282 + - 底薪 = 4000元
  283 + - 总提成 = 0
  284 +
  285 +2. **遍历该总经理/经理管理的每个门店**:
  286 +
  287 + a. **判断是否达到提成门槛**:
  288 + - 获取该门店的生命线(`lq_md_target.F_StoreLifeline`)
  289 + - 获取该门店的总业绩
  290 + - 如果门店业绩 < 门店生命线 → 该门店提成 = 0,跳过
  291 + - 如果门店业绩 ≥ 门店生命线 → 继续计算提成
  292 +
  293 + b. **计算该门店的提成**(如果达到门槛):
  294 + - 获取该门店的提成阶梯设置(`lq_md_general_manager_lifeline`)
  295 + - 根据门店业绩和提成阶梯,使用分段累进方式计算提成金额
  296 + - 累加到总提成
  297 +
  298 +3. **计算最终工资**:
  299 + - 应发工资 = 底薪 + 总提成
  300 +
  301 +### 3. 数据保存
  302 +
  303 +- 将计算结果保存到工资统计表中(待创建表结构)
  304 +- 如果已存在当月数据,则更新;否则插入新数据
  305 +
  306 +---
  307 +
  308 +## 📝 注意事项
  309 +
  310 +1. **数据一致性**:
  311 + - 门店业绩计算逻辑必须与其他统计接口保持一致
  312 + - 门店生命线必须与门店目标表保持一致
  313 +
  314 +2. **数据校验**:
  315 + - 门店生命线(`lq_md_target.F_StoreLifeline`)必须设置,未设置应报错
  316 + - 提成阶梯1(`lq_md_general_manager_lifeline.F_Lifeline1`)必须设置,未设置应报错
  317 + - 提成比例1(`lq_md_general_manager_lifeline.F_CommissionRate1`)必须设置,未设置应报错
  318 + - 如果门店未在 `lq_md_general_manager_lifeline` 表中设置,则无法计算该门店的提成
  319 +
  320 +3. **边界情况**:
  321 + - 如果门店没有业绩数据,业绩为0,未达到门店生命线,提成为0
  322 + - 如果门店业绩 < 门店生命线,提成为0
  323 + - 如果总经理/经理没有管理的门店,总提成为0,应发工资 = 底薪(4000元)
  324 + - 如果提成阶梯2或提成阶梯3未设置,则只使用提成阶梯1计算
  325 +
  326 +4. **计算精度**:
  327 + - 涉及金额计算时,建议保留2位小数
  328 + - 提成比例以百分比形式存储(如:1.0表示1%)
  329 +
  330 +5. **总经理和经理**:
  331 + - 总经理和经理的计算规则相同
  332 + - 都使用门店生命线来判断是否达到提成门槛
  333 + - 都使用 `lq_md_general_manager_lifeline` 表的提成阶梯来计算提成
  334 +
  335 +6. **保底工资**:
  336 + - 暂时不考虑保底工资规则
  337 +
  338 +---
  339 +
  340 +## 📋 参考文档
  341 +
  342 +- [项目信息-薪酬规则与名词解释.md](./项目信息-薪酬规则与名词解释.md)
  343 +- [店长工资计算规则梳理.md](./店长工资计算规则梳理.md)
  344 +- [主任工资计算规则梳理.md](./主任工资计算规则梳理.md)
  345 +- [大项目部老师工资计算规则梳理.md](./大项目部老师工资计算规则梳理.md)
  346 +
  347 +---
  348 +
  349 +**最后更新时间**:2025年1月
  350 +
... ...
大项目部老师工资计算规则梳理.md 0 → 100644
  1 +# 大项目部老师工资计算规则梳理
  2 +
  3 +## 📋 目录
  4 +- [计算规则](#计算规则)
  5 +- [数据来源](#数据来源)
  6 +- [归属规则](#归属规则)
  7 +- [计算流程](#计算流程)
  8 +
  9 +---
  10 +
  11 +## 💰 计算规则
  12 +
  13 +### 1. 底薪规则
  14 +
  15 +**固定底薪**:3000元
  16 +
  17 +- 无论业绩多少,底薪固定为3000元
  18 +- 不设档位,不设条件
  19 +
  20 +---
  21 +
  22 +### 2. 业绩提成规则(阶梯式)
  23 +
  24 +业绩提成基于**总业绩**计算,采用阶梯式方式:
  25 +
  26 +| 总业绩范围 | 提成比例 | 说明 |
  27 +|-----------|---------|------|
  28 +| ≤ 20万 | 0% | 无提成 |
  29 +| > 20万 且 ≤ 100万 | 2% | 按2%计算提成 |
  30 +| > 100万 | 2.5% | 按2.5%计算提成 |
  31 +
  32 +**计算说明**:
  33 +- 提成金额 = 总业绩 × 对应提成比例
  34 +- 采用阶梯式计算,不同区间按不同比例计算
  35 +- **注意**:不是分段累进,而是整个总业绩按对应比例计算
  36 +
  37 +**示例**:
  38 +- 总业绩 = 15万 → 提成 = 0(无提成)
  39 +- 总业绩 = 50万 → 提成 = 50万 × 2% = 1万
  40 +- 总业绩 = 120万 → 提成 = 120万 × 2.5% = 3万
  41 +
  42 +---
  43 +
  44 +## 📊 数据来源
  45 +
  46 +### 总业绩(TotalPerformance)
  47 +
  48 +**定义**:门店总业绩 = 开单业绩 - 退卡业绩
  49 +
  50 +**重要说明**:
  51 +- **大项目部老师计算的是门店总业绩,不是个人业绩**
  52 +- 大项目部老师和科技部老师是两个不同的岗位
  53 +- 根据归属表 `lq_md_major_project_teacher_assignment` 确定该老师在某个月归属于哪个门店
  54 +- 然后统计该门店在该月的总业绩(开单业绩 - 退卡业绩)
  55 +
  56 +**数据来源表及字段**:
  57 +
  58 +| 业绩类型 | 数据表 | 字段 | 说明 |
  59 +|---------|--------|------|------|
  60 +| **开单业绩** | `lq_kd_kdjlb` | `sfyj` | 门店开单实付金额(根据归属表关联的门店ID统计) |
  61 +| **退卡业绩** | `lq_hytk_hytk` | `F_ActualRefundAmount` 或 `tkje` | 门店退卡金额(根据归属表关联的门店ID统计) |
  62 +
  63 +**计算方式**:
  64 +```sql
  65 +门店总业绩 = SUM(门店开单实付金额) - SUM(门店退卡金额)
  66 +```
  67 +
  68 +**过滤条件**:
  69 +- 所有表记录必须满足:`F_IsEffective = 1`(有效记录)
  70 +- 按统计月份(YYYYMM格式)过滤时间范围
  71 +- **关键**:根据 `lq_md_major_project_teacher_assignment` 归属表关联
  72 + - 关联条件:`lq_md_major_project_teacher_assignment.F_TeacherId = 大项目部老师ID`
  73 + - 关联条件:`lq_md_major_project_teacher_assignment.F_Year = 统计年份`
  74 + - 关联条件:`lq_md_major_project_teacher_assignment.F_Month = 统计月份`
  75 + - 关联条件:`lq_md_major_project_teacher_assignment.F_StoreId = 门店ID`
  76 + - 然后统计该门店在该月的开单业绩和退卡业绩
  77 +
  78 +**注意**:
  79 +- 大项目部老师的归属每个月可能不一样
  80 +- 需要根据归属表来确定该老师在该月份归属于哪个门店
  81 +- **统计的是门店的总业绩,不是个人业绩**
  82 +- 如果同一个老师在某个月归属于多个门店,需要合并多个门店的业绩
  83 +
  84 +---
  85 +
  86 +## 🔗 归属规则
  87 +
  88 +### 归属表:`lq_md_major_project_teacher_assignment`
  89 +
  90 +**表结构**:
  91 +- `F_Id`:主键ID
  92 +- `F_StoreId`:门店ID
  93 +- `F_Year`:年份(YYYY格式)
  94 +- `F_Month`:月份(MM格式)
  95 +- `F_TeacherId`:大项目部老师用户ID
  96 +- `F_EducationTeacherId`:教育部老师用户ID(可选)
  97 +- `F_Remark`:备注说明
  98 +
  99 +**归属规则**:
  100 +- 每个大项目部老师每个月可能归属于不同的门店
  101 +- 归属信息存储在 `lq_md_major_project_teacher_assignment` 表中
  102 +- 统计工资时,需要根据该表来确定:
  103 + 1. 该老师在该月份归属于哪个门店
  104 + 2. 该门店的业绩数据中,哪些属于该老师
  105 +
  106 +**关联逻辑**:
  107 +1. 根据 `F_Year` 和 `F_Month` 确定统计月份
  108 +2. 根据 `F_TeacherId` 确定大项目部老师
  109 +3. 根据 `F_StoreId` 确定归属门店
  110 +4. 统计该门店在该月份的业绩数据(开单、消耗、退卡)
  111 +5. 根据业绩表中的老师ID字段关联到该大项目部老师
  112 +
  113 +**业绩关联方式**(待确认):
  114 +- 开单业绩:`lq_kd_kjbsyj` 表中的 `kjblszh` 或 `kjbls` 字段
  115 +- 消耗业绩:`lq_xh_kjbsyj` 表中的 `kjblszh` 或 `kjbls` 字段
  116 +- 退卡业绩:`lq_hytk_kjbsyj` 表中的 `kjblszh` 或 `kjbls` 字段
  117 +
  118 +**注意**:
  119 +- 需要确认大项目部老师的业绩是如何关联的
  120 +- 可能需要通过 `BASE_USER` 表的账号或ID来关联
  121 +- 或者通过其他关联字段来确定
  122 +
  123 +---
  124 +
  125 +## ⚠️ 特殊规则
  126 +
  127 +### 离职员工规则
  128 +
  129 +**适用条件**:
  130 +- 员工状态:`BASE_USER.F_IsOnJob = 0`(离职)
  131 +
  132 +**计算规则**:
  133 +- 离职员工的工资计算规则待确认
  134 +- 可能需要特殊处理(如:只计算在职期间的业绩)
  135 +
  136 +---
  137 +
  138 +## 📝 计算流程
  139 +
  140 +### 1. 数据准备阶段
  141 +
  142 +1. **获取归属信息**
  143 + - 从 `lq_md_major_project_teacher_assignment` 表查询指定月份的所有归属记录
  144 + - 按 `F_TeacherId` 和 `F_StoreId` 分组
  145 +
  146 +2. **获取业绩数据**
  147 + - 开单业绩:从 `lq_kd_kjbsyj` 表统计
  148 + - 消耗业绩:从 `lq_xh_kjbsyj` 表统计
  149 + - 退卡业绩:从 `lq_hytk_kjbsyj` 表统计
  150 + - 按统计月份过滤时间范围
  151 + - 按归属门店和老师ID关联
  152 +
  153 +3. **获取员工信息**
  154 + - 从 `BASE_USER` 表获取员工基本信息
  155 + - 包括:姓名、账号、门店信息等
  156 +
  157 +### 2. 业绩统计阶段
  158 +
  159 +1. **计算开单业绩**
  160 + - 根据归属表关联的开单业绩数据
  161 + - 按大项目部老师ID汇总
  162 +
  163 +2. **计算消耗业绩**
  164 + - 根据归属表关联的消耗业绩数据
  165 + - 按大项目部老师ID汇总
  166 +
  167 +3. **计算退卡业绩**
  168 + - 根据归属表关联的退卡业绩数据
  169 + - 按大项目部老师ID汇总
  170 +
  171 +4. **计算总业绩**
  172 + - 总业绩 = 开单业绩 + 消耗业绩 + 退卡业绩
  173 +
  174 +### 3. 工资计算阶段
  175 +
  176 +1. **计算底薪**
  177 + - 底薪 = 3000元(固定)
  178 +
  179 +2. **计算业绩提成**
  180 + - 判断总业绩范围:
  181 + - ≤ 20万:提成比例 = 0%
  182 + - > 20万 且 ≤ 100万:提成比例 = 2%
  183 + - > 100万:提成比例 = 2.5%
  184 + - 提成金额 = 总业绩 × 提成比例
  185 +
  186 +3. **计算其他收入**
  187 + - 手工费、车补、少休费、全勤奖等(默认0)
  188 +
  189 +4. **计算核算应发工资**
  190 + - 核算应发工资 = 底薪 + 提成合计 + 其他收入
  191 +
  192 +5. **计算最终应发工资**
  193 + - 最终应发工资 = MAX(核算应发工资, 保底工资)
  194 + - 如果没有保底工资,则等于核算应发工资
  195 +
  196 +6. **计算实发工资**
  197 + - 实发工资 = 最终应发工资 - 扣款合计 + 补贴合计 + 奖金
  198 +
  199 +### 4. 数据保存阶段
  200 +
  201 +1. **保存工资统计记录**
  202 + - 保存到 `lq_major_project_teacher_salary_statistics` 表
  203 + - 确保同一员工同一月份只有一条记录(唯一索引)
  204 +
  205 +2. **更新状态**
  206 + - 设置 `F_IsLocked = 0`(未锁定)
  207 + - 设置 `F_IsTerminated`(根据员工状态)
  208 +
  209 +---
  210 +
  211 +## ❓ 待确认问题
  212 +
  213 +1. **业绩关联方式**
  214 + - 大项目部老师的业绩是如何关联的?
  215 + - 是通过 `BASE_USER` 表的账号或ID来关联吗?
  216 + - 还是通过其他字段来确定?
  217 +
  218 +2. **归属门店的业绩统计**
  219 + - 如果一个大项目部老师在某个月归属于某个门店,那么:
  220 + - 该门店的所有业绩都算他的吗?
  221 + - 还是只统计该门店中他参与的业绩?
  222 +
  223 +3. **离职员工处理**
  224 + - 离职员工的工资计算规则是什么?
  225 + - 是否需要特殊处理?
  226 +
  227 +4. **保底工资**
  228 + - 大项目部老师是否有保底工资?
  229 + - 保底工资的计算规则是什么?
  230 +
  231 +---
  232 +
  233 +## 📌 总结
  234 +
  235 +### 核心规则
  236 +1. **底薪**:固定3000元
  237 +2. **业绩提成**:阶梯式计算
  238 + - ≤ 20万:0%
  239 + - > 20万 且 ≤ 100万:2%
  240 + - > 100万:2.5%
  241 +
  242 +### 关键点
  243 +1. **归属表关联**:需要根据 `lq_md_major_project_teacher_assignment` 表来确定归属关系
  244 +2. **业绩统计**:需要根据归属表关联的业绩数据来统计
  245 +3. **月份归属**:每个月的归属可能不一样,需要按月统计
  246 +
  247 +### 下一步
  248 +1. 确认业绩关联方式
  249 +2. 确认归属门店的业绩统计规则
  250 +3. 确认离职员工处理规则
  251 +4. 确认保底工资规则
  252 +5. 实现计算逻辑
  253 +
... ...