Commit ddcc459b5fd6a37683fabaa8b5b96a672f5f9777

Authored by “wangming”
1 parent 6f996def

修改大项目部老师归属设置表:将月份字段拆分为年份和月份两个字段,便于后续统计

Showing 17 changed files with 2847 additions and 0 deletions
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentCrInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
  4 +{
  5 + /// <summary>
  6 + /// 门店大项目部老师归属设置创建输入
  7 + /// </summary>
  8 + public class LqMdMajorProjectTeacherAssignmentCrInput
  9 + {
  10 + /// <summary>
  11 + /// 门店ID
  12 + /// </summary>
  13 + [Required(ErrorMessage = "门店ID不能为空")]
  14 + public string storeId { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 年份(YYYY格式)
  18 + /// </summary>
  19 + [Required(ErrorMessage = "年份不能为空")]
  20 + [StringLength(4, MinimumLength = 4, ErrorMessage = "年份格式必须为YYYY")]
  21 + public string year { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 月份(MM格式)
  25 + /// </summary>
  26 + [Required(ErrorMessage = "月份不能为空")]
  27 + [StringLength(2, MinimumLength = 2, ErrorMessage = "月份格式必须为MM")]
  28 + public string month { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 大项目部老师用户ID
  32 + /// </summary>
  33 + [Required(ErrorMessage = "大项目部老师用户ID不能为空")]
  34 + public string teacherId { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 备注说明
  38 + /// </summary>
  39 + public string remark { get; set; }
  40 + }
  41 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentInfoOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
  4 +{
  5 + /// <summary>
  6 + /// 门店大项目部老师归属设置信息输出
  7 + /// </summary>
  8 + public class LqMdMajorProjectTeacherAssignmentInfoOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店ID
  17 + /// </summary>
  18 + public string storeId { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店名称
  22 + /// </summary>
  23 + public string storeName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 年份(YYYY格式)
  27 + /// </summary>
  28 + public string year { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 月份(MM格式)
  32 + /// </summary>
  33 + public string month { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 大项目部老师用户ID
  37 + /// </summary>
  38 + public string teacherId { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 大项目部老师姓名
  42 + /// </summary>
  43 + public string teacherName { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 备注说明
  47 + /// </summary>
  48 + public string remark { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 创建时间
  52 + /// </summary>
  53 + public DateTime? createTime { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 创建人ID
  57 + /// </summary>
  58 + public string createUserId { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 更新时间
  62 + /// </summary>
  63 + public DateTime? updateTime { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 更新人ID
  67 + /// </summary>
  68 + public string updateUserId { get; set; }
  69 + }
  70 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
  4 +{
  5 + /// <summary>
  6 + /// 门店大项目部老师归属设置列表输出
  7 + /// </summary>
  8 + public class LqMdMajorProjectTeacherAssignmentListOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店ID
  17 + /// </summary>
  18 + public string storeId { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店名称
  22 + /// </summary>
  23 + public string storeName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 年份(YYYY格式)
  27 + /// </summary>
  28 + public string year { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 月份(MM格式)
  32 + /// </summary>
  33 + public string month { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 大项目部老师用户ID
  37 + /// </summary>
  38 + public string teacherId { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 大项目部老师姓名
  42 + /// </summary>
  43 + public string teacherName { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 备注说明
  47 + /// </summary>
  48 + public string remark { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 创建时间
  52 + /// </summary>
  53 + public DateTime? createTime { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 创建人ID
  57 + /// </summary>
  58 + public string createUserId { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 更新时间
  62 + /// </summary>
  63 + public DateTime? updateTime { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 更新人ID
  67 + /// </summary>
  68 + public string updateUserId { get; set; }
  69 + }
  70 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
  4 +{
  5 + /// <summary>
  6 + /// 门店大项目部老师归属设置列表查询输入
  7 + /// </summary>
  8 + public class LqMdMajorProjectTeacherAssignmentListQueryInput : PageInputBase
  9 + {
  10 + /// <summary>
  11 + /// 门店ID
  12 + /// </summary>
  13 + public string storeId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 年份(YYYY格式)
  17 + /// </summary>
  18 + public string year { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 月份(MM格式)
  22 + /// </summary>
  23 + public string month { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 大项目部老师用户ID
  27 + /// </summary>
  28 + public string teacherId { get; set; }
  29 + }
  30 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdMajorProjectTeacherAssignment/LqMdMajorProjectTeacherAssignmentUpInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment
  4 +{
  5 + /// <summary>
  6 + /// 门店大项目部老师归属设置更新输入
  7 + /// </summary>
  8 + public class LqMdMajorProjectTeacherAssignmentUpInput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + [Required(ErrorMessage = "主键ID不能为空")]
  14 + public string id { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 门店ID
  18 + /// </summary>
  19 + [Required(ErrorMessage = "门店ID不能为空")]
  20 + public string storeId { get; set; }
  21 +
  22 + /// <summary>
  23 + /// 年份(YYYY格式)
  24 + /// </summary>
  25 + [Required(ErrorMessage = "年份不能为空")]
  26 + [StringLength(4, MinimumLength = 4, ErrorMessage = "年份格式必须为YYYY")]
  27 + public string year { get; set; }
  28 +
  29 + /// <summary>
  30 + /// 月份(MM格式)
  31 + /// </summary>
  32 + [Required(ErrorMessage = "月份不能为空")]
  33 + [StringLength(2, MinimumLength = 2, ErrorMessage = "月份格式必须为MM")]
  34 + public string month { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 大项目部老师用户ID
  38 + /// </summary>
  39 + [Required(ErrorMessage = "大项目部老师用户ID不能为空")]
  40 + public string teacherId { get; set; }
  41 +
  42 + /// <summary>
  43 + /// 备注说明
  44 + /// </summary>
  45 + public string remark { get; set; }
  46 + }
  47 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherSalaryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary
  5 +{
  6 + /// <summary>
  7 + /// 科技老师工资查询参数
  8 + /// </summary>
  9 + public class TechTeacherSalaryInput : 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/LqTechTeacherSalary/TechTeacherSalaryOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary
  4 +{
  5 + /// <summary>
  6 + /// 科技老师工资输出
  7 + /// </summary>
  8 + public class TechTeacherSalaryOutput
  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 ProjectCount { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 底薪档位
  77 + /// </summary>
  78 + public int BaseSalaryLevel { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 底薪
  82 + /// </summary>
  83 + public decimal BaseSalary { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 业绩提成比例
  87 + /// </summary>
  88 + public decimal PerformanceCommissionRate { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 业绩提成金额
  92 + /// </summary>
  93 + public decimal PerformanceCommissionAmount { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 消耗提成比例
  97 + /// </summary>
  98 + public decimal ConsumeCommissionRate { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 消耗提成金额
  102 + /// </summary>
  103 + public decimal ConsumeCommissionAmount { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 手工费
  107 + /// </summary>
  108 + public decimal HandworkFee { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 在店天数
  112 + /// </summary>
  113 + public decimal WorkingDays { get; set; }
  114 +
  115 + /// <summary>
  116 + /// 请假天数
  117 + /// </summary>
  118 + public decimal LeaveDays { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 提成合计
  122 + /// </summary>
  123 + public decimal TotalCommission { get; set; }
  124 +
  125 + /// <summary>
  126 + /// 车补
  127 + /// </summary>
  128 + public decimal TransportationAllowance { get; set; }
  129 +
  130 + /// <summary>
  131 + /// 少休费
  132 + /// </summary>
  133 + public decimal LessRest { get; set; }
  134 +
  135 + /// <summary>
  136 + /// 全勤奖
  137 + /// </summary>
  138 + public decimal FullAttendance { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 核算应发工资
  142 + /// </summary>
  143 + public decimal CalculatedGrossSalary { get; set; }
  144 +
  145 + /// <summary>
  146 + /// 保底工资
  147 + /// </summary>
  148 + public decimal GuaranteedSalary { get; set; }
  149 +
  150 + /// <summary>
  151 + /// 保底请假扣款
  152 + /// </summary>
  153 + public decimal GuaranteedLeaveDeduction { get; set; }
  154 +
  155 + /// <summary>
  156 + /// 保底底薪
  157 + /// </summary>
  158 + public decimal GuaranteedBaseSalary { get; set; }
  159 +
  160 + /// <summary>
  161 + /// 保底补差
  162 + /// </summary>
  163 + public decimal GuaranteedSupplement { get; set; }
  164 +
  165 + /// <summary>
  166 + /// 最终应发工资
  167 + /// </summary>
  168 + public decimal FinalGrossSalary { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 当月培训补贴
  172 + /// </summary>
  173 + public decimal MonthlyTrainingSubsidy { get; set; }
  174 +
  175 + /// <summary>
  176 + /// 当月交通补贴
  177 + /// </summary>
  178 + public decimal MonthlyTransportSubsidy { get; set; }
  179 +
  180 + /// <summary>
  181 + /// 上月培训补贴
  182 + /// </summary>
  183 + public decimal LastMonthTrainingSubsidy { get; set; }
  184 +
  185 + /// <summary>
  186 + /// 上月交通补贴
  187 + /// </summary>
  188 + public decimal LastMonthTransportSubsidy { get; set; }
  189 +
  190 + /// <summary>
  191 + /// 补贴合计
  192 + /// </summary>
  193 + public decimal TotalSubsidy { get; set; }
  194 +
  195 + /// <summary>
  196 + /// 缺卡扣款
  197 + /// </summary>
  198 + public decimal MissingCard { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 迟到扣款
  202 + /// </summary>
  203 + public decimal LateArrival { get; set; }
  204 +
  205 + /// <summary>
  206 + /// 请假扣款
  207 + /// </summary>
  208 + public decimal LeaveDeduction { get; set; }
  209 +
  210 + /// <summary>
  211 + /// 扣社保
  212 + /// </summary>
  213 + public decimal SocialInsuranceDeduction { get; set; }
  214 +
  215 + /// <summary>
  216 + /// 扣除奖励
  217 + /// </summary>
  218 + public decimal RewardDeduction { get; set; }
  219 +
  220 + /// <summary>
  221 + /// 扣住宿费
  222 + /// </summary>
  223 + public decimal AccommodationDeduction { get; set; }
  224 +
  225 + /// <summary>
  226 + /// 扣学习期费用
  227 + /// </summary>
  228 + public decimal StudyPeriodDeduction { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 扣工作服费用
  232 + /// </summary>
  233 + public decimal WorkClothesDeduction { get; set; }
  234 +
  235 + /// <summary>
  236 + /// 扣款合计
  237 + /// </summary>
  238 + public decimal TotalDeduction { get; set; }
  239 +
  240 + /// <summary>
  241 + /// 发奖金
  242 + /// </summary>
  243 + public decimal Bonus { get; set; }
  244 +
  245 + /// <summary>
  246 + /// 退手机押金
  247 + /// </summary>
  248 + public decimal ReturnPhoneDeposit { get; set; }
  249 +
  250 + /// <summary>
  251 + /// 退住宿押金
  252 + /// </summary>
  253 + public decimal ReturnAccommodationDeposit { get; set; }
  254 +
  255 + /// <summary>
  256 + /// 实发工资
  257 + /// </summary>
  258 + public decimal ActualSalary { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 当月是否发放
  262 + /// </summary>
  263 + public string MonthlyPaymentStatus { get; set; }
  264 +
  265 + /// <summary>
  266 + /// 支付金额
  267 + /// </summary>
  268 + public decimal PaidAmount { get; set; }
  269 +
  270 + /// <summary>
  271 + /// 待支付金额
  272 + /// </summary>
  273 + public decimal PendingAmount { get; set; }
  274 +
  275 + /// <summary>
  276 + /// 补发上月
  277 + /// </summary>
  278 + public decimal LastMonthSupplement { get; set; }
  279 +
  280 + /// <summary>
  281 + /// 当月支付总额
  282 + /// </summary>
  283 + public decimal MonthlyTotalPayment { get; set; }
  284 +
  285 + /// <summary>
  286 + /// 是否锁定
  287 + /// </summary>
  288 + public int IsLocked { get; set; }
  289 +
  290 + /// <summary>
  291 + /// 是否离职
  292 + /// </summary>
  293 + public int IsTerminated { get; set; }
  294 +
  295 + /// <summary>
  296 + /// 更新时间
  297 + /// </summary>
  298 + public DateTime UpdateTime { get; set; }
  299 +
  300 + /// <summary>
  301 + /// 门店类型
  302 + /// </summary>
  303 + public int? StoreType { get; set; }
  304 +
  305 + /// <summary>
  306 + /// 门店类别
  307 + /// </summary>
  308 + public int? StoreCategory { get; set; }
  309 +
  310 + /// <summary>
  311 + /// 是否新店
  312 + /// </summary>
  313 + public string IsNewStore { get; set; }
  314 +
  315 + /// <summary>
  316 + /// 新店保护阶段
  317 + /// </summary>
  318 + public int NewStoreProtectionStage { get; set; }
  319 + }
  320 +}
  321 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_md_major_project_teacher_assignment/LqMdMajorProjectTeacherAssignmentEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_md_major_project_teacher_assignment
  6 +{
  7 + /// <summary>
  8 + /// 门店大项目部老师归属设置
  9 + /// </summary>
  10 + [SugarTable("lq_md_major_project_teacher_assignment")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqMdMajorProjectTeacherAssignmentEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_StoreId")]
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 年份(YYYY格式)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_Year")]
  30 + public string Year { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 月份(MM格式)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_Month")]
  36 + public string Month { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 大项目部老师用户ID
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_TeacherId")]
  42 + public string TeacherId { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 备注说明
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_Remark")]
  48 + public string Remark { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 创建时间
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_CreateTime")]
  54 + public DateTime? CreateTime { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 创建人ID
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_CreateUserId")]
  60 + public string CreateUserId { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 更新时间
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_UpdateTime")]
  66 + public DateTime? UpdateTime { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 更新人ID
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_UpdateUserId")]
  72 + public string UpdateUserId { get; set; }
  73 + }
  74 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_tech_teacher_salary_statistics/LqTechTeacherSalaryStatisticsEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_tech_teacher_salary_statistics
  6 +{
  7 + /// <summary>
  8 + /// 科技部老师工资统计表
  9 + /// </summary>
  10 + [SugarTable("lq_tech_teacher_salary_statistics")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqTechTeacherSalaryStatisticsEntity
  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 + /// 项目数(用于底薪计算,主要来源于耗卡品相次数)
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_ProjectCount", DecimalDigits = 2)]
  90 + public decimal ProjectCount { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 底薪档位(1=第一档2500, 2=第二档3000, 3=第三档3500)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_BaseSalaryLevel")]
  96 + public int BaseSalaryLevel { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 健康师底薪(根据项目数和业绩计算)
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_BaseSalary", DecimalDigits = 2)]
  102 + public decimal BaseSalary { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 业绩提成比例(百分比,如2表示2%)
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_PerformanceCommissionRate", DecimalDigits = 2)]
  108 + public decimal PerformanceCommissionRate { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 业绩提成金额
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_PerformanceCommissionAmount", DecimalDigits = 2)]
  114 + public decimal PerformanceCommissionAmount { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 消耗提成比例(百分比,如0.5表示0.5%,负数表示扣除,如-300表示扣除300元)
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_ConsumeCommissionRate", DecimalDigits = 2)]
  120 + public decimal ConsumeCommissionRate { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 消耗提成金额(可能为负数,如扣除300元)
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_ConsumeCommissionAmount", DecimalDigits = 2)]
  126 + public decimal ConsumeCommissionAmount { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 手工费
  130 + /// </summary>
  131 + [SugarColumn(ColumnName = "F_HandworkFee", DecimalDigits = 2)]
  132 + public decimal HandworkFee { 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_TotalCommission", DecimalDigits = 2)]
  150 + public decimal TotalCommission { get; set; }
  151 +
  152 + /// <summary>
  153 + /// 车补
  154 + /// </summary>
  155 + [SugarColumn(ColumnName = "F_TransportationAllowance", DecimalDigits = 2)]
  156 + public decimal TransportationAllowance { get; set; }
  157 +
  158 + /// <summary>
  159 + /// 少休费
  160 + /// </summary>
  161 + [SugarColumn(ColumnName = "F_LessRest", DecimalDigits = 2)]
  162 + public decimal LessRest { get; set; }
  163 +
  164 + /// <summary>
  165 + /// 全勤奖
  166 + /// </summary>
  167 + [SugarColumn(ColumnName = "F_FullAttendance", DecimalDigits = 2)]
  168 + public decimal FullAttendance { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 核算应发工资
  172 + /// </summary>
  173 + [SugarColumn(ColumnName = "F_CalculatedGrossSalary", DecimalDigits = 2)]
  174 + public decimal CalculatedGrossSalary { get; set; }
  175 +
  176 + /// <summary>
  177 + /// 保底工资
  178 + /// </summary>
  179 + [SugarColumn(ColumnName = "F_GuaranteedSalary", DecimalDigits = 2)]
  180 + public decimal GuaranteedSalary { get; set; }
  181 +
  182 + /// <summary>
  183 + /// 保底请假扣款
  184 + /// </summary>
  185 + [SugarColumn(ColumnName = "F_GuaranteedLeaveDeduction", DecimalDigits = 2)]
  186 + public decimal GuaranteedLeaveDeduction { get; set; }
  187 +
  188 + /// <summary>
  189 + /// 保底底薪
  190 + /// </summary>
  191 + [SugarColumn(ColumnName = "F_GuaranteedBaseSalary", DecimalDigits = 2)]
  192 + public decimal GuaranteedBaseSalary { get; set; }
  193 +
  194 + /// <summary>
  195 + /// 保底补差
  196 + /// </summary>
  197 + [SugarColumn(ColumnName = "F_GuaranteedSupplement", DecimalDigits = 2)]
  198 + public decimal GuaranteedSupplement { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 最终应发工资
  202 + /// </summary>
  203 + [SugarColumn(ColumnName = "F_FinalGrossSalary", DecimalDigits = 2)]
  204 + public decimal FinalGrossSalary { get; set; }
  205 +
  206 + /// <summary>
  207 + /// 当月培训补贴
  208 + /// </summary>
  209 + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy", DecimalDigits = 2)]
  210 + public decimal MonthlyTrainingSubsidy { get; set; }
  211 +
  212 + /// <summary>
  213 + /// 当月交通补贴
  214 + /// </summary>
  215 + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy", DecimalDigits = 2)]
  216 + public decimal MonthlyTransportSubsidy { get; set; }
  217 +
  218 + /// <summary>
  219 + /// 上月培训补贴
  220 + /// </summary>
  221 + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy", DecimalDigits = 2)]
  222 + public decimal LastMonthTrainingSubsidy { get; set; }
  223 +
  224 + /// <summary>
  225 + /// 上月交通补贴
  226 + /// </summary>
  227 + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy", DecimalDigits = 2)]
  228 + public decimal LastMonthTransportSubsidy { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 补贴合计
  232 + /// </summary>
  233 + [SugarColumn(ColumnName = "F_TotalSubsidy", DecimalDigits = 2)]
  234 + public decimal TotalSubsidy { get; set; }
  235 +
  236 + /// <summary>
  237 + /// 缺卡扣款
  238 + /// </summary>
  239 + [SugarColumn(ColumnName = "F_MissingCard", DecimalDigits = 2)]
  240 + public decimal MissingCard { get; set; }
  241 +
  242 + /// <summary>
  243 + /// 迟到扣款
  244 + /// </summary>
  245 + [SugarColumn(ColumnName = "F_LateArrival", DecimalDigits = 2)]
  246 + public decimal LateArrival { get; set; }
  247 +
  248 + /// <summary>
  249 + /// 请假扣款
  250 + /// </summary>
  251 + [SugarColumn(ColumnName = "F_LeaveDeduction", DecimalDigits = 2)]
  252 + public decimal LeaveDeduction { get; set; }
  253 +
  254 + /// <summary>
  255 + /// 扣社保
  256 + /// </summary>
  257 + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction", DecimalDigits = 2)]
  258 + public decimal SocialInsuranceDeduction { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 扣除奖励
  262 + /// </summary>
  263 + [SugarColumn(ColumnName = "F_RewardDeduction", DecimalDigits = 2)]
  264 + public decimal RewardDeduction { get; set; }
  265 +
  266 + /// <summary>
  267 + /// 扣住宿费
  268 + /// </summary>
  269 + [SugarColumn(ColumnName = "F_AccommodationDeduction", DecimalDigits = 2)]
  270 + public decimal AccommodationDeduction { get; set; }
  271 +
  272 + /// <summary>
  273 + /// 扣学习期费用
  274 + /// </summary>
  275 + [SugarColumn(ColumnName = "F_StudyPeriodDeduction", DecimalDigits = 2)]
  276 + public decimal StudyPeriodDeduction { get; set; }
  277 +
  278 + /// <summary>
  279 + /// 扣工作服费用
  280 + /// </summary>
  281 + [SugarColumn(ColumnName = "F_WorkClothesDeduction", DecimalDigits = 2)]
  282 + public decimal WorkClothesDeduction { get; set; }
  283 +
  284 + /// <summary>
  285 + /// 扣款合计
  286 + /// </summary>
  287 + [SugarColumn(ColumnName = "F_TotalDeduction", DecimalDigits = 2)]
  288 + public decimal TotalDeduction { get; set; }
  289 +
  290 + /// <summary>
  291 + /// 发奖金
  292 + /// </summary>
  293 + [SugarColumn(ColumnName = "F_Bonus", DecimalDigits = 2)]
  294 + public decimal Bonus { get; set; }
  295 +
  296 + /// <summary>
  297 + /// 退手机押金
  298 + /// </summary>
  299 + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit", DecimalDigits = 2)]
  300 + public decimal ReturnPhoneDeposit { get; set; }
  301 +
  302 + /// <summary>
  303 + /// 退住宿押金
  304 + /// </summary>
  305 + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit", DecimalDigits = 2)]
  306 + public decimal ReturnAccommodationDeposit { get; set; }
  307 +
  308 + /// <summary>
  309 + /// 实发工资
  310 + /// </summary>
  311 + [SugarColumn(ColumnName = "F_ActualSalary", DecimalDigits = 2)]
  312 + public decimal ActualSalary { get; set; }
  313 +
  314 + /// <summary>
  315 + /// 当月是否发放
  316 + /// </summary>
  317 + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")]
  318 + public string MonthlyPaymentStatus { get; set; }
  319 +
  320 + /// <summary>
  321 + /// 支付金额
  322 + /// </summary>
  323 + [SugarColumn(ColumnName = "F_PaidAmount", DecimalDigits = 2)]
  324 + public decimal PaidAmount { get; set; }
  325 +
  326 + /// <summary>
  327 + /// 待支付金额
  328 + /// </summary>
  329 + [SugarColumn(ColumnName = "F_PendingAmount", DecimalDigits = 2)]
  330 + public decimal PendingAmount { get; set; }
  331 +
  332 + /// <summary>
  333 + /// 补发上月
  334 + /// </summary>
  335 + [SugarColumn(ColumnName = "F_LastMonthSupplement", DecimalDigits = 2)]
  336 + public decimal LastMonthSupplement { get; set; }
  337 +
  338 + /// <summary>
  339 + /// 当月支付总额
  340 + /// </summary>
  341 + [SugarColumn(ColumnName = "F_MonthlyTotalPayment", DecimalDigits = 2)]
  342 + public decimal MonthlyTotalPayment { get; set; }
  343 +
  344 + /// <summary>
  345 + /// 是否锁定(0未锁定,1已锁定)
  346 + /// </summary>
  347 + [SugarColumn(ColumnName = "F_IsLocked")]
  348 + public int IsLocked { get; set; }
  349 +
  350 + /// <summary>
  351 + /// 是否离职(0=在职,1=离职)
  352 + /// </summary>
  353 + [SugarColumn(ColumnName = "F_IsTerminated")]
  354 + public int IsTerminated { get; set; }
  355 +
  356 + /// <summary>
  357 + /// 创建时间
  358 + /// </summary>
  359 + [SugarColumn(ColumnName = "F_CreateTime")]
  360 + public DateTime CreateTime { get; set; }
  361 +
  362 + /// <summary>
  363 + /// 更新时间
  364 + /// </summary>
  365 + [SugarColumn(ColumnName = "F_UpdateTime")]
  366 + public DateTime UpdateTime { get; set; }
  367 +
  368 + /// <summary>
  369 + /// 创建人
  370 + /// </summary>
  371 + [SugarColumn(ColumnName = "F_CreateUser")]
  372 + public string CreateUser { get; set; }
  373 +
  374 + /// <summary>
  375 + /// 更新人
  376 + /// </summary>
  377 + [SugarColumn(ColumnName = "F_UpdateUser")]
  378 + public string UpdateUser { get; set; }
  379 +
  380 + /// <summary>
  381 + /// 是否新店
  382 + /// </summary>
  383 + [SugarColumn(ColumnName = "F_IsNewStore")]
  384 + public string IsNewStore { get; set; }
  385 +
  386 + /// <summary>
  387 + /// 新店保护阶段
  388 + /// </summary>
  389 + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")]
  390 + public int NewStoreProtectionStage { get; set; }
  391 +
  392 + /// <summary>
  393 + /// 门店类型
  394 + /// </summary>
  395 + [SugarColumn(ColumnName = "F_StoreType")]
  396 + public int? StoreType { get; set; }
  397 +
  398 + /// <summary>
  399 + /// 门店类别
  400 + /// </summary>
  401 + [SugarColumn(ColumnName = "F_StoreCategory")]
  402 + public int? StoreCategory { get; set; }
  403 + }
  404 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqMdMajorProjectTeacherAssignment/ILqMdMajorProjectTeacherAssignmentService.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment;
  3 +using System.Threading.Tasks;
  4 +
  5 +namespace NCC.Extend.Interfaces.LqMdMajorProjectTeacherAssignment
  6 +{
  7 + /// <summary>
  8 + /// 门店大项目部老师归属设置服务接口
  9 + /// </summary>
  10 + public interface ILqMdMajorProjectTeacherAssignmentService
  11 + {
  12 + /// <summary>
  13 + /// 复制上月设置
  14 + /// </summary>
  15 + /// <param name="targetMonth">目标月份(YYYYMM格式),如果为空则复制到当前月份</param>
  16 + /// <returns></returns>
  17 + Task<dynamic> CopyLastMonthData(string targetMonth = null);
  18 + }
  19 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqMdMajorProjectTeacherAssignmentService.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 Mapster;
  7 +using Microsoft.AspNetCore.Mvc;
  8 +using NCC.Common.Core.Manager;
  9 +using NCC.Common.Enum;
  10 +using NCC.Common.Extension;
  11 +using NCC.Common.Filter;
  12 +using NCC.Dependency;
  13 +using NCC.DynamicApiController;
  14 +using NCC.Extend.Entitys.Dto.LqMdMajorProjectTeacherAssignment;
  15 +using NCC.Extend.Entitys.lq_md_major_project_teacher_assignment;
  16 +using NCC.Extend.Entitys.lq_mdxx;
  17 +using NCC.Extend.Interfaces.LqMdMajorProjectTeacherAssignment;
  18 +using NCC.FriendlyException;
  19 +using NCC.System.Entitys.Permission;
  20 +using SqlSugar;
  21 +using Yitter.IdGenerator;
  22 +
  23 +namespace NCC.Extend
  24 +{
  25 + /// <summary>
  26 + /// 门店大项目部老师归属设置服务
  27 + /// </summary>
  28 + [ApiDescriptionSettings(Tag = "绿纤门店大项目部老师归属设置服务", Name = "LqMdMajorProjectTeacherAssignment", Order = 201)]
  29 + [Route("api/Extend/[controller]")]
  30 + public class LqMdMajorProjectTeacherAssignmentService : ILqMdMajorProjectTeacherAssignmentService, IDynamicApiController, ITransient
  31 + {
  32 + private readonly ISqlSugarRepository<LqMdMajorProjectTeacherAssignmentEntity> _repository;
  33 + private readonly SqlSugarScope _db;
  34 + private readonly IUserManager _userManager;
  35 +
  36 + /// <summary>
  37 + /// 初始化一个<see cref="LqMdMajorProjectTeacherAssignmentService"/>类型的新实例
  38 + /// </summary>
  39 + public LqMdMajorProjectTeacherAssignmentService(ISqlSugarRepository<LqMdMajorProjectTeacherAssignmentEntity> repository, IUserManager userManager)
  40 + {
  41 + _repository = repository;
  42 + _db = _repository.Context;
  43 + _userManager = userManager;
  44 + }
  45 +
  46 + #region 获取门店大项目部老师归属设置信息
  47 + /// <summary>
  48 + /// 获取门店大项目部老师归属设置信息
  49 + /// </summary>
  50 + /// <param name="id">主键ID</param>
  51 + /// <returns></returns>
  52 + [HttpGet("{id}")]
  53 + public async Task<dynamic> GetInfo(string id)
  54 + {
  55 + var entity = await _db.Queryable<LqMdMajorProjectTeacherAssignmentEntity>()
  56 + .Where(p => p.Id == id)
  57 + .Select(it => new LqMdMajorProjectTeacherAssignmentInfoOutput
  58 + {
  59 + id = it.Id,
  60 + storeId = it.StoreId,
  61 + storeName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(x => x.Id == it.StoreId).Select(x => x.Dm),
  62 + year = it.Year,
  63 + month = it.Month,
  64 + teacherId = it.TeacherId,
  65 + teacherName = SqlFunc.Subqueryable<UserEntity>().Where(x => x.Id == it.TeacherId).Select(x => x.RealName),
  66 + remark = it.Remark,
  67 + createTime = it.CreateTime,
  68 + createUserId = it.CreateUserId,
  69 + updateTime = it.UpdateTime,
  70 + updateUserId = it.UpdateUserId,
  71 + })
  72 + .FirstAsync();
  73 +
  74 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  75 + return entity;
  76 + }
  77 + #endregion
  78 +
  79 + #region 获取门店大项目部老师归属设置列表
  80 + /// <summary>
  81 + /// 获取门店大项目部老师归属设置列表
  82 + /// </summary>
  83 + /// <param name="input">请求参数</param>
  84 + /// <returns></returns>
  85 + [HttpGet("")]
  86 + public async Task<dynamic> GetList([FromQuery] LqMdMajorProjectTeacherAssignmentListQueryInput input)
  87 + {
  88 + var sidx = input.sidx == null ? "id" : input.sidx;
  89 + var data = await _db.Queryable<LqMdMajorProjectTeacherAssignmentEntity>()
  90 + .WhereIF(!string.IsNullOrEmpty(input.storeId), p => p.StoreId == input.storeId)
  91 + .WhereIF(!string.IsNullOrEmpty(input.year), p => p.Year == input.year)
  92 + .WhereIF(!string.IsNullOrEmpty(input.month), p => p.Month == input.month)
  93 + .WhereIF(!string.IsNullOrEmpty(input.teacherId), p => p.TeacherId == input.teacherId)
  94 + .Select(it => new LqMdMajorProjectTeacherAssignmentListOutput
  95 + {
  96 + id = it.Id,
  97 + storeId = it.StoreId,
  98 + storeName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(x => x.Id == it.StoreId).Select(x => x.Dm),
  99 + year = it.Year,
  100 + month = it.Month,
  101 + teacherId = it.TeacherId,
  102 + teacherName = SqlFunc.Subqueryable<UserEntity>().Where(x => x.Id == it.TeacherId).Select(x => x.RealName),
  103 + remark = it.Remark,
  104 + createTime = it.CreateTime,
  105 + createUserId = it.CreateUserId,
  106 + updateTime = it.UpdateTime,
  107 + updateUserId = it.UpdateUserId,
  108 + })
  109 + .MergeTable()
  110 + .OrderBy(sidx + " " + input.sort)
  111 + .ToPagedListAsync(input.currentPage, input.pageSize);
  112 +
  113 + return PageResult<LqMdMajorProjectTeacherAssignmentListOutput>.SqlSugarPageResult(data);
  114 + }
  115 + #endregion
  116 +
  117 + #region 新建门店大项目部老师归属设置
  118 + /// <summary>
  119 + /// 新建门店大项目部老师归属设置
  120 + /// </summary>
  121 + /// <param name="input">参数</param>
  122 + /// <returns></returns>
  123 + [HttpPost("")]
  124 + public async Task Create([FromBody] LqMdMajorProjectTeacherAssignmentCrInput input)
  125 + {
  126 + var userInfo = await _userManager.GetUserInfo();
  127 +
  128 + // 验证年份格式
  129 + if (input.year.Length != 4 || !Regex.IsMatch(input.year, @"^\d{4}$"))
  130 + {
  131 + throw NCCException.Oh(ErrorCode.COM1000, "年份格式必须为YYYY(如:2025)");
  132 + }
  133 +
  134 + // 验证月份格式
  135 + if (input.month.Length != 2 || !Regex.IsMatch(input.month, @"^\d{2}$"))
  136 + {
  137 + throw NCCException.Oh(ErrorCode.COM1000, "月份格式必须为MM(如:01表示1月)");
  138 + }
  139 +
  140 + // 验证月份范围(01-12)
  141 + if (!int.TryParse(input.month, out int monthInt) || monthInt < 1 || monthInt > 12)
  142 + {
  143 + throw NCCException.Oh(ErrorCode.COM1000, "月份必须在01-12之间");
  144 + }
  145 +
  146 + // 验证门店ID、年份、月份、老师ID的唯一性
  147 + var exists = await _db.Queryable<LqMdMajorProjectTeacherAssignmentEntity>()
  148 + .Where(p => p.StoreId == input.storeId && p.Year == input.year && p.Month == input.month && p.TeacherId == input.teacherId)
  149 + .AnyAsync();
  150 + if (exists)
  151 + {
  152 + throw NCCException.Oh(ErrorCode.COM1000, "该门店在该年份该月份该老师已存在归属设置记录");
  153 + }
  154 +
  155 + var entity = input.Adapt<LqMdMajorProjectTeacherAssignmentEntity>();
  156 + entity.Id = YitIdHelper.NextId().ToString();
  157 + entity.CreateTime = DateTime.Now;
  158 + entity.CreateUserId = userInfo.userId;
  159 +
  160 + var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync();
  161 + if (!(isOk > 0))
  162 + throw NCCException.Oh(ErrorCode.COM1000);
  163 + }
  164 + #endregion
  165 +
  166 + #region 更新门店大项目部老师归属设置
  167 + /// <summary>
  168 + /// 更新门店大项目部老师归属设置
  169 + /// </summary>
  170 + /// <param name="id">主键ID</param>
  171 + /// <param name="input">更新参数</param>
  172 + /// <returns></returns>
  173 + [HttpPut("{id}")]
  174 + public async Task Update([FromRoute] string id, [FromBody] LqMdMajorProjectTeacherAssignmentUpInput input)
  175 + {
  176 + var userInfo = await _userManager.GetUserInfo();
  177 +
  178 + // 验证年份格式
  179 + if (input.year.Length != 4 || !Regex.IsMatch(input.year, @"^\d{4}$"))
  180 + {
  181 + throw NCCException.Oh(ErrorCode.COM1000, "年份格式必须为YYYY(如:2025)");
  182 + }
  183 +
  184 + // 验证月份格式
  185 + if (input.month.Length != 2 || !Regex.IsMatch(input.month, @"^\d{2}$"))
  186 + {
  187 + throw NCCException.Oh(ErrorCode.COM1000, "月份格式必须为MM(如:01表示1月)");
  188 + }
  189 +
  190 + // 验证月份范围(01-12)
  191 + if (!int.TryParse(input.month, out int monthInt) || monthInt < 1 || monthInt > 12)
  192 + {
  193 + throw NCCException.Oh(ErrorCode.COM1000, "月份必须在01-12之间");
  194 + }
  195 +
  196 + var entity = await _db.Queryable<LqMdMajorProjectTeacherAssignmentEntity>().FirstAsync(p => p.Id == id);
  197 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  198 +
  199 + // 如果门店、年份、月份、老师ID有变化,需要验证唯一性
  200 + if (entity.StoreId != input.storeId || entity.Year != input.year || entity.Month != input.month || entity.TeacherId != input.teacherId)
  201 + {
  202 + var exists = await _db.Queryable<LqMdMajorProjectTeacherAssignmentEntity>()
  203 + .Where(p => p.Id != id && p.StoreId == input.storeId && p.Year == input.year && p.Month == input.month && p.TeacherId == input.teacherId)
  204 + .AnyAsync();
  205 + if (exists)
  206 + {
  207 + throw NCCException.Oh(ErrorCode.COM1000, "该门店在该年份该月份该老师已存在归属设置记录");
  208 + }
  209 + }
  210 +
  211 + entity.StoreId = input.storeId;
  212 + entity.Year = input.year;
  213 + entity.Month = input.month;
  214 + entity.TeacherId = input.teacherId;
  215 + entity.Remark = input.remark;
  216 + entity.UpdateTime = DateTime.Now;
  217 + entity.UpdateUserId = userInfo.userId;
  218 +
  219 + var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
  220 + if (!(isOk > 0))
  221 + throw NCCException.Oh(ErrorCode.COM1000);
  222 + }
  223 + #endregion
  224 +
  225 + #region 删除门店大项目部老师归属设置
  226 + /// <summary>
  227 + /// 删除门店大项目部老师归属设置
  228 + /// </summary>
  229 + /// <param name="id">主键ID</param>
  230 + /// <returns></returns>
  231 + [HttpDelete("{id}")]
  232 + public async Task Delete(string id)
  233 + {
  234 + var entity = await _db.Queryable<LqMdMajorProjectTeacherAssignmentEntity>().FirstAsync(p => p.Id == id);
  235 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  236 +
  237 + var isOk = await _db.Deleteable<LqMdMajorProjectTeacherAssignmentEntity>().Where(p => p.Id == id).ExecuteCommandAsync();
  238 + if (!(isOk > 0))
  239 + throw NCCException.Oh(ErrorCode.COM1000);
  240 + }
  241 + #endregion
  242 +
  243 + #region 复制上月设置
  244 + /// <summary>
  245 + /// 从上个月复制设置数据到目标月份
  246 + /// </summary>
  247 + /// <remarks>
  248 + /// 从上个月复制门店大项目部老师归属设置数据到目标月份
  249 + ///
  250 + /// 示例请求:
  251 + /// POST /api/Extend/LqMdMajorProjectTeacherAssignment/CopyLastMonthData?targetYear=2025&targetMonth=02
  252 + ///
  253 + /// 业务逻辑:
  254 + /// 1. 确定目标年份和月份(如果未指定,使用当前年月)
  255 + /// 2. 计算上个月份
  256 + /// 3. 查询上个月的所有设置数据
  257 + /// 4. 查询目标月份已存在的记录
  258 + /// 5. 过滤掉已存在的记录,只复制新记录
  259 + /// 6. 批量插入新记录
  260 + /// </remarks>
  261 + /// <param name="targetYear">目标年份(YYYY格式),如果为空则使用当前年份</param>
  262 + /// <param name="targetMonth">目标月份(MM格式),如果为空则使用当前月份</param>
  263 + /// <returns>复制结果</returns>
  264 + /// <response code="200">成功返回复制结果</response>
  265 + /// <response code="400">参数错误</response>
  266 + /// <response code="500">服务器内部错误</response>
  267 + [HttpPost("CopyLastMonthData")]
  268 + public async Task<dynamic> CopyLastMonthData([FromQuery] string targetYear = null, [FromQuery] string targetMonth = null)
  269 + {
  270 + // 确定目标年份和月份
  271 + DateTime targetDate;
  272 + if (string.IsNullOrEmpty(targetYear) || string.IsNullOrEmpty(targetMonth))
  273 + {
  274 + targetDate = DateTime.Now;
  275 + }
  276 + else
  277 + {
  278 + if (targetYear.Length != 4 || !Regex.IsMatch(targetYear, @"^\d{4}$"))
  279 + {
  280 + throw NCCException.Oh(ErrorCode.COM1000, "目标年份格式必须为YYYY(如:2025)");
  281 + }
  282 + if (targetMonth.Length != 2 || !Regex.IsMatch(targetMonth, @"^\d{2}$"))
  283 + {
  284 + throw NCCException.Oh(ErrorCode.COM1000, "目标月份格式必须为MM(如:01表示1月)");
  285 + }
  286 + if (!int.TryParse(targetMonth, out int monthInt) || monthInt < 1 || monthInt > 12)
  287 + {
  288 + throw NCCException.Oh(ErrorCode.COM1000, "目标月份必须在01-12之间");
  289 + }
  290 + targetDate = DateTime.ParseExact($"{targetYear}{targetMonth}", "yyyyMM", null);
  291 + }
  292 +
  293 + // 计算上个月份
  294 + var lastMonthDate = targetDate.AddMonths(-1);
  295 + var lastYear = lastMonthDate.ToString("yyyy");
  296 + var lastMonth = lastMonthDate.ToString("MM");
  297 + var targetYearStr = targetDate.ToString("yyyy");
  298 + var targetMonthStr = targetDate.ToString("MM");
  299 +
  300 + // 查询上个月的所有设置数据
  301 + var lastMonthData = await _db.Queryable<LqMdMajorProjectTeacherAssignmentEntity>()
  302 + .Where(p => p.Year == lastYear && p.Month == lastMonth)
  303 + .ToListAsync();
  304 +
  305 + if (lastMonthData == null || lastMonthData.Count == 0)
  306 + {
  307 + return new { message = $"上个月({lastYear}年{lastMonth}月)没有设置数据,无法复制" };
  308 + }
  309 +
  310 + // 查询目标月份已存在的记录
  311 + var existingTargetMonthData = await _db.Queryable<LqMdMajorProjectTeacherAssignmentEntity>()
  312 + .Where(p => p.Year == targetYearStr && p.Month == targetMonthStr)
  313 + .Select(p => new { p.StoreId, p.TeacherId })
  314 + .ToListAsync();
  315 +
  316 + var existingKeys = existingTargetMonthData
  317 + .Select(x => $"{x.StoreId}_{x.TeacherId}")
  318 + .ToHashSet();
  319 +
  320 + // 过滤掉已存在的记录,只复制新记录
  321 + var newData = lastMonthData
  322 + .Where(x => !existingKeys.Contains($"{x.StoreId}_{x.TeacherId}"))
  323 + .Select(x => new LqMdMajorProjectTeacherAssignmentEntity
  324 + {
  325 + Id = YitIdHelper.NextId().ToString(),
  326 + StoreId = x.StoreId,
  327 + Year = targetYearStr,
  328 + Month = targetMonthStr,
  329 + TeacherId = x.TeacherId,
  330 + Remark = x.Remark,
  331 + CreateTime = DateTime.Now,
  332 + CreateUserId = _userManager.UserId,
  333 + UpdateTime = null,
  334 + UpdateUserId = null
  335 + })
  336 + .ToList();
  337 +
  338 + if (newData.Count == 0)
  339 + {
  340 + return new { message = $"目标月份({targetYearStr}年{targetMonthStr}月)已存在所有设置,无需复制" };
  341 + }
  342 +
  343 + // 批量插入新记录
  344 + var insertCount = await _db.Insertable(newData).ExecuteCommandAsync();
  345 +
  346 + return new { message = $"成功从{lastYear}年{lastMonth}月复制{insertCount}条记录到{targetYearStr}年{targetMonthStr}月" };
  347 + }
  348 + #endregion
  349 + }
  350 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.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.LqTechTeacherSalary;
  8 +using NCC.Extend.Entitys.lq_hytk_kjbsyj;
  9 +using NCC.Extend.Entitys.lq_kd_kjbsyj;
  10 +using NCC.Extend.Entitys.lq_md_xdbhsj;
  11 +using NCC.Extend.Entitys.lq_mdxx;
  12 +using NCC.Extend.Entitys.lq_attendance_summary;
  13 +using NCC.Extend.Entitys.lq_tech_teacher_salary_statistics;
  14 +using NCC.Extend.Entitys.lq_xh_hyhk;
  15 +using NCC.Extend.Entitys.lq_xh_kjbsyj;
  16 +using NCC.System.Entitys.Permission;
  17 +using SqlSugar;
  18 +using System;
  19 +using System.Collections.Generic;
  20 +using System.Linq;
  21 +using System.Threading.Tasks;
  22 +using Yitter.IdGenerator;
  23 +
  24 +namespace NCC.Extend
  25 +{
  26 + /// <summary>
  27 + /// 科技老师薪酬服务
  28 + /// </summary>
  29 + [ApiDescriptionSettings(Tag = "科技老师薪酬服务", Name = "LqTechTeacherSalary", Order = 302)]
  30 + [Route("api/Extend/[controller]")]
  31 + public class LqTechTeacherSalaryService : IDynamicApiController, ITransient
  32 + {
  33 + private readonly ISqlSugarClient _db;
  34 +
  35 + /// <summary>
  36 + /// 初始化一个<see cref="LqTechTeacherSalaryService"/>类型的新实例
  37 + /// </summary>
  38 + public LqTechTeacherSalaryService(ISqlSugarClient db)
  39 + {
  40 + _db = db;
  41 + }
  42 +
  43 + /// <summary>
  44 + /// 获取科技老师工资列表
  45 + /// </summary>
  46 + /// <param name="input">查询参数</param>
  47 + /// <returns>科技老师工资分页列表</returns>
  48 + [HttpGet("tech-teacher")]
  49 + public async Task<dynamic> GetTechTeacherSalaryList([FromQuery] TechTeacherSalaryInput input)
  50 + {
  51 + var monthStr = $"{input.Year}{input.Month:D2}";
  52 +
  53 + // 1. 检查当月是否已生成工资数据
  54 + var exists = await _db.Queryable<LqTechTeacherSalaryStatisticsEntity>()
  55 + .AnyAsync(x => x.StatisticsMonth == monthStr);
  56 +
  57 + // 2. 如果没有数据,则进行计算
  58 + if (!exists)
  59 + {
  60 + await CalculateTechTeacherSalary(input.Year, input.Month);
  61 + }
  62 +
  63 + // 3. 查询数据
  64 + var query = _db.Queryable<LqTechTeacherSalaryStatisticsEntity>()
  65 + .Where(x => x.StatisticsMonth == monthStr);
  66 +
  67 + if (!string.IsNullOrEmpty(input.StoreId))
  68 + {
  69 + query = query.Where(x => x.StoreId == input.StoreId);
  70 + }
  71 +
  72 + if (!string.IsNullOrEmpty(input.Keyword))
  73 + {
  74 + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeAccount.Contains(input.Keyword));
  75 + }
  76 +
  77 + var list = await query.Select(x => new TechTeacherSalaryOutput
  78 + {
  79 + Id = x.Id,
  80 + StatisticsMonth = x.StatisticsMonth,
  81 + StoreId = x.StoreId,
  82 + StoreName = x.StoreName,
  83 + Position = x.Position,
  84 + EmployeeName = x.EmployeeName,
  85 + EmployeeId = x.EmployeeId,
  86 + EmployeeAccount = x.EmployeeAccount,
  87 + OrderAchievement = x.OrderAchievement,
  88 + ConsumeAchievement = x.ConsumeAchievement,
  89 + RefundAchievement = x.RefundAchievement,
  90 + TotalPerformance = x.TotalPerformance,
  91 + ProjectCount = x.ProjectCount,
  92 + BaseSalaryLevel = x.BaseSalaryLevel,
  93 + BaseSalary = x.BaseSalary,
  94 + PerformanceCommissionRate = x.PerformanceCommissionRate,
  95 + PerformanceCommissionAmount = x.PerformanceCommissionAmount,
  96 + ConsumeCommissionRate = x.ConsumeCommissionRate,
  97 + ConsumeCommissionAmount = x.ConsumeCommissionAmount,
  98 + HandworkFee = x.HandworkFee,
  99 + WorkingDays = x.WorkingDays,
  100 + LeaveDays = x.LeaveDays,
  101 + TotalCommission = x.TotalCommission,
  102 + TransportationAllowance = x.TransportationAllowance,
  103 + LessRest = x.LessRest,
  104 + FullAttendance = x.FullAttendance,
  105 + CalculatedGrossSalary = x.CalculatedGrossSalary,
  106 + GuaranteedSalary = x.GuaranteedSalary,
  107 + GuaranteedLeaveDeduction = x.GuaranteedLeaveDeduction,
  108 + GuaranteedBaseSalary = x.GuaranteedBaseSalary,
  109 + GuaranteedSupplement = x.GuaranteedSupplement,
  110 + FinalGrossSalary = x.FinalGrossSalary,
  111 + MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy,
  112 + MonthlyTransportSubsidy = x.MonthlyTransportSubsidy,
  113 + LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy,
  114 + LastMonthTransportSubsidy = x.LastMonthTransportSubsidy,
  115 + TotalSubsidy = x.TotalSubsidy,
  116 + MissingCard = x.MissingCard,
  117 + LateArrival = x.LateArrival,
  118 + LeaveDeduction = x.LeaveDeduction,
  119 + SocialInsuranceDeduction = x.SocialInsuranceDeduction,
  120 + RewardDeduction = x.RewardDeduction,
  121 + AccommodationDeduction = x.AccommodationDeduction,
  122 + StudyPeriodDeduction = x.StudyPeriodDeduction,
  123 + WorkClothesDeduction = x.WorkClothesDeduction,
  124 + TotalDeduction = x.TotalDeduction,
  125 + Bonus = x.Bonus,
  126 + ReturnPhoneDeposit = x.ReturnPhoneDeposit,
  127 + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit,
  128 + ActualSalary = x.ActualSalary,
  129 + MonthlyPaymentStatus = x.MonthlyPaymentStatus,
  130 + PaidAmount = x.PaidAmount,
  131 + PendingAmount = x.PendingAmount,
  132 + LastMonthSupplement = x.LastMonthSupplement,
  133 + MonthlyTotalPayment = x.MonthlyTotalPayment,
  134 + IsLocked = x.IsLocked,
  135 + IsTerminated = x.IsTerminated,
  136 + UpdateTime = x.UpdateTime,
  137 + StoreType = x.StoreType,
  138 + StoreCategory = x.StoreCategory,
  139 + IsNewStore = x.IsNewStore,
  140 + NewStoreProtectionStage = x.NewStoreProtectionStage
  141 + })
  142 + .ToPagedListAsync(input.currentPage, input.pageSize);
  143 +
  144 + return PageResult<TechTeacherSalaryOutput>.SqlSugarPageResult(list);
  145 + }
  146 +
  147 + /// <summary>
  148 + /// 计算科技老师工资
  149 + /// </summary>
  150 + /// <param name="year">年份</param>
  151 + /// <param name="month">月份</param>
  152 + /// <returns></returns>
  153 + [HttpPost("calculate/tech-teacher")]
  154 + public async Task CalculateTechTeacherSalary(int year, int month)
  155 + {
  156 + var startDate = new DateTime(year, month, 1);
  157 + var endDate = startDate.AddMonths(1).AddDays(-1);
  158 + var monthStr = $"{year}{month:D2}";
  159 +
  160 + // 1. 获取基础数据
  161 +
  162 + // 1.1 获取科技老师员工列表(从BASE_USER表,岗位为"科技老师")
  163 + var techTeacherUserList = await _db.Queryable<UserEntity>()
  164 + .Where(x => x.Gw == "科技老师" && x.DeleteMark == null && x.EnabledMark == 1)
  165 + .Select(x => new { x.Id, x.RealName, x.Account, x.Mdid, x.IsOnJob })
  166 + .ToListAsync();
  167 +
  168 + if (!techTeacherUserList.Any())
  169 + {
  170 + // 如果没有科技老师员工,直接返回
  171 + return;
  172 + }
  173 +
  174 + // 1.2 门店信息 (lq_mdxx)
  175 + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
  176 + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x);
  177 +
  178 + // 1.3 门店新店保护信息 (lq_md_xdbhsj)
  179 + var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>()
  180 + .Where(x => x.Sfqy == 1)
  181 + .ToListAsync();
  182 +
  183 + var newStoreProtectionDict = newStoreProtectionList
  184 + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate)
  185 + .GroupBy(x => x.Mdid)
  186 + .ToDictionary(g => g.Key, g => g.First());
  187 +
  188 + // 1.4 开单业绩数据 (lq_kd_kjbsyj)
  189 + var orderPerformanceList = await _db.Queryable<LqKdKjbsyjEntity>()
  190 + .Where(x => x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1) && x.IsEffective == 1)
  191 + .ToListAsync();
  192 +
  193 + // 1.5 消耗业绩和项目数数据 (lq_xh_kjbsyj,关联lq_xh_hyhk获取时间)
  194 + var consumePerformanceList = await _db.Queryable<LqXhKjbsyjEntity, LqXhHyhkEntity>(
  195 + (kjbsyj, hyhk) => kjbsyj.Glkdbh == hyhk.Id && hyhk.IsEffective == 1)
  196 + .Where((kjbsyj, hyhk) => kjbsyj.IsEffective == 1
  197 + && hyhk.Hksj >= startDate && hyhk.Hksj <= endDate.AddDays(1))
  198 + .Select((kjbsyj, hyhk) => new
  199 + {
  200 + kjbsyj.Kjblszh,
  201 + kjbsyj.Kjblsyj,
  202 + kjbsyj.HdpxNumber,
  203 + kjbsyj.LaborCost,
  204 + hyhk.Md
  205 + })
  206 + .ToListAsync();
  207 +
  208 + // 1.6 退卡业绩数据 (lq_hytk_kjbsyj)
  209 + var refundPerformanceList = await _db.Queryable<LqHytkKjbsyjEntity>()
  210 + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
  211 + .ToListAsync();
  212 +
  213 + // 1.7 考勤数据 (lq_attendance_summary)
  214 + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
  215 + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
  216 + .ToListAsync();
  217 + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x);
  218 +
  219 + // 2. 按科技老师聚合数据
  220 + var techTeacherStats = new Dictionary<string, LqTechTeacherSalaryStatisticsEntity>();
  221 +
  222 + foreach (var techTeacherUser in techTeacherUserList)
  223 + {
  224 + var teacherId = techTeacherUser.Id;
  225 + var isTerminated = techTeacherUser.IsOnJob == 0;
  226 +
  227 + // 2.1 创建工资统计对象
  228 + var salary = new LqTechTeacherSalaryStatisticsEntity
  229 + {
  230 + Id = YitIdHelper.NextId().ToString(),
  231 + StatisticsMonth = monthStr,
  232 + EmployeeId = teacherId,
  233 + EmployeeName = techTeacherUser.RealName,
  234 + EmployeeAccount = techTeacherUser.Account ?? "",
  235 + Position = "科技部老师",
  236 + IsTerminated = isTerminated ? 1 : 0,
  237 + CreateTime = DateTime.Now,
  238 + UpdateTime = DateTime.Now,
  239 + IsLocked = 0
  240 + };
  241 +
  242 + // 2.2 填充门店信息
  243 + var storeId = techTeacherUser.Mdid;
  244 + if (!string.IsNullOrEmpty(storeId) && storeDict.ContainsKey(storeId))
  245 + {
  246 + var store = storeDict[storeId];
  247 + salary.StoreId = storeId;
  248 + salary.StoreName = store.Dm ?? "";
  249 + salary.StoreType = store.StoreType;
  250 + salary.StoreCategory = store.StoreCategory;
  251 + }
  252 + else
  253 + {
  254 + // 如果用户没有门店,尝试从业绩数据中获取
  255 + var firstOrderStore = orderPerformanceList.FirstOrDefault(x => x.Kjbls == teacherId || x.Kjblszh == teacherId);
  256 + var firstConsumeStore = consumePerformanceList.FirstOrDefault(x => x.Kjblszh == teacherId);
  257 + var firstRefundStore = refundPerformanceList.FirstOrDefault(x => x.Kjbls == teacherId || x.Kjblszh == teacherId);
  258 +
  259 + if (firstOrderStore != null && !string.IsNullOrEmpty(firstOrderStore.StoreId))
  260 + {
  261 + storeId = firstOrderStore.StoreId;
  262 + }
  263 + else if (firstConsumeStore != null && !string.IsNullOrEmpty(firstConsumeStore.Md))
  264 + {
  265 + storeId = firstConsumeStore.Md;
  266 + }
  267 + else if (firstRefundStore != null && !string.IsNullOrEmpty(firstRefundStore.StoreId))
  268 + {
  269 + storeId = firstRefundStore.StoreId;
  270 + }
  271 +
  272 + if (!string.IsNullOrEmpty(storeId) && storeDict.ContainsKey(storeId))
  273 + {
  274 + var store = storeDict[storeId];
  275 + salary.StoreId = storeId;
  276 + salary.StoreName = store.Dm ?? "";
  277 + salary.StoreType = store.StoreType;
  278 + salary.StoreCategory = store.StoreCategory;
  279 + }
  280 + }
  281 +
  282 + // 2.3 新店保护信息
  283 + if (!string.IsNullOrEmpty(salary.StoreId) && newStoreProtectionDict.ContainsKey(salary.StoreId))
  284 + {
  285 + var protection = newStoreProtectionDict[salary.StoreId];
  286 + salary.IsNewStore = "是";
  287 + salary.NewStoreProtectionStage = protection.Stage;
  288 + }
  289 + else
  290 + {
  291 + salary.IsNewStore = "否";
  292 + salary.NewStoreProtectionStage = 0;
  293 + }
  294 +
  295 + // 2.4 统计业绩数据
  296 + // 开单业绩(注意:kjblsyj字段是string类型,需要转换)
  297 + var orderPerformance = orderPerformanceList
  298 + .Where(x => (x.Kjbls == teacherId || x.Kjblszh == teacherId) && !string.IsNullOrEmpty(x.Kjblsyj))
  299 + .Sum(x => decimal.TryParse(x.Kjblsyj, out var val) ? val : 0m);
  300 + salary.OrderAchievement = orderPerformance;
  301 +
  302 + // 消耗业绩和项目数
  303 + var consumeData = consumePerformanceList
  304 + .Where(x => x.Kjblszh == teacherId)
  305 + .ToList();
  306 + salary.ConsumeAchievement = consumeData.Sum(x => x.Kjblsyj ?? 0m);
  307 + salary.ProjectCount = consumeData.Sum(x => x.HdpxNumber ?? 0m);
  308 + salary.HandworkFee = consumeData.Sum(x => x.LaborCost ?? 0m);
  309 +
  310 + // 退卡业绩
  311 + var refundPerformance = refundPerformanceList
  312 + .Where(x => (x.Kjbls == teacherId || x.Kjblszh == teacherId))
  313 + .Sum(x => x.Kjblsyj ?? 0m);
  314 + salary.RefundAchievement = refundPerformance;
  315 +
  316 + // 总业绩
  317 + salary.TotalPerformance = salary.OrderAchievement + salary.ConsumeAchievement + salary.RefundAchievement;
  318 +
  319 + // 2.5 考勤数据
  320 + var attendance = attendanceDict.ContainsKey(teacherId) ? attendanceDict[teacherId] : null;
  321 + salary.WorkingDays = attendance?.WorkDays ?? 0;
  322 + salary.LeaveDays = attendance?.LeaveDays ?? 0;
  323 +
  324 + // 3. 工资计算
  325 + if (isTerminated)
  326 + {
  327 + // 离职员工特殊处理
  328 + if (salary.TotalPerformance > 30000m)
  329 + {
  330 + // 总业绩 > 30,000元:只计算2%提成
  331 + salary.BaseSalary = 0;
  332 + salary.BaseSalaryLevel = 0;
  333 + salary.PerformanceCommissionRate = 2m;
  334 + salary.PerformanceCommissionAmount = salary.TotalPerformance * 0.02m;
  335 + salary.ConsumeCommissionRate = 0;
  336 + salary.ConsumeCommissionAmount = 0;
  337 + salary.TotalCommission = salary.PerformanceCommissionAmount;
  338 + }
  339 + else
  340 + {
  341 + // 总业绩 ≤ 30,000元:无任何工资
  342 + salary.BaseSalary = 0;
  343 + salary.BaseSalaryLevel = 0;
  344 + salary.PerformanceCommissionRate = 0;
  345 + salary.PerformanceCommissionAmount = 0;
  346 + salary.ConsumeCommissionRate = 0;
  347 + salary.ConsumeCommissionAmount = 0;
  348 + salary.TotalCommission = 0;
  349 + }
  350 + }
  351 + else
  352 + {
  353 + // 在职员工正常计算
  354 +
  355 + // 3.1 计算底薪(根据项目数和总业绩)
  356 + var baseSalaryResult = CalculateBaseSalary(salary.ProjectCount, salary.TotalPerformance);
  357 + salary.BaseSalary = baseSalaryResult.BaseSalary;
  358 + salary.BaseSalaryLevel = baseSalaryResult.Level;
  359 +
  360 + // 3.2 计算业绩提成(分段累进)
  361 + var performanceCommissionResult = CalculatePerformanceCommission(salary.TotalPerformance);
  362 + salary.PerformanceCommissionRate = performanceCommissionResult.Rate;
  363 + salary.PerformanceCommissionAmount = performanceCommissionResult.Amount;
  364 +
  365 + // 3.3 计算消耗提成(分段累进,可能为负数)
  366 + var consumeCommissionResult = CalculateConsumeCommission(salary.ConsumeAchievement);
  367 + salary.ConsumeCommissionRate = consumeCommissionResult.Rate;
  368 + salary.ConsumeCommissionAmount = consumeCommissionResult.Amount;
  369 +
  370 + // 3.4 提成合计
  371 + salary.TotalCommission = salary.PerformanceCommissionAmount + salary.ConsumeCommissionAmount;
  372 + }
  373 +
  374 + // 3.5 初始化其他字段(默认值为0)
  375 + salary.TransportationAllowance = 0;
  376 + salary.LessRest = 0;
  377 + salary.FullAttendance = 0;
  378 + salary.CalculatedGrossSalary = salary.BaseSalary + salary.TotalCommission + salary.HandworkFee;
  379 + salary.GuaranteedSalary = 0;
  380 + salary.GuaranteedLeaveDeduction = 0;
  381 + salary.GuaranteedBaseSalary = 0;
  382 + salary.GuaranteedSupplement = 0;
  383 + salary.FinalGrossSalary = salary.CalculatedGrossSalary;
  384 + salary.MonthlyTrainingSubsidy = 0;
  385 + salary.MonthlyTransportSubsidy = 0;
  386 + salary.LastMonthTrainingSubsidy = 0;
  387 + salary.LastMonthTransportSubsidy = 0;
  388 + salary.TotalSubsidy = 0;
  389 + salary.MissingCard = 0;
  390 + salary.LateArrival = 0;
  391 + salary.LeaveDeduction = 0;
  392 + salary.SocialInsuranceDeduction = 0;
  393 + salary.RewardDeduction = 0;
  394 + salary.AccommodationDeduction = 0;
  395 + salary.StudyPeriodDeduction = 0;
  396 + salary.WorkClothesDeduction = 0;
  397 + salary.TotalDeduction = 0;
  398 + salary.Bonus = 0;
  399 + salary.ReturnPhoneDeposit = 0;
  400 + salary.ReturnAccommodationDeposit = 0;
  401 + salary.ActualSalary = salary.FinalGrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus;
  402 + salary.MonthlyPaymentStatus = "";
  403 + salary.PaidAmount = 0;
  404 + salary.PendingAmount = salary.ActualSalary;
  405 + salary.LastMonthSupplement = 0;
  406 + salary.MonthlyTotalPayment = 0;
  407 +
  408 + techTeacherStats[teacherId] = salary;
  409 + }
  410 +
  411 + // 4. 保存数据
  412 + if (techTeacherStats.Any())
  413 + {
  414 + // 先删除当月旧数据 (防止重复)
  415 + await _db.Deleteable<LqTechTeacherSalaryStatisticsEntity>()
  416 + .Where(x => x.StatisticsMonth == monthStr)
  417 + .ExecuteCommandAsync();
  418 +
  419 + await _db.Insertable(techTeacherStats.Values.ToList()).ExecuteCommandAsync();
  420 + }
  421 + }
  422 +
  423 + /// <summary>
  424 + /// 计算底薪(根据项目数和总业绩)
  425 + /// </summary>
  426 + /// <param name="projectCount">项目数</param>
  427 + /// <param name="totalPerformance">总业绩</param>
  428 + /// <returns>底薪金额和档位</returns>
  429 + private (decimal BaseSalary, int Level) CalculateBaseSalary(decimal projectCount, decimal totalPerformance)
  430 + {
  431 + // 从高到低判断档位,同时满足项目数和总业绩两个条件
  432 + // 第三档:≥ 110个 且 ≥ 100,000元 → 3,500元
  433 + if (projectCount >= 110m && totalPerformance >= 100000m)
  434 + {
  435 + return (3500m, 3);
  436 + }
  437 + // 第二档:≥ 95个 且 ≥ 80,000元 → 3,000元
  438 + else if (projectCount >= 95m && totalPerformance >= 80000m)
  439 + {
  440 + return (3000m, 2);
  441 + }
  442 + // 第一档:≥ 80个 且 ≥ 40,000元 → 2,500元
  443 + else if (projectCount >= 80m && totalPerformance >= 40000m)
  444 + {
  445 + return (2500m, 1);
  446 + }
  447 + // 都不满足:默认第一档 2,500元
  448 + else
  449 + {
  450 + return (2500m, 1);
  451 + }
  452 + }
  453 +
  454 + /// <summary>
  455 + /// 计算业绩提成(分段累进)
  456 + /// </summary>
  457 + /// <param name="totalPerformance">总业绩</param>
  458 + /// <returns>提成比例和金额</returns>
  459 + private (decimal Rate, decimal Amount) CalculatePerformanceCommission(decimal totalPerformance)
  460 + {
  461 + if (totalPerformance < 10000m)
  462 + {
  463 + // < 10,000元 → 0%
  464 + return (0m, 0m);
  465 + }
  466 + else if (totalPerformance < 70000m)
  467 + {
  468 + // 10,000-70,000元 → 2%
  469 + return (2m, totalPerformance * 0.02m);
  470 + }
  471 + else if (totalPerformance < 150000m)
  472 + {
  473 + // 70,000-150,000元 → 2.5%
  474 + return (2.5m, totalPerformance * 0.025m);
  475 + }
  476 + else
  477 + {
  478 + // > 150,000元 → 3%
  479 + return (3m, totalPerformance * 0.03m);
  480 + }
  481 + }
  482 +
  483 + /// <summary>
  484 + /// 计算消耗提成(分段累进,可能为负数)
  485 + /// </summary>
  486 + /// <param name="consumeAchievement">消耗业绩</param>
  487 + /// <returns>提成比例和金额(金额可能为负数,比例用于显示)</returns>
  488 + private (decimal Rate, decimal Amount) CalculateConsumeCommission(decimal consumeAchievement)
  489 + {
  490 + if (consumeAchievement < 80000m)
  491 + {
  492 + // < 80,000元 → 扣除300元(负数)
  493 + // 比例显示为0,金额为-300
  494 + return (0m, -300m);
  495 + }
  496 + else if (consumeAchievement < 100000m)
  497 + {
  498 + // 80,000-100,000元 → 0.5%
  499 + return (0.5m, consumeAchievement * 0.005m);
  500 + }
  501 + else if (consumeAchievement < 200000m)
  502 + {
  503 + // 100,000-200,000元 → 0.5%
  504 + return (0.5m, consumeAchievement * 0.005m);
  505 + }
  506 + else
  507 + {
  508 + // > 200,000元 → 1%
  509 + return (1m, consumeAchievement * 0.01m);
  510 + }
  511 + }
  512 + }
  513 +}
  514 +
... ...
sql/创建大项目部老师归属设置表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 创建大项目部老师归属设置表
  3 +-- 功能:存储各门店每月的大项目部老师归属设置
  4 +-- 创建时间:2025年
  5 +-- ============================================
  6 +
  7 +-- 删除表(如果存在)
  8 +DROP TABLE IF EXISTS lq_md_major_project_teacher_assignment;
  9 +
  10 +-- ============================================
  11 +-- 创建大项目部老师归属设置表
  12 +-- ============================================
  13 +CREATE TABLE lq_md_major_project_teacher_assignment (
  14 + -- 主键
  15 + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID',
  16 + -- 基本信息
  17 + F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID',
  18 + F_Year VARCHAR(4) NOT NULL COMMENT '年份(YYYY格式)',
  19 + F_Month VARCHAR(2) NOT NULL COMMENT '月份(MM格式)',
  20 + F_TeacherId VARCHAR(50) NOT NULL COMMENT '大项目部老师用户ID',
  21 + -- 备注说明
  22 + F_Remark TEXT NULL COMMENT '备注说明',
  23 + -- 审计字段
  24 + F_CreateTime DATETIME NULL COMMENT '创建时间',
  25 + F_CreateUserId VARCHAR(50) NULL COMMENT '创建人ID',
  26 + F_UpdateTime DATETIME NULL COMMENT '更新时间',
  27 + F_UpdateUserId VARCHAR(50) NULL COMMENT '更新人ID',
  28 + -- 主键约束
  29 + PRIMARY KEY (F_Id)
  30 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='门店大项目部老师归属设置表';
  31 +
  32 +-- ============================================
  33 +-- 创建索引
  34 +-- ============================================
  35 +
  36 +-- 门店ID + 年份 + 月份 + 老师ID唯一索引(同一门店同一年份同一月份同一老师只能有一条记录)
  37 +CREATE UNIQUE INDEX idx_store_year_month_teacher ON lq_md_major_project_teacher_assignment(F_StoreId, F_Year, F_Month, F_TeacherId);
  38 +
  39 +-- 门店ID索引(查询某个门店的所有设置)
  40 +CREATE INDEX idx_store_id ON lq_md_major_project_teacher_assignment(F_StoreId);
  41 +
  42 +-- 年份索引(查询某年的所有设置)
  43 +CREATE INDEX idx_year ON lq_md_major_project_teacher_assignment(F_Year);
  44 +-- 月份索引(查询某个月份的所有设置)
  45 +CREATE INDEX idx_month ON lq_md_major_project_teacher_assignment(F_Month);
  46 +-- 年份+月份组合索引(查询某年某月的所有设置)
  47 +CREATE INDEX idx_year_month ON lq_md_major_project_teacher_assignment(F_Year, F_Month);
  48 +
  49 +-- 老师ID索引(查询某个老师的所有门店设置)
  50 +CREATE INDEX idx_teacher_id ON lq_md_major_project_teacher_assignment(F_TeacherId);
  51 +
  52 +-- ============================================
  53 +-- 表结构说明
  54 +-- ============================================
  55 +/*
  56 +业务规则:
  57 +1. 同一门店同一年份同一月份同一老师只能有一条归属设置记录
  58 +2. 年份格式为YYYY(如:2025表示2025年)
  59 +3. 月份格式为MM(如:01表示1月)
  60 +4. 一个门店在一个月份可以有多个老师(通过多条记录实现)
  61 +5. 一个门店在一个月份也可以没有老师(不创建记录即可)
  62 +6. 老师ID关联BASE_USER.F_Id
  63 +7. 门店ID关联lq_mdxx.F_Id
  64 +
  65 +字段说明:
  66 +- F_Id: 主键,使用YitIdHelper生成
  67 +- F_StoreId: 门店ID,关联lq_mdxx表
  68 +- F_Year: 年份,格式YYYY(如:2025)
  69 +- F_Month: 月份,格式MM(如:01表示1月)
  70 +- F_TeacherId: 大项目部老师用户ID,关联BASE_USER表
  71 +- F_Remark: 备注说明(可选)
  72 +- F_CreateTime/F_CreateUserId: 创建时间和创建人
  73 +- F_UpdateTime/F_UpdateUserId: 更新时间和更新人
  74 +*/
... ...
sql/创建科技部老师工资统计表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 创建科技部老师工资统计表
  3 +-- 功能:存储科技部老师每月的工资计算数据,包括底薪、业绩提成、消耗提成、考核扣款、扣款、补贴、奖金、支付等信息
  4 +-- 创建时间:2025年
  5 +-- ============================================
  6 +
  7 +-- 删除表(如果存在)
  8 +DROP TABLE IF EXISTS lq_tech_teacher_salary_statistics;
  9 +
  10 +-- ============================================
  11 +-- 创建科技部老师工资统计表
  12 +-- ============================================
  13 +CREATE TABLE lq_tech_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) NOT 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 + F_ProjectCount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '项目数(用于底薪计算,主要来源于耗卡品相次数F_hdpxNumber)',
  36 +
  37 + -- 三、底薪相关字段
  38 + F_BaseSalaryLevel INT NOT NULL DEFAULT 0 COMMENT '底薪档位(1=第一档2500, 2=第二档3000, 3=第三档3500)',
  39 + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '底薪金额(根据项目数和业绩计算,2500/3000/3500)',
  40 +
  41 + -- 四、业绩提成相关字段
  42 + F_PerformanceCommissionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '业绩提成比例(百分比,如2.00表示2%)',
  43 + F_PerformanceCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '业绩提成金额(总业绩 × 业绩提成比例)',
  44 +
  45 + -- 五、消耗提成相关字段
  46 + F_ConsumeCommissionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '消耗提成比例(百分比,如0.50表示0.5%,负数表示扣除,如-300表示扣除300元)',
  47 + F_ConsumeCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '消耗提成金额(可能为负数,如扣除300元)',
  48 + F_TotalCommission DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成合计(业绩提成 + 消耗提成)',
  49 +
  50 + -- 六、其他收入字段
  51 + F_HandworkFee DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '手工费',
  52 + F_TransportationAllowance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '车补',
  53 + F_LessRest DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '少休费',
  54 + F_FullAttendance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '全勤奖',
  55 +
  56 + -- 七、考勤相关字段
  57 + F_WorkingDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '在店天数',
  58 + F_LeaveDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假天数',
  59 +
  60 + -- 八、工资计算字段
  61 + F_CalculatedGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '核算应发工资(底薪 + 提成合计 + 其他收入)',
  62 + F_GuaranteedSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底工资',
  63 + F_GuaranteedLeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底请假扣款',
  64 + F_GuaranteedBaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底底薪',
  65 + F_GuaranteedSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '保底补差',
  66 + F_FinalGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '最终应发工资(取核算应发工资和保底工资的较大值)',
  67 +
  68 + -- 九、补贴相关字段
  69 + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴',
  70 + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴',
  71 + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴',
  72 + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴',
  73 + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计',
  74 +
  75 + -- 十、扣款相关字段
  76 + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款',
  77 + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款',
  78 + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款',
  79 + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保',
  80 + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励',
  81 + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费',
  82 + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用',
  83 + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用',
  84 + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计',
  85 +
  86 + -- 十一、奖金相关字段
  87 + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金',
  88 + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金',
  89 + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金',
  90 +
  91 + -- 十二、支付相关字段
  92 + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(最终应发工资 - 扣款合计 + 补贴合计 + 奖金)',
  93 + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)',
  94 + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额',
  95 + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额',
  96 + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月',
  97 + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额',
  98 +
  99 + -- 十三、系统字段
  100 + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)',
  101 + F_IsTerminated INT NOT NULL DEFAULT 0 COMMENT '是否离职(0=在职,1=离职)',
  102 + F_CreateTime DATETIME NOT NULL COMMENT '创建时间',
  103 + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间',
  104 + F_CreateUser VARCHAR(50) NULL COMMENT '创建人',
  105 + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人',
  106 +
  107 + -- 主键约束
  108 + PRIMARY KEY (F_Id),
  109 +
  110 + -- 唯一索引:确保同一员工同一月份只有一条记录
  111 + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth),
  112 +
  113 + -- 普通索引
  114 + KEY `idx_store_id` (F_StoreId),
  115 + KEY `idx_statistics_month` (F_StatisticsMonth),
  116 + KEY `idx_employee_id` (F_EmployeeId),
  117 + KEY `idx_employee_account` (F_EmployeeAccount),
  118 + KEY `idx_store_category` (F_StoreCategory),
  119 + KEY `idx_is_terminated` (F_IsTerminated),
  120 + KEY `idx_create_time` (F_CreateTime)
  121 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='科技部老师工资统计表';
... ...
报销退回后重新走流程-接口调用示例.md 0 → 100644
  1 +# 报销退回后修改并重新走流程 - 接口调用示例
  2 +
  3 +## 前置条件
  4 +
  5 +- 已创建报销申请并提交审批
  6 +- 申请已被退回(状态为"已退回")
  7 +- 申请ID:`767672243453953285`(示例)
  8 +- Token:`Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`(需要替换为实际token)
  9 +
  10 +---
  11 +
  12 +## 完整流程
  13 +
  14 +### 步骤1:退回申请(如果还未退回)
  15 +
  16 +**接口地址:** `POST /api/Extend/LqReimbursementApplication/{id}/Actions/Approve`
  17 +
  18 +**请求参数:**
  19 +- `result`: 审批结果,值为 `退回`(需要URL编码为 `%E9%80%80%E5%9B%9E`)
  20 +- `opinion`: 退回原因
  21 +
  22 +**cURL 示例:**
  23 +```bash
  24 +curl -X POST "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285/Actions/Approve?result=%E9%80%80%E5%9B%9E&opinion=%E9%9C%80%E8%A6%81%E4%BF%AE%E6%94%B9%E9%87%91%E9%A2%9D" \
  25 + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \
  26 + -H "Content-Type: application/json"
  27 +```
  28 +
  29 +**返回示例:**
  30 +```json
  31 +{
  32 + "code": 200,
  33 + "msg": "操作成功",
  34 + "data": null
  35 +}
  36 +```
  37 +
  38 +---
  39 +
  40 +### 步骤2:查看退回后的状态
  41 +
  42 +**接口地址:** `GET /api/Extend/LqReimbursementApplication/{id}`
  43 +
  44 +**cURL 示例:**
  45 +```bash
  46 +curl -X GET "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285" \
  47 + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \
  48 + -H "Content-Type: application/json"
  49 +```
  50 +
  51 +**返回示例:**
  52 +```json
  53 +{
  54 + "code": 200,
  55 + "msg": "操作成功",
  56 + "data": {
  57 + "form": {
  58 + "id": "767672243453953285",
  59 + "applicationUserId": "admin",
  60 + "applicationUserName": "管理员",
  61 + "applicationStoreId": "1649328471923847168",
  62 + "amount": "200.00",
  63 + ...
  64 + },
  65 + "approvalStatus": "已退回",
  66 + "currentNodeOrder": 0,
  67 + "returnedReason": "需要修改金额",
  68 + "nodes": [
  69 + {
  70 + "nodeOrder": 1,
  71 + "nodeName": "部门经理审批",
  72 + "approvalRecords": [
  73 + {
  74 + "approverName": "管理员",
  75 + "approvalResult": "退回",
  76 + "approvalOpinion": "需要修改金额",
  77 + "approvalTime": 1765192675000
  78 + }
  79 + ]
  80 + }
  81 + ]
  82 + }
  83 +}
  84 +```
  85 +
  86 +---
  87 +
  88 +### 步骤3:修改报销单
  89 +
  90 +**接口地址:** `PUT /api/Extend/LqReimbursementApplication/{id}`
  91 +
  92 +**请求体参数:**
  93 +```json
  94 +{
  95 + "id": "767672243453953285",
  96 + "applicationUserId": "admin",
  97 + "applicationUserName": "管理员",
  98 + "applicationStoreId": "1649328471923847168",
  99 + "applicationTime": 1765123200000,
  100 + "amount": "300.00",
  101 + "selectedPurchaseRecordIds": [],
  102 + "purchaseRecordsId": null
  103 +}
  104 +```
  105 +
  106 +**参数说明:**
  107 +- `id`: 申请编号(必填)
  108 +- `applicationUserId`: 申请人编号(可选,不修改可不传)
  109 +- `applicationUserName`: 申请人姓名(可选,不修改可不传)
  110 +- `applicationStoreId`: 申请门店ID(可选,不修改可不传)
  111 +- `applicationTime`: 申请时间(时间戳,可选)
  112 +- `amount`: 总金额(可选,要修改的字段)
  113 +- `selectedPurchaseRecordIds`: 选中的购买记录ID列表(可选,数组)
  114 +- `purchaseRecordsId`: 关联购买编号(可选,字符串,多个用逗号分隔)
  115 +
  116 +**cURL 示例:**
  117 +```bash
  118 +curl -X PUT "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285" \
  119 + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \
  120 + -H "Content-Type: application/json" \
  121 + -d '{
  122 + "id": "767672243453953285",
  123 + "amount": "300.00"
  124 + }'
  125 +```
  126 +
  127 +**返回示例:**
  128 +```json
  129 +{
  130 + "code": 200,
  131 + "msg": "操作成功",
  132 + "data": null
  133 +}
  134 +```
  135 +
  136 +**注意:**
  137 +- 只有"待审批"(CurrentNodeOrder=0)或"已退回"状态的申请才能修改
  138 +- 如果状态为"审批中"、"已通过"、"未通过",修改会被拒绝
  139 +
  140 +---
  141 +
  142 +### 步骤4:重新提交审批
  143 +
  144 +**接口地址:** `POST /api/Extend/LqReimbursementApplication/{id}/Actions/SubmitApproval`
  145 +
  146 +**cURL 示例:**
  147 +```bash
  148 +curl -X POST "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285/Actions/SubmitApproval" \
  149 + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \
  150 + -H "Content-Type: application/json"
  151 +```
  152 +
  153 +**返回示例:**
  154 +```json
  155 +{
  156 + "code": 200,
  157 + "msg": "操作成功",
  158 + "data": null
  159 +}
  160 +```
  161 +
  162 +**说明:**
  163 +- 重新提交审批后,会创建新的"待审批"记录
  164 +- 之前的"退回"记录会被保留,不会删除
  165 +- 申请状态会从"已退回"变为"审批中"
  166 +
  167 +---
  168 +
  169 +### 步骤5:审批通过
  170 +
  171 +**接口地址:** `POST /api/Extend/LqReimbursementApplication/{id}/Actions/Approve`
  172 +
  173 +**请求参数:**
  174 +- `result`: 审批结果,值为 `通过`(需要URL编码为 `%E9%80%9A%E8%BF%87`)
  175 +- `opinion`: 审批意见(可选)
  176 +
  177 +**cURL 示例:**
  178 +```bash
  179 +curl -X POST "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285/Actions/Approve?result=%E9%80%9A%E8%BF%87&opinion=%E4%BF%AE%E6%94%B9%E5%90%8E%E5%AE%A1%E6%89%B9%E9%80%9A%E8%BF%87" \
  180 + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \
  181 + -H "Content-Type: application/json"
  182 +```
  183 +
  184 +**返回示例:**
  185 +```json
  186 +{
  187 + "code": 200,
  188 + "msg": "操作成功",
  189 + "data": {
  190 + "approvalStatus": "已通过"
  191 + }
  192 +}
  193 +```
  194 +
  195 +---
  196 +
  197 +### 步骤6:查看最终状态和审批记录
  198 +
  199 +**接口地址:** `GET /api/Extend/LqReimbursementApplication/{id}`
  200 +
  201 +**cURL 示例:**
  202 +```bash
  203 +curl -X GET "http://localhost:2011/api/Extend/LqReimbursementApplication/767672243453953285" \
  204 + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUxOTIzMDgsIm5iZiI6MTc2NTE5MjMwOCwiZXhwIjoxNzY1MjQ2MzA4LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.5YBxyOU5SmmIldlVp6V7uclsLrzLR8KIp51kraW7EGQ" \
  205 + -H "Content-Type: application/json"
  206 +```
  207 +
  208 +**返回示例:**
  209 +```json
  210 +{
  211 + "code": 200,
  212 + "msg": "操作成功",
  213 + "data": {
  214 + "form": {
  215 + "id": "767672243453953285",
  216 + "amount": "300.00",
  217 + ...
  218 + },
  219 + "approvalStatus": "已通过",
  220 + "currentNodeOrder": 2,
  221 + "nodes": [
  222 + {
  223 + "nodeOrder": 1,
  224 + "nodeName": "部门经理审批",
  225 + "approvalRecords": [
  226 + {
  227 + "approverName": "管理员",
  228 + "approvalResult": "退回",
  229 + "approvalOpinion": "需要修改金额",
  230 + "approvalTime": 1765192675000
  231 + },
  232 + {
  233 + "approverName": "管理员",
  234 + "approvalResult": "通过",
  235 + "approvalOpinion": "修改后审批通过",
  236 + "approvalTime": 1765192762000
  237 + }
  238 + ]
  239 + }
  240 + ]
  241 + }
  242 +}
  243 +```
  244 +
  245 +**说明:**
  246 +- 审批记录中包含完整的审批历史
  247 +- "退回"记录和"通过"记录都会保留
  248 +- 可以清楚地看到退回原因和重新审批的结果
  249 +
  250 +---
  251 +
  252 +## 完整流程总结
  253 +
  254 +1. **退回申请** → 状态变为"已退回",当前节点为0
  255 +2. **修改报销单** → 修改需要调整的字段(金额、购买记录等)
  256 +3. **重新提交审批** → 状态变为"审批中",创建新的"待审批"记录
  257 +4. **审批通过** → 状态变为"已通过",审批记录中包含退回和通过两条记录
  258 +
  259 +---
  260 +
  261 +## 注意事项
  262 +
  263 +1. **URL编码**:中文字符需要URL编码
  264 + - `退回` → `%E9%80%80%E5%9B%9E`
  265 + - `通过` → `%E9%80%9A%E8%BF%87`
  266 +
  267 +2. **状态检查**:
  268 + - 只有"待审批"或"已退回"状态的申请才能修改
  269 + - 只有"审批中"状态的申请才能进行审批操作
  270 +
  271 +3. **审批记录**:
  272 + - 退回记录会被保留,不会删除
  273 + - 重新提交审批后会创建新的"待审批"记录
  274 + - 审批记录按时间顺序显示,包含完整的审批历史
  275 +
  276 +4. **Token**:所有接口都需要在Header中携带有效的Authorization Token
  277 +
  278 +---
  279 +
  280 +## JavaScript/Axios 示例
  281 +
  282 +```javascript
  283 +const axios = require('axios');
  284 +const BASE_URL = 'http://localhost:2011';
  285 +const TOKEN = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
  286 +const APPLICATION_ID = '767672243453953285';
  287 +
  288 +// 1. 退回申请
  289 +async function returnApplication() {
  290 + const response = await axios.post(
  291 + `${BASE_URL}/api/Extend/LqReimbursementApplication/${APPLICATION_ID}/Actions/Approve`,
  292 + null,
  293 + {
  294 + headers: { Authorization: TOKEN },
  295 + params: {
  296 + result: '退回',
  297 + opinion: '需要修改金额'
  298 + }
  299 + }
  300 + );
  301 + return response.data;
  302 +}
  303 +
  304 +// 2. 修改报销单
  305 +async function updateApplication() {
  306 + const response = await axios.put(
  307 + `${BASE_URL}/api/Extend/LqReimbursementApplication/${APPLICATION_ID}`,
  308 + {
  309 + id: APPLICATION_ID,
  310 + amount: '300.00'
  311 + },
  312 + {
  313 + headers: { Authorization: TOKEN }
  314 + }
  315 + );
  316 + return response.data;
  317 +}
  318 +
  319 +// 3. 重新提交审批
  320 +async function resubmitApproval() {
  321 + const response = await axios.post(
  322 + `${BASE_URL}/api/Extend/LqReimbursementApplication/${APPLICATION_ID}/Actions/SubmitApproval`,
  323 + null,
  324 + {
  325 + headers: { Authorization: TOKEN }
  326 + }
  327 + );
  328 + return response.data;
  329 +}
  330 +
  331 +// 4. 审批通过
  332 +async function approveApplication() {
  333 + const response = await axios.post(
  334 + `${BASE_URL}/api/Extend/LqReimbursementApplication/${APPLICATION_ID}/Actions/Approve`,
  335 + null,
  336 + {
  337 + headers: { Authorization: TOKEN },
  338 + params: {
  339 + result: '通过',
  340 + opinion: '修改后审批通过'
  341 + }
  342 + }
  343 + );
  344 + return response.data;
  345 +}
  346 +
  347 +// 完整流程
  348 +async function completeFlow() {
  349 + try {
  350 + // 1. 退回
  351 + await returnApplication();
  352 + console.log('✅ 申请已退回');
  353 +
  354 + // 2. 修改
  355 + await updateApplication();
  356 + console.log('✅ 申请已修改');
  357 +
  358 + // 3. 重新提交
  359 + await resubmitApproval();
  360 + console.log('✅ 已重新提交审批');
  361 +
  362 + // 4. 审批通过
  363 + await approveApplication();
  364 + console.log('✅ 审批已通过');
  365 +
  366 + } catch (error) {
  367 + console.error('❌ 流程执行失败:', error.response?.data || error.message);
  368 + }
  369 +}
  370 +```
  371 +
... ...
科技部老师工资计算规则.md 0 → 100644
  1 +# 科技部老师工资计算规则
  2 +
  3 +## 📋 目录
  4 +- [计算规则](#计算规则)
  5 +- [数据来源](#数据来源)
  6 +- [特殊规则](#特殊规则)
  7 +- [计算流程](#计算流程)
  8 +
  9 +---
  10 +
  11 +## 💰 计算规则
  12 +
  13 +### 1. 底薪规则(分三档)
  14 +
  15 +底薪根据**项目数**和**总业绩**两个条件同时满足来确定档位:
  16 +
  17 +| 档位 | 项目数要求 | 总业绩要求 | 底薪金额 |
  18 +|------|-----------|-----------|---------|
  19 +| **第一档** | ≥ 80个 | ≥ 40,000元 | 2,500元 |
  20 +| **第二档** | ≥ 95个 | ≥ 80,000元 | 3,000元 |
  21 +| **第三档** | ≥ 110个 | ≥ 100,000元 | 3,500元 |
  22 +| **最低档** | 不满足第一档条件 | - | 2,500元(默认第一档) |
  23 +
  24 +**计算逻辑**:
  25 +- 从高到低判断,同时满足项目数和总业绩两个条件才能获得对应档位底薪
  26 +- 如果都不满足,默认使用第一档(2,500元)
  27 +
  28 +---
  29 +
  30 +### 2. 业绩提成规则
  31 +
  32 +业绩提成基于**总业绩**计算,采用分段累进方式:
  33 +
  34 +| 总业绩范围 | 提成比例 |
  35 +|-----------|---------|
  36 +| < 10,000元 | 0% (无提成) |
  37 +| 10,000元 - 70,000元 | 2% |
  38 +| 70,000元 - 150,000元 | 2.5% |
  39 +| > 150,000元 | 3% |
  40 +
  41 +**计算说明**:
  42 +- 提成金额 = 总业绩 × 对应提成比例
  43 +- 采用分段计算,不同区间按不同比例计算
  44 +
  45 +---
  46 +
  47 +### 3. 消耗提成规则
  48 +
  49 +消耗提成基于**消耗业绩**计算,包含扣除机制:
  50 +
  51 +| 消耗业绩范围 | 提成规则 |
  52 +|------------|---------|
  53 +| < 80,000元 | **扣除300元**(负提成) |
  54 +| 80,000元 - 100,000元 | 0.5% 提成 |
  55 +| 100,000元 - 200,000元 | 0.5% 提成 |
  56 +| > 200,000元 | 1% 提成 |
  57 +
  58 +**重要说明**:
  59 +- 当消耗业绩 < 80,000元时,不是无提成,而是**直接扣除300元工资**
  60 +- 消耗提成金额可能为负数(扣除情况)
  61 +
  62 +---
  63 +
  64 +## 📊 数据来源
  65 +
  66 +### 总业绩(TotalPerformance)
  67 +
  68 +**定义**:开单业绩 + 消耗业绩 + 退卡业绩
  69 +
  70 +**数据来源表及字段**:
  71 +
  72 +| 业绩类型 | 数据表 | 字段 | 说明 |
  73 +|---------|--------|------|------|
  74 +| **开单业绩** | `lq_kd_kjbsyj` | `kjblsyj` | 科技部老师开单业绩 |
  75 +| **消耗业绩** | `lq_xh_kjbsyj` | `kjblsyj` | 科技部老师消耗业绩 |
  76 +| **退卡业绩** | `lq_hytk_kjbsyj` | `kjblsyj` | 科技部老师退卡业绩 |
  77 +
  78 +**计算方式**:
  79 +```sql
  80 +总业绩 = SUM(开单业绩) + SUM(消耗业绩) + SUM(退卡业绩)
  81 +```
  82 +
  83 +**过滤条件**:
  84 +- 所有表记录必须满足:`F_IsEffective = 1`(有效记录)
  85 +- 按统计月份(YYYYMM格式)过滤时间范围
  86 +- 按科技老师ID(`kjblszh` 或 `kjbls`)关联
  87 +
  88 +---
  89 +
  90 +### 消耗(ConsumeAchievement)
  91 +
  92 +**定义**:科技部老师在耗卡记录中的业绩总和
  93 +
  94 +**数据来源**:
  95 +- **表名**:`lq_xh_kjbsyj`(耗卡科技部老师业绩表)
  96 +- **字段**:`kjblsyj`(科技部老师业绩)
  97 +- **关联表**:`lq_xh_hyhk`(耗卡主表,用于时间过滤)
  98 +
  99 +**计算方式**:
  100 +```sql
  101 +消耗 = SUM(lq_xh_kjbsyj.kjblsyj)
  102 +WHERE lq_xh_kjbsyj.F_IsEffective = 1
  103 + AND lq_xh_hyhk.F_IsEffective = 1
  104 + AND DATE_FORMAT(lq_xh_hyhk.hksj, '%Y%m') = @统计月份
  105 + AND lq_xh_kjbsyj.kjblszh = @科技老师账号
  106 +```
  107 +
  108 +**对应实体字段**:
  109 +- `LqTechTeacherSalaryStatisticsEntity.F_ConsumeAchievement`
  110 +
  111 +---
  112 +
  113 +### 项目数(ProjectCount)
  114 +
  115 +**定义**:科技部老师在耗卡记录中的项目次数总和
  116 +
  117 +**数据来源**:
  118 +- **表名**:`lq_xh_kjbsyj`(耗卡科技部老师业绩表)
  119 +- **字段**:`F_hdpxNumber`(耗卡品项次数)
  120 +- **关联表**:`lq_xh_hyhk`(耗卡主表,用于时间过滤)
  121 +
  122 +**计算方式**:
  123 +```sql
  124 +项目数 = SUM(lq_xh_kjbsyj.F_hdpxNumber)
  125 +WHERE lq_xh_kjbsyj.F_IsEffective = 1
  126 + AND lq_xh_hyhk.F_IsEffective = 1
  127 + AND DATE_FORMAT(lq_xh_hyhk.hksj, '%Y%m') = @统计月份
  128 + AND lq_xh_kjbsyj.kjblszh = @科技老师账号
  129 +```
  130 +
  131 +**对应实体字段**:
  132 +- `LqTechTeacherSalaryStatisticsEntity.F_ProjectCount`
  133 +
  134 +---
  135 +
  136 +## ⚠️ 特殊规则
  137 +
  138 +### 离职员工规则
  139 +
  140 +**适用条件**:
  141 +- 员工状态:`BASE_USER.F_IsOnJob = 0`(离职)
  142 +
  143 +**计算规则**:
  144 +- 如果离职员工当月**总业绩 > 30,000元**:
  145 + - **提成**:总业绩 × 2%
  146 + - **无底薪**:不计算底薪
  147 + - **无奖励**:不计算任何奖励
  148 + - **无消耗提成**:不计算消耗提成
  149 +- 如果离职员工当月总业绩 ≤ 30,000元:
  150 + - 无任何工资
  151 +
  152 +**记录标识**:
  153 +- 在 `LqTechTeacherSalaryStatisticsEntity.F_IsTerminated` 字段中记录:
  154 + - `0` = 在职
  155 + - `1` = 离职
  156 +
  157 +**重要说明**:
  158 +- 离职员工规则优先级最高,一旦判定为离职且业绩>3万,只按2%提成计算,其他规则不适用
  159 +
  160 +---
  161 +
  162 +## 🔄 计算流程
  163 +
  164 +### 步骤1:识别科技部老师
  165 +- 从 `BASE_USER` 表中筛选:`Gw == "科技老师"` 且 `DeleteMark == null` 且 `EnabledMark == 1`
  166 +
  167 +### 步骤2:判断是否离职
  168 +- 检查 `BASE_USER.F_IsOnJob`:
  169 + - `F_IsOnJob == 0` → 离职员工
  170 + - `F_IsOnJob == 1` → 在职员工
  171 +
  172 +### 步骤3:数据汇总(在职员工)
  173 +- 查询开单业绩:从 `lq_kd_kjbsyj` 表汇总 `kjblsyj`
  174 +- 查询消耗业绩:从 `lq_xh_kjbsyj` 表汇总 `kjblsyj`
  175 +- 查询退卡业绩:从 `lq_hytk_kjbsyj` 表汇总 `kjblsyj`
  176 +- 查询项目数:从 `lq_xh_kjbsyj` 表汇总 `F_hdpxNumber`
  177 +- 计算总业绩:开单业绩 + 消耗业绩 + 退卡业绩
  178 +
  179 +### 步骤4:工资计算(在职员工)
  180 +
  181 +#### 4.1 计算底薪
  182 +- 根据项目数和总业绩,从高到低判断档位:
  183 + - 同时满足第三档条件 → 底薪 3,500元
  184 + - 同时满足第二档条件 → 底薪 3,000元
  185 + - 同时满足第一档条件 → 底薪 2,500元
  186 + - 都不满足 → 默认第一档 2,500元
  187 +
  188 +#### 4.2 计算业绩提成
  189 +- 根据总业绩范围确定提成比例:
  190 + - < 10,000元 → 0%
  191 + - 10,000-70,000元 → 2%
  192 + - 70,000-150,000元 → 2.5%
  193 + - > 150,000元 → 3%
  194 +- 业绩提成金额 = 总业绩 × 提成比例
  195 +
  196 +#### 4.3 计算消耗提成
  197 +- 根据消耗业绩范围确定提成规则:
  198 + - < 80,000元 → 扣除300元(负数)
  199 + - 80,000-100,000元 → 0.5%
  200 + - 100,000-200,000元 → 0.5%
  201 + - > 200,000元 → 1%
  202 +- 消耗提成金额 = 消耗业绩 × 提成比例(或直接扣除300元)
  203 +
  204 +### 步骤5:工资计算(离职员工)
  205 +- 如果总业绩 > 30,000元:
  206 + - 底薪 = 0
  207 + - 业绩提成 = 总业绩 × 2%
  208 + - 消耗提成 = 0
  209 + - 奖励 = 0
  210 +- 如果总业绩 ≤ 30,000元:
  211 + - 所有工资项 = 0
  212 +
  213 +### 步骤6:保存结果
  214 +- 将计算结果保存到 `lq_tech_teacher_salary_statistics` 表
  215 +- 记录 `F_IsTerminated` 字段标识是否离职
  216 +
  217 +---
  218 +
  219 +## 📝 数据表结构
  220 +
  221 +### 主表:`lq_tech_teacher_salary_statistics`
  222 +
  223 +**关键字段说明**:
  224 +
  225 +| 字段名 | 说明 | 数据来源 |
  226 +|--------|------|---------|
  227 +| `F_StatisticsMonth` | 统计月份(YYYYMM) | 计算参数 |
  228 +| `F_EmployeeId` | 员工ID | `BASE_USER.F_Id` |
  229 +| `F_EmployeeAccount` | 员工账号 | `BASE_USER.F_Account` |
  230 +| `F_EmployeeName` | 员工姓名 | `BASE_USER.F_RealName` |
  231 +| `F_StoreId` | 门店ID | `lq_mdxx.F_Id` |
  232 +| `F_StoreName` | 门店名称 | `lq_mdxx.dm` |
  233 +| `F_TotalPerformance` | 总业绩 | 计算得出 |
  234 +| `F_ConsumeAchievement` | 消耗业绩 | `lq_xh_kjbsyj.kjblsyj` |
  235 +| `F_ProjectCount` | 项目数 | `lq_xh_kjbsyj.F_hdpxNumber` |
  236 +| `F_BaseSalary` | 底薪 | 根据规则计算 |
  237 +| `F_PerformanceCommissionAmount` | 业绩提成金额 | 根据规则计算 |
  238 +| `F_ConsumeCommissionAmount` | 消耗提成金额 | 根据规则计算(可能为负数) |
  239 +| `F_IsTerminated` | 是否离职(0=在职,1=离职) | `BASE_USER.F_IsOnJob` |
  240 +
  241 +---
  242 +
  243 +## 🔍 查询示例
  244 +
  245 +### 查询科技部老师消耗数据(参考 `LqStatisticsService.GetTechTeacherStatistics`)
  246 +
  247 +```sql
  248 +-- 消耗业绩和项目数查询
  249 +SELECT
  250 + kjbsyj.kjblszh as F_UserId,
  251 + kjbsyj.kjblsxm as F_UserName,
  252 + hyhk.md as F_StoreId,
  253 + md.dm as F_StoreName,
  254 + @statisticsMonth as F_StatisticsMonth,
  255 + COALESCE(SUM(kjbsyj.kjblsyj), 0) as F_ConsumePerformance, -- 消耗业绩
  256 + COALESCE(SUM(kjbsyj.F_hdpxNumber), 0) as F_ConsumeQuantity, -- 项目数
  257 + COALESCE(SUM(kjbsyj.F_LaborCost), 0) as F_ManualFee
  258 +FROM lq_xh_kjbsyj kjbsyj
  259 +INNER JOIN lq_xh_hyhk hyhk ON kjbsyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1
  260 +LEFT JOIN lq_mdxx md ON hyhk.md = md.F_Id
  261 +WHERE kjbsyj.F_IsEffective = 1
  262 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @statisticsMonth
  263 +GROUP BY kjbsyj.kjblszh, kjbsyj.kjblsxm, hyhk.md, md.mdbm, md.dm
  264 +```
  265 +
  266 +---
  267 +
  268 +## ✅ 验证要点
  269 +
  270 +1. **数据准确性**:
  271 + - 确保所有业绩数据来自有效记录(`F_IsEffective = 1`)
  272 + - 确保时间范围正确(按月份YYYYMM格式)
  273 +
  274 +2. **计算逻辑**:
  275 + - 底薪档位判断必须同时满足项目数和总业绩两个条件
  276 + - 消耗提成 < 80,000元时是扣除300元,不是0
  277 + - 离职员工规则优先级最高
  278 +
  279 +3. **数据一致性**:
  280 + - 总业绩 = 开单业绩 + 消耗业绩 + 退卡业绩
  281 + - 消耗和项目数必须来自同一数据源(`lq_xh_kjbsyj`)
  282 +
  283 +---
  284 +
  285 +## 📌 注意事项
  286 +
  287 +1. **离职员工处理**:
  288 + - 必须先判断 `F_IsOnJob`,离职员工走特殊逻辑
  289 + - 离职员工不计算底薪和消耗提成
  290 +
  291 +2. **消耗提成扣除**:
  292 + - 消耗 < 80,000元时,`F_ConsumeCommissionAmount` 字段值为 `-300`(负数)
  293 +
  294 +3. **数据汇总**:
  295 + - 同一科技部老师可能在不同门店有数据,需要按门店分别计算
  296 + - 或者按科技部老师汇总所有门店数据(根据业务需求)
  297 +
  298 +4. **月份格式**:
  299 + - 统计月份统一使用 `YYYYMM` 格式(如:202501)
  300 +
  301 +---
  302 +
  303 +## 🔗 相关文件
  304 +
  305 +- **实体类**:`LqTechTeacherSalaryStatisticsEntity.cs`
  306 +- **薪酬规则文档**:`项目信息-薪酬规则与名词解释.md`
  307 +- **参考服务**:`LqSalaryService.cs`(健康师工资计算)、`LqAssistantSalaryService.cs`(店助工资计算)
  308 +- **统计数据服务**:`LqStatisticsService.GetTechTeacherStatistics`(科技部老师业绩统计)
... ...
项目信息-薪酬规则与名词解释.md
... ... @@ -68,6 +68,7 @@
68 68  
69 69 ### 通用规则
70 70  
  71 +1. **离职员工规则**:离职员工当月业绩大于30000元,按照2%提成,且无奖励
71 72 2. **金三角(战队)标准**:组员当月考勤天数 ≥ 20天,则算战队组成成功,可按战队提成计算
72 73  
73 74 ### 健康师薪酬规则
... ...