Commit 42273b705b6f1c2f575d851e3400e3672680c16a
Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP
Showing
45 changed files
with
7987 additions
and
175 deletions
antis-ncc-admin/.env.development
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | VUE_CLI_BABEL_TRANSPILE_MODULES = true |
| 4 | 4 | # VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com' |
| 5 | -VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com' | |
| 6 | -# VUE_APP_BASE_API = 'http://localhost:2011' | |
| 5 | +# VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com' | |
| 6 | +VUE_APP_BASE_API = 'http://localhost:2011' | |
| 7 | 7 | # VUE_APP_BASE_API = 'http://localhost:2011' |
| 8 | 8 | VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket' | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqAssistantSalary/AssistantSalaryInput.cs
0 → 100644
| 1 | +using NCC.Common.Filter; | |
| 2 | +using System; | |
| 3 | + | |
| 4 | +namespace NCC.Extend.Entitys.Dto.LqAssistantSalary | |
| 5 | +{ | |
| 6 | + /// <summary> | |
| 7 | + /// 店助工资查询参数 | |
| 8 | + /// </summary> | |
| 9 | + public class AssistantSalaryInput : 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/LqAssistantSalary/AssistantSalaryOutput.cs
0 → 100644
| 1 | +using System; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Dto.LqAssistantSalary | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 店助工资输出 | |
| 7 | + /// </summary> | |
| 8 | + public class AssistantSalaryOutput | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 主键ID | |
| 12 | + /// </summary> | |
| 13 | + public string Id { get; set; } | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 门店名称 | |
| 17 | + /// </summary> | |
| 18 | + public string StoreName { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 员工姓名 | |
| 22 | + /// </summary> | |
| 23 | + public string EmployeeName { get; set; } | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 岗位 | |
| 27 | + /// </summary> | |
| 28 | + public string Position { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 门店总业绩 | |
| 32 | + /// </summary> | |
| 33 | + public decimal StoreTotalPerformance { get; set; } | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 门店开单业绩 | |
| 37 | + /// </summary> | |
| 38 | + public decimal StoreBillingPerformance { get; set; } | |
| 39 | + | |
| 40 | + /// <summary> | |
| 41 | + /// 门店退卡业绩 | |
| 42 | + /// </summary> | |
| 43 | + public decimal StoreRefundPerformance { get; set; } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 门店生命线 | |
| 47 | + /// </summary> | |
| 48 | + public decimal StoreLifeline { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 业绩完成率 | |
| 52 | + /// </summary> | |
| 53 | + public decimal PerformanceCompletionRate { get; set; } | |
| 54 | + | |
| 55 | + /// <summary> | |
| 56 | + /// 提成比例 | |
| 57 | + /// </summary> | |
| 58 | + public decimal CommissionRate { get; set; } | |
| 59 | + | |
| 60 | + /// <summary> | |
| 61 | + /// 提成金额 | |
| 62 | + /// </summary> | |
| 63 | + public decimal CommissionAmount { get; set; } | |
| 64 | + | |
| 65 | + /// <summary> | |
| 66 | + /// 进店消耗人数 | |
| 67 | + /// </summary> | |
| 68 | + public int HeadCount { get; set; } | |
| 69 | + | |
| 70 | + /// <summary> | |
| 71 | + /// 第一阶段目标人数 | |
| 72 | + /// </summary> | |
| 73 | + public int Stage1TargetHeadCount { get; set; } | |
| 74 | + | |
| 75 | + /// <summary> | |
| 76 | + /// 第二阶段目标人数 | |
| 77 | + /// </summary> | |
| 78 | + public int Stage2TargetHeadCount { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 是否达到第一阶段 | |
| 82 | + /// </summary> | |
| 83 | + public string ReachedStage1 { get; set; } | |
| 84 | + | |
| 85 | + /// <summary> | |
| 86 | + /// 是否达到第二阶段 | |
| 87 | + /// </summary> | |
| 88 | + public string ReachedStage2 { get; set; } | |
| 89 | + | |
| 90 | + /// <summary> | |
| 91 | + /// 阶段奖励金额 | |
| 92 | + /// </summary> | |
| 93 | + public decimal StageRewardAmount { get; set; } | |
| 94 | + | |
| 95 | + /// <summary> | |
| 96 | + /// 第一阶段奖励 | |
| 97 | + /// </summary> | |
| 98 | + public decimal Stage1Reward { get; set; } | |
| 99 | + | |
| 100 | + /// <summary> | |
| 101 | + /// 第二阶段奖励 | |
| 102 | + /// </summary> | |
| 103 | + public decimal Stage2Reward { get; set; } | |
| 104 | + | |
| 105 | + /// <summary> | |
| 106 | + /// 底薪金额 | |
| 107 | + /// </summary> | |
| 108 | + public decimal BaseSalary { get; set; } | |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// 手机管理费 | |
| 112 | + /// </summary> | |
| 113 | + public decimal PhoneManagementFee { get; set; } | |
| 114 | + | |
| 115 | + /// <summary> | |
| 116 | + /// 在店天数 | |
| 117 | + /// </summary> | |
| 118 | + public int WorkingDays { get; set; } | |
| 119 | + | |
| 120 | + /// <summary> | |
| 121 | + /// 请假天数 | |
| 122 | + /// </summary> | |
| 123 | + public int LeaveDays { get; set; } | |
| 124 | + | |
| 125 | + /// <summary> | |
| 126 | + /// 应发工资 | |
| 127 | + /// </summary> | |
| 128 | + public decimal GrossSalary { get; set; } | |
| 129 | + | |
| 130 | + /// <summary> | |
| 131 | + /// 实发工资 | |
| 132 | + /// </summary> | |
| 133 | + public decimal ActualSalary { get; set; } | |
| 134 | + | |
| 135 | + /// <summary> | |
| 136 | + /// 扣款合计 | |
| 137 | + /// </summary> | |
| 138 | + public decimal TotalDeduction { get; set; } | |
| 139 | + | |
| 140 | + /// <summary> | |
| 141 | + /// 补贴合计 | |
| 142 | + /// </summary> | |
| 143 | + public decimal TotalSubsidy { get; set; } | |
| 144 | + | |
| 145 | + /// <summary> | |
| 146 | + /// 发奖金 | |
| 147 | + /// </summary> | |
| 148 | + public decimal Bonus { get; set; } | |
| 149 | + | |
| 150 | + /// <summary> | |
| 151 | + /// 退手机押金 | |
| 152 | + /// </summary> | |
| 153 | + public decimal ReturnPhoneDeposit { get; set; } | |
| 154 | + | |
| 155 | + /// <summary> | |
| 156 | + /// 退住宿押金 | |
| 157 | + /// </summary> | |
| 158 | + public decimal ReturnAccommodationDeposit { get; set; } | |
| 159 | + | |
| 160 | + /// <summary> | |
| 161 | + /// 当月是否发放 | |
| 162 | + /// </summary> | |
| 163 | + public string MonthlyPaymentStatus { get; set; } | |
| 164 | + | |
| 165 | + /// <summary> | |
| 166 | + /// 支付金额 | |
| 167 | + /// </summary> | |
| 168 | + public decimal PaidAmount { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 待支付金额 | |
| 172 | + /// </summary> | |
| 173 | + public decimal PendingAmount { get; set; } | |
| 174 | + | |
| 175 | + /// <summary> | |
| 176 | + /// 补发上月 | |
| 177 | + /// </summary> | |
| 178 | + public decimal LastMonthSupplement { get; set; } | |
| 179 | + | |
| 180 | + /// <summary> | |
| 181 | + /// 当月支付总额 | |
| 182 | + /// </summary> | |
| 183 | + public decimal MonthlyTotalPayment { get; set; } | |
| 184 | + | |
| 185 | + /// <summary> | |
| 186 | + /// 是否锁定 | |
| 187 | + /// </summary> | |
| 188 | + public int IsLocked { get; set; } | |
| 189 | + | |
| 190 | + /// <summary> | |
| 191 | + /// 更新时间 | |
| 192 | + /// </summary> | |
| 193 | + public DateTime UpdateTime { get; set; } | |
| 194 | + | |
| 195 | + /// <summary> | |
| 196 | + /// 门店类型 | |
| 197 | + /// </summary> | |
| 198 | + public int? StoreType { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 门店类别 | |
| 202 | + /// </summary> | |
| 203 | + public int? StoreCategory { get; set; } | |
| 204 | + | |
| 205 | + /// <summary> | |
| 206 | + /// 是否新店 | |
| 207 | + /// </summary> | |
| 208 | + public string IsNewStore { get; set; } | |
| 209 | + | |
| 210 | + /// <summary> | |
| 211 | + /// 新店保护阶段 | |
| 212 | + /// </summary> | |
| 213 | + public int NewStoreProtectionStage { get; set; } | |
| 214 | + } | |
| 215 | +} | |
| 216 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryInput.cs
0 → 100644
| 1 | +using NCC.Common.Filter; | |
| 2 | +using System; | |
| 3 | + | |
| 4 | +namespace NCC.Extend.Entitys.Dto.LqDirectorSalary | |
| 5 | +{ | |
| 6 | + /// <summary> | |
| 7 | + /// 主任工资查询参数 | |
| 8 | + /// </summary> | |
| 9 | + public class DirectorSalaryInput : 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/LqDirectorSalary/DirectorSalaryOutput.cs
0 → 100644
| 1 | +using System; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Dto.LqDirectorSalary | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 主任工资输出 | |
| 7 | + /// </summary> | |
| 8 | + public class DirectorSalaryOutput | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 主键ID | |
| 12 | + /// </summary> | |
| 13 | + public string Id { get; set; } | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 门店名称 | |
| 17 | + /// </summary> | |
| 18 | + public string StoreName { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 员工姓名 | |
| 22 | + /// </summary> | |
| 23 | + public string EmployeeName { get; set; } | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 岗位 | |
| 27 | + /// </summary> | |
| 28 | + public string Position { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 门店总业绩 | |
| 32 | + /// </summary> | |
| 33 | + public decimal StoreTotalPerformance { get; set; } | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 门店开单业绩 | |
| 37 | + /// </summary> | |
| 38 | + public decimal StoreBillingPerformance { get; set; } | |
| 39 | + | |
| 40 | + /// <summary> | |
| 41 | + /// 门店退卡业绩 | |
| 42 | + /// </summary> | |
| 43 | + public decimal StoreRefundPerformance { get; set; } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 门店生命线 | |
| 47 | + /// </summary> | |
| 48 | + public decimal StoreLifeline { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 业绩完成率 | |
| 52 | + /// </summary> | |
| 53 | + public decimal PerformanceCompletionRate { get; set; } | |
| 54 | + | |
| 55 | + /// <summary> | |
| 56 | + /// 业绩是否达标 | |
| 57 | + /// </summary> | |
| 58 | + public string PerformanceReached { get; set; } | |
| 59 | + | |
| 60 | + /// <summary> | |
| 61 | + /// 人头是否达标 | |
| 62 | + /// </summary> | |
| 63 | + public string HeadCountReached { get; set; } | |
| 64 | + | |
| 65 | + /// <summary> | |
| 66 | + /// 消耗是否达标 | |
| 67 | + /// </summary> | |
| 68 | + public string ConsumeReached { get; set; } | |
| 69 | + | |
| 70 | + /// <summary> | |
| 71 | + /// 考核扣款金额 | |
| 72 | + /// </summary> | |
| 73 | + public decimal AssessmentDeduction { get; set; } | |
| 74 | + | |
| 75 | + /// <summary> | |
| 76 | + /// 未达标指标数量 | |
| 77 | + /// </summary> | |
| 78 | + public int UnreachedIndicatorCount { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 进店消耗人数 | |
| 82 | + /// </summary> | |
| 83 | + public int HeadCount { get; set; } | |
| 84 | + | |
| 85 | + /// <summary> | |
| 86 | + /// 目标人头数 | |
| 87 | + /// </summary> | |
| 88 | + public decimal TargetHeadCount { get; set; } | |
| 89 | + | |
| 90 | + /// <summary> | |
| 91 | + /// 门店消耗金额 | |
| 92 | + /// </summary> | |
| 93 | + public decimal StoreConsume { get; set; } | |
| 94 | + | |
| 95 | + /// <summary> | |
| 96 | + /// 目标消耗金额 | |
| 97 | + /// </summary> | |
| 98 | + public decimal TargetConsume { get; set; } | |
| 99 | + | |
| 100 | + /// <summary> | |
| 101 | + /// ≤生命线部分提成比例 | |
| 102 | + /// </summary> | |
| 103 | + public decimal CommissionRateBelowLifeline { get; set; } | |
| 104 | + | |
| 105 | + /// <summary> | |
| 106 | + /// >生命线部分提成比例 | |
| 107 | + /// </summary> | |
| 108 | + public decimal CommissionRateAboveLifeline { get; set; } | |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// ≤生命线部分提成金额 | |
| 112 | + /// </summary> | |
| 113 | + public decimal CommissionAmountBelowLifeline { get; set; } | |
| 114 | + | |
| 115 | + /// <summary> | |
| 116 | + /// >生命线部分提成金额 | |
| 117 | + /// </summary> | |
| 118 | + public decimal CommissionAmountAboveLifeline { get; set; } | |
| 119 | + | |
| 120 | + /// <summary> | |
| 121 | + /// 提成总金额 | |
| 122 | + /// </summary> | |
| 123 | + public decimal TotalCommissionAmount { get; set; } | |
| 124 | + | |
| 125 | + /// <summary> | |
| 126 | + /// 底薪金额 | |
| 127 | + /// </summary> | |
| 128 | + public decimal BaseSalary { get; set; } | |
| 129 | + | |
| 130 | + /// <summary> | |
| 131 | + /// 实际底薪 | |
| 132 | + /// </summary> | |
| 133 | + public decimal ActualBaseSalary { get; set; } | |
| 134 | + | |
| 135 | + /// <summary> | |
| 136 | + /// 在店天数 | |
| 137 | + /// </summary> | |
| 138 | + public int WorkingDays { get; set; } | |
| 139 | + | |
| 140 | + /// <summary> | |
| 141 | + /// 请假天数 | |
| 142 | + /// </summary> | |
| 143 | + public int LeaveDays { get; set; } | |
| 144 | + | |
| 145 | + /// <summary> | |
| 146 | + /// 应发工资 | |
| 147 | + /// </summary> | |
| 148 | + public decimal GrossSalary { get; set; } | |
| 149 | + | |
| 150 | + /// <summary> | |
| 151 | + /// 实发工资 | |
| 152 | + /// </summary> | |
| 153 | + public decimal ActualSalary { get; set; } | |
| 154 | + | |
| 155 | + /// <summary> | |
| 156 | + /// 扣款合计 | |
| 157 | + /// </summary> | |
| 158 | + public decimal TotalDeduction { get; set; } | |
| 159 | + | |
| 160 | + /// <summary> | |
| 161 | + /// 补贴合计 | |
| 162 | + /// </summary> | |
| 163 | + public decimal TotalSubsidy { get; set; } | |
| 164 | + | |
| 165 | + /// <summary> | |
| 166 | + /// 发奖金 | |
| 167 | + /// </summary> | |
| 168 | + public decimal Bonus { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 退手机押金 | |
| 172 | + /// </summary> | |
| 173 | + public decimal ReturnPhoneDeposit { get; set; } | |
| 174 | + | |
| 175 | + /// <summary> | |
| 176 | + /// 退住宿押金 | |
| 177 | + /// </summary> | |
| 178 | + public decimal ReturnAccommodationDeposit { get; set; } | |
| 179 | + | |
| 180 | + /// <summary> | |
| 181 | + /// 当月是否发放 | |
| 182 | + /// </summary> | |
| 183 | + public string MonthlyPaymentStatus { get; set; } | |
| 184 | + | |
| 185 | + /// <summary> | |
| 186 | + /// 支付金额 | |
| 187 | + /// </summary> | |
| 188 | + public decimal PaidAmount { get; set; } | |
| 189 | + | |
| 190 | + /// <summary> | |
| 191 | + /// 待支付金额 | |
| 192 | + /// </summary> | |
| 193 | + public decimal PendingAmount { get; set; } | |
| 194 | + | |
| 195 | + /// <summary> | |
| 196 | + /// 补发上月 | |
| 197 | + /// </summary> | |
| 198 | + public decimal LastMonthSupplement { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 当月支付总额 | |
| 202 | + /// </summary> | |
| 203 | + public decimal MonthlyTotalPayment { get; set; } | |
| 204 | + | |
| 205 | + /// <summary> | |
| 206 | + /// 是否锁定 | |
| 207 | + /// </summary> | |
| 208 | + public int IsLocked { get; set; } | |
| 209 | + | |
| 210 | + /// <summary> | |
| 211 | + /// 更新时间 | |
| 212 | + /// </summary> | |
| 213 | + public DateTime UpdateTime { get; set; } | |
| 214 | + | |
| 215 | + /// <summary> | |
| 216 | + /// 门店类型 | |
| 217 | + /// </summary> | |
| 218 | + public int? StoreType { get; set; } | |
| 219 | + | |
| 220 | + /// <summary> | |
| 221 | + /// 门店类别 | |
| 222 | + /// </summary> | |
| 223 | + public int? StoreCategory { get; set; } | |
| 224 | + | |
| 225 | + /// <summary> | |
| 226 | + /// 是否新店 | |
| 227 | + /// </summary> | |
| 228 | + public string IsNewStore { get; set; } | |
| 229 | + | |
| 230 | + /// <summary> | |
| 231 | + /// 新店保护阶段 | |
| 232 | + /// </summary> | |
| 233 | + public int NewStoreProtectionStage { get; set; } | |
| 234 | + } | |
| 235 | +} | |
| 236 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationCrInput.cs
| ... | ... | @@ -12,56 +12,91 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication |
| 12 | 12 | /// 申请编号 |
| 13 | 13 | /// </summary> |
| 14 | 14 | public string id { get; set; } |
| 15 | - | |
| 15 | + | |
| 16 | 16 | /// <summary> |
| 17 | 17 | /// 申请人编号 |
| 18 | 18 | /// </summary> |
| 19 | 19 | public string applicationUserId { get; set; } |
| 20 | - | |
| 20 | + | |
| 21 | 21 | /// <summary> |
| 22 | 22 | /// 申请人姓名 |
| 23 | 23 | /// </summary> |
| 24 | 24 | public string applicationUserName { get; set; } |
| 25 | - | |
| 25 | + | |
| 26 | 26 | /// <summary> |
| 27 | 27 | /// 申请门店 |
| 28 | 28 | /// </summary> |
| 29 | 29 | public string applicationStoreId { get; set; } |
| 30 | - | |
| 30 | + | |
| 31 | 31 | /// <summary> |
| 32 | 32 | /// 申请时间 |
| 33 | 33 | /// </summary> |
| 34 | 34 | public DateTime? applicationTime { get; set; } |
| 35 | - | |
| 35 | + | |
| 36 | 36 | /// <summary> |
| 37 | 37 | /// 总金额 |
| 38 | 38 | /// </summary> |
| 39 | 39 | public string amount { get; set; } |
| 40 | - | |
| 40 | + | |
| 41 | 41 | /// <summary> |
| 42 | 42 | /// 审批人 |
| 43 | 43 | /// </summary> |
| 44 | 44 | public string approveUser { get; set; } |
| 45 | - | |
| 45 | + | |
| 46 | 46 | /// <summary> |
| 47 | 47 | /// 审批结果 |
| 48 | 48 | /// </summary> |
| 49 | 49 | public string approveStatus { get; set; } |
| 50 | - | |
| 50 | + | |
| 51 | 51 | /// <summary> |
| 52 | 52 | /// 审批时间 |
| 53 | 53 | /// </summary> |
| 54 | 54 | public DateTime? approveTime { get; set; } |
| 55 | - | |
| 55 | + | |
| 56 | 56 | /// <summary> |
| 57 | 57 | /// 关联购买编号 |
| 58 | 58 | /// </summary> |
| 59 | 59 | public string purchaseRecordsId { get; set; } |
| 60 | - | |
| 60 | + | |
| 61 | 61 | /// <summary> |
| 62 | 62 | /// 选中的购买记录ID列表(用于更新购买记录的审批单编号) |
| 63 | 63 | /// </summary> |
| 64 | 64 | public List<string> selectedPurchaseRecordIds { get; set; } |
| 65 | - | |
| 65 | + | |
| 66 | + /// <summary> | |
| 67 | + /// 节点配置列表(3-5个节点) | |
| 68 | + /// </summary> | |
| 69 | + public List<ApprovalNodeConfig> nodes { get; set; } | |
| 70 | + } | |
| 71 | + | |
| 72 | + /// <summary> | |
| 73 | + /// 审批节点配置 | |
| 74 | + /// </summary> | |
| 75 | + public class ApprovalNodeConfig | |
| 76 | + { | |
| 77 | + /// <summary> | |
| 78 | + /// 节点顺序(1, 2, 3, 4, 5) | |
| 79 | + /// </summary> | |
| 80 | + public int nodeOrder { get; set; } | |
| 81 | + | |
| 82 | + /// <summary> | |
| 83 | + /// 节点名称(如:部门经理审批、财务审批等) | |
| 84 | + /// </summary> | |
| 85 | + public string nodeName { get; set; } | |
| 86 | + | |
| 87 | + /// <summary> | |
| 88 | + /// 审批类型(会签/或签) | |
| 89 | + /// </summary> | |
| 90 | + public string approvalType { get; set; } | |
| 91 | + | |
| 92 | + /// <summary> | |
| 93 | + /// 审批人ID列表(可多选) | |
| 94 | + /// </summary> | |
| 95 | + public List<string> approverIds { get; set; } | |
| 96 | + | |
| 97 | + /// <summary> | |
| 98 | + /// 审批人姓名列表(用于显示) | |
| 99 | + /// </summary> | |
| 100 | + public List<string> approverNames { get; set; } | |
| 66 | 101 | } |
| 67 | 102 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationListOutput.cs
| ... | ... | @@ -56,6 +56,21 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication |
| 56 | 56 | /// 关联购买编号 |
| 57 | 57 | /// </summary> |
| 58 | 58 | public string purchaseRecordsId { get; set; } |
| 59 | + | |
| 60 | + /// <summary> | |
| 61 | + /// 当前审批人(多个用逗号分隔) | |
| 62 | + /// </summary> | |
| 63 | + public string currentApprovers { get; set; } | |
| 64 | + | |
| 65 | + /// <summary> | |
| 66 | + /// 当前节点顺序 | |
| 67 | + /// </summary> | |
| 68 | + public int? currentNodeOrder { get; set; } | |
| 69 | + | |
| 70 | + /// <summary> | |
| 71 | + /// 节点总数 | |
| 72 | + /// </summary> | |
| 73 | + public int? nodeCount { get; set; } | |
| 59 | 74 | |
| 60 | 75 | } |
| 61 | 76 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs
| ... | ... | @@ -111,5 +111,340 @@ namespace NCC.Extend.Entitys.Dto.LqSalary |
| 111 | 111 | /// 更新时间 |
| 112 | 112 | /// </summary> |
| 113 | 113 | public DateTime UpdateTime { get; set; } |
| 114 | + | |
| 115 | + /// <summary> | |
| 116 | + /// 是否新店 | |
| 117 | + /// </summary> | |
| 118 | + public string IsNewStore { get; set; } | |
| 119 | + | |
| 120 | + /// <summary> | |
| 121 | + /// 新店保护阶段 | |
| 122 | + /// </summary> | |
| 123 | + public int NewStoreProtectionStage { get; set; } | |
| 124 | + | |
| 125 | + /// <summary> | |
| 126 | + /// 门店类型 | |
| 127 | + /// </summary> | |
| 128 | + public int? StoreType { get; set; } | |
| 129 | + | |
| 130 | + /// <summary> | |
| 131 | + /// 门店类别 | |
| 132 | + /// </summary> | |
| 133 | + public int? StoreCategory { get; set; } | |
| 134 | + | |
| 135 | + /// <summary> | |
| 136 | + /// 实际基础业绩 | |
| 137 | + /// </summary> | |
| 138 | + public decimal ActualBasePerformance { get; set; } | |
| 139 | + | |
| 140 | + /// <summary> | |
| 141 | + /// 实际合作业绩 | |
| 142 | + /// </summary> | |
| 143 | + public decimal ActualCooperationPerformance { get; set; } | |
| 144 | + | |
| 145 | + /// <summary> | |
| 146 | + /// 新客业绩提成 | |
| 147 | + /// </summary> | |
| 148 | + public decimal NewCustomerCommission { get; set; } | |
| 149 | + | |
| 150 | + /// <summary> | |
| 151 | + /// 升单业绩提成 | |
| 152 | + /// </summary> | |
| 153 | + public decimal UpgradeCommission { get; set; } | |
| 154 | + | |
| 155 | + /// <summary> | |
| 156 | + /// 新客业绩 | |
| 157 | + /// </summary> | |
| 158 | + public decimal NewCustomerPerformance { get; set; } | |
| 159 | + | |
| 160 | + /// <summary> | |
| 161 | + /// 新客转化率 | |
| 162 | + /// </summary> | |
| 163 | + public decimal NewCustomerConversionRate { get; set; } | |
| 164 | + | |
| 165 | + /// <summary> | |
| 166 | + /// 新客提点 | |
| 167 | + /// </summary> | |
| 168 | + public decimal NewCustomerPoint { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 升单人头数 | |
| 172 | + /// </summary> | |
| 173 | + public decimal UpgradeCustomerCount { get; set; } | |
| 174 | + | |
| 175 | + /// <summary> | |
| 176 | + /// 升单业绩 | |
| 177 | + /// </summary> | |
| 178 | + public decimal UpgradePerformance { get; set; } | |
| 179 | + | |
| 180 | + /// <summary> | |
| 181 | + /// 升单提点 | |
| 182 | + /// </summary> | |
| 183 | + public decimal UpgradePoint { get; set; } | |
| 184 | + | |
| 185 | + /// <summary> | |
| 186 | + /// 基础奖励业绩 | |
| 187 | + /// </summary> | |
| 188 | + public decimal BaseRewardPerformance { get; set; } | |
| 189 | + | |
| 190 | + /// <summary> | |
| 191 | + /// 合作奖励业绩 | |
| 192 | + /// </summary> | |
| 193 | + public decimal CooperationRewardPerformance { get; set; } | |
| 194 | + | |
| 195 | + /// <summary> | |
| 196 | + /// 日均消耗 | |
| 197 | + /// </summary> | |
| 198 | + public decimal DailyAverageConsumption { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 日均项目数 | |
| 202 | + /// </summary> | |
| 203 | + public decimal DailyAverageProjectCount { get; set; } | |
| 204 | + | |
| 205 | + /// <summary> | |
| 206 | + /// 战队总消耗 | |
| 207 | + /// </summary> | |
| 208 | + public decimal TeamTotalConsumption { get; set; } | |
| 209 | + | |
| 210 | + /// <summary> | |
| 211 | + /// 门店ID | |
| 212 | + /// </summary> | |
| 213 | + public string StoreId { get; set; } | |
| 214 | + | |
| 215 | + /// <summary> | |
| 216 | + /// 员工ID | |
| 217 | + /// </summary> | |
| 218 | + public string EmployeeId { get; set; } | |
| 219 | + | |
| 220 | + /// <summary> | |
| 221 | + /// 金三角ID | |
| 222 | + /// </summary> | |
| 223 | + public string GoldTriangleId { get; set; } | |
| 224 | + | |
| 225 | + /// <summary> | |
| 226 | + /// 门店总业绩 | |
| 227 | + /// </summary> | |
| 228 | + public decimal StoreTotalPerformance { get; set; } | |
| 229 | + | |
| 230 | + /// <summary> | |
| 231 | + /// 队伍业绩 | |
| 232 | + /// </summary> | |
| 233 | + public decimal TeamPerformance { get; set; } | |
| 234 | + | |
| 235 | + /// <summary> | |
| 236 | + /// 占比 | |
| 237 | + /// </summary> | |
| 238 | + public decimal Percentage { get; set; } | |
| 239 | + | |
| 240 | + /// <summary> | |
| 241 | + /// 其他业绩加 | |
| 242 | + /// </summary> | |
| 243 | + public decimal OtherPerformanceAdd { get; set; } | |
| 244 | + | |
| 245 | + /// <summary> | |
| 246 | + /// 其他业绩减 | |
| 247 | + /// </summary> | |
| 248 | + public decimal OtherPerformanceSubtract { get; set; } | |
| 249 | + | |
| 250 | + /// <summary> | |
| 251 | + /// 请假天数 | |
| 252 | + /// </summary> | |
| 253 | + public decimal LeaveDays { get; set; } | |
| 254 | + | |
| 255 | + /// <summary> | |
| 256 | + /// 提点 | |
| 257 | + /// </summary> | |
| 258 | + public decimal CommissionPoint { get; set; } | |
| 259 | + | |
| 260 | + /// <summary> | |
| 261 | + /// 基础业绩提成 | |
| 262 | + /// </summary> | |
| 263 | + public decimal BasePerformanceCommission { get; set; } | |
| 264 | + | |
| 265 | + /// <summary> | |
| 266 | + /// 合作业绩提成 | |
| 267 | + /// </summary> | |
| 268 | + public decimal CooperationPerformanceCommission { get; set; } | |
| 269 | + | |
| 270 | + /// <summary> | |
| 271 | + /// 顾问提成 | |
| 272 | + /// </summary> | |
| 273 | + public decimal ConsultantCommission { get; set; } | |
| 274 | + | |
| 275 | + /// <summary> | |
| 276 | + /// 门店T区提成 | |
| 277 | + /// </summary> | |
| 278 | + public decimal StoreTZoneCommission { get; set; } | |
| 279 | + | |
| 280 | + /// <summary> | |
| 281 | + /// 额外手工费 | |
| 282 | + /// </summary> | |
| 283 | + public decimal OutherHandworkFee { get; set; } | |
| 284 | + | |
| 285 | + /// <summary> | |
| 286 | + /// 车补 | |
| 287 | + /// </summary> | |
| 288 | + public decimal TransportationAllowance { get; set; } | |
| 289 | + | |
| 290 | + /// <summary> | |
| 291 | + /// 少休费 | |
| 292 | + /// </summary> | |
| 293 | + public decimal LessRest { get; set; } | |
| 294 | + | |
| 295 | + /// <summary> | |
| 296 | + /// 全勤奖 | |
| 297 | + /// </summary> | |
| 298 | + public decimal FullAttendance { get; set; } | |
| 299 | + | |
| 300 | + /// <summary> | |
| 301 | + /// 核算应发工资 | |
| 302 | + /// </summary> | |
| 303 | + public decimal CalculatedGrossSalary { get; set; } | |
| 304 | + | |
| 305 | + /// <summary> | |
| 306 | + /// 保底工资 | |
| 307 | + /// </summary> | |
| 308 | + public decimal GuaranteedSalary { get; set; } | |
| 309 | + | |
| 310 | + /// <summary> | |
| 311 | + /// 保底请假扣款 | |
| 312 | + /// </summary> | |
| 313 | + public decimal GuaranteedLeaveDeduction { get; set; } | |
| 314 | + | |
| 315 | + /// <summary> | |
| 316 | + /// 保底底薪 | |
| 317 | + /// </summary> | |
| 318 | + public decimal GuaranteedBaseSalary { get; set; } | |
| 319 | + | |
| 320 | + /// <summary> | |
| 321 | + /// 保底补差 | |
| 322 | + /// </summary> | |
| 323 | + public decimal GuaranteedSupplement { get; set; } | |
| 324 | + | |
| 325 | + /// <summary> | |
| 326 | + /// 最终应发工资 | |
| 327 | + /// </summary> | |
| 328 | + public decimal FinalGrossSalary { get; set; } | |
| 329 | + | |
| 330 | + /// <summary> | |
| 331 | + /// 当月培训补贴 | |
| 332 | + /// </summary> | |
| 333 | + public decimal MonthlyTrainingSubsidy { get; set; } | |
| 334 | + | |
| 335 | + /// <summary> | |
| 336 | + /// 当月交通补贴 | |
| 337 | + /// </summary> | |
| 338 | + public decimal MonthlyTransportSubsidy { get; set; } | |
| 339 | + | |
| 340 | + /// <summary> | |
| 341 | + /// 上月培训补贴 | |
| 342 | + /// </summary> | |
| 343 | + public decimal LastMonthTrainingSubsidy { get; set; } | |
| 344 | + | |
| 345 | + /// <summary> | |
| 346 | + /// 上月交通补贴 | |
| 347 | + /// </summary> | |
| 348 | + public decimal LastMonthTransportSubsidy { get; set; } | |
| 349 | + | |
| 350 | + /// <summary> | |
| 351 | + /// 缺卡扣款 | |
| 352 | + /// </summary> | |
| 353 | + public decimal MissingCard { get; set; } | |
| 354 | + | |
| 355 | + /// <summary> | |
| 356 | + /// 迟到扣款 | |
| 357 | + /// </summary> | |
| 358 | + public decimal LateArrival { get; set; } | |
| 359 | + | |
| 360 | + /// <summary> | |
| 361 | + /// 请假扣款 | |
| 362 | + /// </summary> | |
| 363 | + public decimal LeaveDeduction { get; set; } | |
| 364 | + | |
| 365 | + /// <summary> | |
| 366 | + /// 扣社保 | |
| 367 | + /// </summary> | |
| 368 | + public decimal SocialInsuranceDeduction { get; set; } | |
| 369 | + | |
| 370 | + /// <summary> | |
| 371 | + /// 扣除奖励 | |
| 372 | + /// </summary> | |
| 373 | + public decimal RewardDeduction { get; set; } | |
| 374 | + | |
| 375 | + /// <summary> | |
| 376 | + /// 扣住宿费 | |
| 377 | + /// </summary> | |
| 378 | + public decimal AccommodationDeduction { get; set; } | |
| 379 | + | |
| 380 | + /// <summary> | |
| 381 | + /// 扣学习期费用 | |
| 382 | + /// </summary> | |
| 383 | + public decimal StudyPeriodDeduction { get; set; } | |
| 384 | + | |
| 385 | + /// <summary> | |
| 386 | + /// 扣工作服费用 | |
| 387 | + /// </summary> | |
| 388 | + public decimal WorkClothesDeduction { get; set; } | |
| 389 | + | |
| 390 | + /// <summary> | |
| 391 | + /// 发奖金 | |
| 392 | + /// </summary> | |
| 393 | + public decimal Bonus { get; set; } | |
| 394 | + | |
| 395 | + /// <summary> | |
| 396 | + /// 退手机押金 | |
| 397 | + /// </summary> | |
| 398 | + public decimal ReturnPhoneDeposit { get; set; } | |
| 399 | + | |
| 400 | + /// <summary> | |
| 401 | + /// 退住宿押金 | |
| 402 | + /// </summary> | |
| 403 | + public decimal ReturnAccommodationDeposit { get; set; } | |
| 404 | + | |
| 405 | + /// <summary> | |
| 406 | + /// 当月是否发放 | |
| 407 | + /// </summary> | |
| 408 | + public string MonthlyPaymentStatus { get; set; } | |
| 409 | + | |
| 410 | + /// <summary> | |
| 411 | + /// 支付金额 | |
| 412 | + /// </summary> | |
| 413 | + public decimal PaidAmount { get; set; } | |
| 414 | + | |
| 415 | + /// <summary> | |
| 416 | + /// 待支付金额 | |
| 417 | + /// </summary> | |
| 418 | + public decimal PendingAmount { get; set; } | |
| 419 | + | |
| 420 | + /// <summary> | |
| 421 | + /// 补发上月 | |
| 422 | + /// </summary> | |
| 423 | + public decimal LastMonthSupplement { get; set; } | |
| 424 | + | |
| 425 | + /// <summary> | |
| 426 | + /// 当月支付总额 | |
| 427 | + /// </summary> | |
| 428 | + public decimal MonthlyTotalPayment { get; set; } | |
| 429 | + | |
| 430 | + /// <summary> | |
| 431 | + /// 统计月份(YYYYMM) | |
| 432 | + /// </summary> | |
| 433 | + public string StatisticsMonth { get; set; } | |
| 434 | + | |
| 435 | + /// <summary> | |
| 436 | + /// 创建时间 | |
| 437 | + /// </summary> | |
| 438 | + public DateTime CreateTime { get; set; } | |
| 439 | + | |
| 440 | + /// <summary> | |
| 441 | + /// 创建人 | |
| 442 | + /// </summary> | |
| 443 | + public string CreateUser { get; set; } | |
| 444 | + | |
| 445 | + /// <summary> | |
| 446 | + /// 更新人 | |
| 447 | + /// </summary> | |
| 448 | + public string UpdateUser { get; set; } | |
| 114 | 449 | } |
| 115 | 450 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationImportInput.cs
0 → 100644
| 1 | +namespace NCC.Extend.Entitys.Dto.LqSalaryExtraCalculation | |
| 2 | +{ | |
| 3 | + /// <summary> | |
| 4 | + /// 健康师工资额外计算导入数据 | |
| 5 | + /// </summary> | |
| 6 | + public class SalaryExtraCalculationImportInput | |
| 7 | + { | |
| 8 | + /// <summary> | |
| 9 | + /// ID(可选,用于更新已有记录) | |
| 10 | + /// </summary> | |
| 11 | + public string Id { get; set; } | |
| 12 | + | |
| 13 | + /// <summary> | |
| 14 | + /// 健康师姓名 | |
| 15 | + /// </summary> | |
| 16 | + public string EmployeeName { get; set; } | |
| 17 | + | |
| 18 | + /// <summary> | |
| 19 | + /// 健康师电话 | |
| 20 | + /// </summary> | |
| 21 | + public string EmployeePhone { get; set; } | |
| 22 | + | |
| 23 | + /// <summary> | |
| 24 | + /// 年份 | |
| 25 | + /// </summary> | |
| 26 | + public int Year { get; set; } | |
| 27 | + | |
| 28 | + /// <summary> | |
| 29 | + /// 月份 | |
| 30 | + /// </summary> | |
| 31 | + public int Month { get; set; } | |
| 32 | + | |
| 33 | + /// <summary> | |
| 34 | + /// 基础奖励业绩 | |
| 35 | + /// </summary> | |
| 36 | + public decimal BaseRewardPerformance { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 合作奖励业绩 | |
| 40 | + /// </summary> | |
| 41 | + public decimal CooperationRewardPerformance { get; set; } | |
| 42 | + | |
| 43 | + /// <summary> | |
| 44 | + /// 新客业绩 | |
| 45 | + /// </summary> | |
| 46 | + public decimal NewCustomerPerformance { get; set; } | |
| 47 | + | |
| 48 | + /// <summary> | |
| 49 | + /// 新客成交率 | |
| 50 | + /// </summary> | |
| 51 | + public decimal NewCustomerConversionRate { get; set; } | |
| 52 | + | |
| 53 | + /// <summary> | |
| 54 | + /// 升单业绩 | |
| 55 | + /// </summary> | |
| 56 | + public decimal UpgradePerformance { get; set; } | |
| 57 | + | |
| 58 | + /// <summary> | |
| 59 | + /// 升单成交率 | |
| 60 | + /// </summary> | |
| 61 | + public decimal UpgradeConversionRate { get; set; } | |
| 62 | + | |
| 63 | + /// <summary> | |
| 64 | + /// 升单人头数 | |
| 65 | + /// </summary> | |
| 66 | + public decimal UpgradeCustomerCount { get; set; } | |
| 67 | + | |
| 68 | + /// <summary> | |
| 69 | + /// 其他业绩加 | |
| 70 | + /// </summary> | |
| 71 | + public decimal OtherPerformanceAdd { get; set; } | |
| 72 | + | |
| 73 | + /// <summary> | |
| 74 | + /// 其他业绩减 | |
| 75 | + /// </summary> | |
| 76 | + public decimal OtherPerformanceSubtract { get; set; } | |
| 77 | + } | |
| 78 | +} | |
| 79 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalaryExtraCalculation/SalaryExtraCalculationInput.cs
0 → 100644
| 1 | +using NCC.Common.Filter; | |
| 2 | +using System; | |
| 3 | + | |
| 4 | +namespace NCC.Extend.Entitys.Dto.LqSalaryExtraCalculation | |
| 5 | +{ | |
| 6 | + /// <summary> | |
| 7 | + /// 健康师工资额外计算查询参数 | |
| 8 | + /// </summary> | |
| 9 | + public class SalaryExtraCalculationInput : 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 EmployeeId { 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/LqSalaryExtraCalculation/SalaryExtraCalculationOutput.cs
0 → 100644
| 1 | +using System; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Dto.LqSalaryExtraCalculation | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 健康师工资额外计算输出 | |
| 7 | + /// </summary> | |
| 8 | + public class SalaryExtraCalculationOutput | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 主键ID | |
| 12 | + /// </summary> | |
| 13 | + public string Id { get; set; } | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 健康师ID | |
| 17 | + /// </summary> | |
| 18 | + public string EmployeeId { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 健康师姓名 | |
| 22 | + /// </summary> | |
| 23 | + public string EmployeeName { get; set; } | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 健康师电话 | |
| 27 | + /// </summary> | |
| 28 | + public string EmployeePhone { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 年份 | |
| 32 | + /// </summary> | |
| 33 | + public int Year { get; set; } | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 月份 | |
| 37 | + /// </summary> | |
| 38 | + public int Month { get; set; } | |
| 39 | + | |
| 40 | + /// <summary> | |
| 41 | + /// 基础奖励业绩 | |
| 42 | + /// </summary> | |
| 43 | + public decimal BaseRewardPerformance { get; set; } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 合作奖励业绩 | |
| 47 | + /// </summary> | |
| 48 | + public decimal CooperationRewardPerformance { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 新客业绩 | |
| 52 | + /// </summary> | |
| 53 | + public decimal NewCustomerPerformance { get; set; } | |
| 54 | + | |
| 55 | + /// <summary> | |
| 56 | + /// 新客成交率 | |
| 57 | + /// </summary> | |
| 58 | + public decimal NewCustomerConversionRate { get; set; } | |
| 59 | + | |
| 60 | + /// <summary> | |
| 61 | + /// 升单业绩 | |
| 62 | + /// </summary> | |
| 63 | + public decimal UpgradePerformance { get; set; } | |
| 64 | + | |
| 65 | + /// <summary> | |
| 66 | + /// 升单成交率 | |
| 67 | + /// </summary> | |
| 68 | + public decimal UpgradeConversionRate { get; set; } | |
| 69 | + | |
| 70 | + /// <summary> | |
| 71 | + /// 升单人头数 | |
| 72 | + /// </summary> | |
| 73 | + public decimal UpgradeCustomerCount { get; set; } | |
| 74 | + | |
| 75 | + /// <summary> | |
| 76 | + /// 其他业绩加 | |
| 77 | + /// </summary> | |
| 78 | + public decimal OtherPerformanceAdd { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 其他业绩减 | |
| 82 | + /// </summary> | |
| 83 | + public decimal OtherPerformanceSubtract { get; set; } | |
| 84 | + } | |
| 85 | +} | |
| 86 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/LqReimbursementApplicationEntity.cs
| ... | ... | @@ -16,60 +16,96 @@ namespace NCC.Extend.Entitys |
| 16 | 16 | /// </summary> |
| 17 | 17 | [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] |
| 18 | 18 | public string Id { get; set; } |
| 19 | - | |
| 19 | + | |
| 20 | 20 | /// <summary> |
| 21 | 21 | /// 申请人编号 |
| 22 | 22 | /// </summary> |
| 23 | - [SugarColumn(ColumnName = "F_ApplicationUserId")] | |
| 23 | + [SugarColumn(ColumnName = "F_ApplicationUserId")] | |
| 24 | 24 | public string ApplicationUserId { get; set; } |
| 25 | - | |
| 25 | + | |
| 26 | 26 | /// <summary> |
| 27 | 27 | /// 申请人姓名 |
| 28 | 28 | /// </summary> |
| 29 | - [SugarColumn(ColumnName = "F_ApplicationUserName")] | |
| 29 | + [SugarColumn(ColumnName = "F_ApplicationUserName")] | |
| 30 | 30 | public string ApplicationUserName { get; set; } |
| 31 | - | |
| 31 | + | |
| 32 | 32 | /// <summary> |
| 33 | 33 | /// 申请门店 |
| 34 | 34 | /// </summary> |
| 35 | - [SugarColumn(ColumnName = "F_ApplicationStoreId")] | |
| 35 | + [SugarColumn(ColumnName = "F_ApplicationStoreId")] | |
| 36 | 36 | public string ApplicationStoreId { get; set; } |
| 37 | - | |
| 37 | + | |
| 38 | 38 | /// <summary> |
| 39 | 39 | /// 申请时间 |
| 40 | 40 | /// </summary> |
| 41 | - [SugarColumn(ColumnName = "F_ApplicationTime")] | |
| 41 | + [SugarColumn(ColumnName = "F_ApplicationTime")] | |
| 42 | 42 | public DateTime? ApplicationTime { get; set; } |
| 43 | - | |
| 43 | + | |
| 44 | 44 | /// <summary> |
| 45 | 45 | /// 总金额 |
| 46 | 46 | /// </summary> |
| 47 | - [SugarColumn(ColumnName = "F_Amount")] | |
| 47 | + [SugarColumn(ColumnName = "F_Amount")] | |
| 48 | 48 | public string Amount { get; set; } |
| 49 | - | |
| 49 | + | |
| 50 | 50 | /// <summary> |
| 51 | 51 | /// 审批人 |
| 52 | 52 | /// </summary> |
| 53 | - [SugarColumn(ColumnName = "F_ApproveUser")] | |
| 53 | + [SugarColumn(ColumnName = "F_ApproveUser")] | |
| 54 | 54 | public string ApproveUser { get; set; } |
| 55 | - | |
| 55 | + | |
| 56 | 56 | /// <summary> |
| 57 | 57 | /// 审批结果 |
| 58 | 58 | /// </summary> |
| 59 | - [SugarColumn(ColumnName = "F_ApproveStatus")] | |
| 59 | + [SugarColumn(ColumnName = "F_ApproveStatus")] | |
| 60 | 60 | public string ApproveStatus { get; set; } |
| 61 | - | |
| 61 | + | |
| 62 | 62 | /// <summary> |
| 63 | 63 | /// 审批时间 |
| 64 | 64 | /// </summary> |
| 65 | - [SugarColumn(ColumnName = "F_ApproveTime")] | |
| 65 | + [SugarColumn(ColumnName = "F_ApproveTime")] | |
| 66 | 66 | public DateTime? ApproveTime { get; set; } |
| 67 | - | |
| 67 | + | |
| 68 | 68 | /// <summary> |
| 69 | 69 | /// 关联购买编号 |
| 70 | 70 | /// </summary> |
| 71 | - [SugarColumn(ColumnName = "F_PurchaseRecordsId")] | |
| 71 | + [SugarColumn(ColumnName = "F_PurchaseRecordsId")] | |
| 72 | 72 | public string PurchaseRecordsId { get; set; } |
| 73 | - | |
| 73 | + | |
| 74 | + /// <summary> | |
| 75 | + /// 节点数量(3,4,5) | |
| 76 | + /// </summary> | |
| 77 | + [SugarColumn(ColumnName = "F_NodeCount")] | |
| 78 | + public int? NodeCount { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 当前审批节点(0-待审批,1-N节点,N+1-已完成) | |
| 82 | + /// </summary> | |
| 83 | + [SugarColumn(ColumnName = "F_CurrentNodeOrder")] | |
| 84 | + public int? CurrentNodeOrder { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 当前节点ID | |
| 88 | + /// </summary> | |
| 89 | + [SugarColumn(ColumnName = "F_CurrentNodeId")] | |
| 90 | + public string CurrentNodeId { get; set; } | |
| 91 | + | |
| 92 | + /// <summary> | |
| 93 | + /// 审批状态(待审批/审批中/已通过/未通过/已退回) | |
| 94 | + /// </summary> | |
| 95 | + [SugarColumn(ColumnName = "F_ApprovalStatus")] | |
| 96 | + public string ApprovalStatus { get; set; } | |
| 97 | + | |
| 98 | + /// <summary> | |
| 99 | + /// 退回节点 | |
| 100 | + /// </summary> | |
| 101 | + [SugarColumn(ColumnName = "F_ReturnedNodeOrder")] | |
| 102 | + public int? ReturnedNodeOrder { get; set; } | |
| 103 | + | |
| 104 | + /// <summary> | |
| 105 | + /// 退回原因 | |
| 106 | + /// </summary> | |
| 107 | + [SugarColumn(ColumnName = "F_ReturnedReason")] | |
| 108 | + public string ReturnedReason { get; set; } | |
| 109 | + | |
| 74 | 110 | } |
| 75 | 111 | } |
| 76 | 112 | \ No newline at end of file | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_assistant_salary_statistics/LqAssistantSalaryStatisticsEntity.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using NCC.Common.Const; | |
| 3 | +using SqlSugar; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.lq_assistant_salary_statistics | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 店助工资统计表 | |
| 9 | + /// </summary> | |
| 10 | + [SugarTable("lq_assistant_salary_statistics")] | |
| 11 | + [Tenant(ClaimConst.TENANT_ID)] | |
| 12 | + public class LqAssistantSalaryStatisticsEntity | |
| 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 | + /// 门店名称 | |
| 28 | + /// </summary> | |
| 29 | + [SugarColumn(ColumnName = "F_StoreName")] | |
| 30 | + public string StoreName { get; set; } | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 核算岗位 | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_Position")] | |
| 36 | + public string Position { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 员工姓名 | |
| 40 | + /// </summary> | |
| 41 | + [SugarColumn(ColumnName = "F_EmployeeName")] | |
| 42 | + public string EmployeeName { get; set; } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 员工ID | |
| 46 | + /// </summary> | |
| 47 | + [SugarColumn(ColumnName = "F_EmployeeId")] | |
| 48 | + public string EmployeeId { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 统计月份(YYYYMM) | |
| 52 | + /// </summary> | |
| 53 | + [SugarColumn(ColumnName = "F_StatisticsMonth")] | |
| 54 | + public string StatisticsMonth { get; set; } | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 门店类型 | |
| 58 | + /// </summary> | |
| 59 | + [SugarColumn(ColumnName = "F_StoreType")] | |
| 60 | + public int? StoreType { get; set; } | |
| 61 | + | |
| 62 | + /// <summary> | |
| 63 | + /// 门店类别 | |
| 64 | + /// </summary> | |
| 65 | + [SugarColumn(ColumnName = "F_StoreCategory")] | |
| 66 | + public int? StoreCategory { get; set; } | |
| 67 | + | |
| 68 | + /// <summary> | |
| 69 | + /// 是否新店 | |
| 70 | + /// </summary> | |
| 71 | + [SugarColumn(ColumnName = "F_IsNewStore")] | |
| 72 | + public string IsNewStore { get; set; } | |
| 73 | + | |
| 74 | + /// <summary> | |
| 75 | + /// 新店保护阶段 | |
| 76 | + /// </summary> | |
| 77 | + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")] | |
| 78 | + public int NewStoreProtectionStage { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 门店总业绩 | |
| 82 | + /// </summary> | |
| 83 | + [SugarColumn(ColumnName = "F_StoreTotalPerformance")] | |
| 84 | + public decimal StoreTotalPerformance { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 门店开单业绩 | |
| 88 | + /// </summary> | |
| 89 | + [SugarColumn(ColumnName = "F_StoreBillingPerformance")] | |
| 90 | + public decimal StoreBillingPerformance { get; set; } | |
| 91 | + | |
| 92 | + /// <summary> | |
| 93 | + /// 门店退卡业绩 | |
| 94 | + /// </summary> | |
| 95 | + [SugarColumn(ColumnName = "F_StoreRefundPerformance")] | |
| 96 | + public decimal StoreRefundPerformance { get; set; } | |
| 97 | + | |
| 98 | + /// <summary> | |
| 99 | + /// 门店生命线 | |
| 100 | + /// </summary> | |
| 101 | + [SugarColumn(ColumnName = "F_StoreLifeline")] | |
| 102 | + public decimal StoreLifeline { get; set; } | |
| 103 | + | |
| 104 | + /// <summary> | |
| 105 | + /// 业绩完成率 | |
| 106 | + /// </summary> | |
| 107 | + [SugarColumn(ColumnName = "F_PerformanceCompletionRate")] | |
| 108 | + public decimal PerformanceCompletionRate { get; set; } | |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// 提成比例 | |
| 112 | + /// </summary> | |
| 113 | + [SugarColumn(ColumnName = "F_CommissionRate")] | |
| 114 | + public decimal CommissionRate { get; set; } | |
| 115 | + | |
| 116 | + /// <summary> | |
| 117 | + /// 提成金额 | |
| 118 | + /// </summary> | |
| 119 | + [SugarColumn(ColumnName = "F_CommissionAmount")] | |
| 120 | + public decimal CommissionAmount { get; set; } | |
| 121 | + | |
| 122 | + /// <summary> | |
| 123 | + /// 进店消耗人数 | |
| 124 | + /// </summary> | |
| 125 | + [SugarColumn(ColumnName = "F_HeadCount")] | |
| 126 | + public int HeadCount { get; set; } | |
| 127 | + | |
| 128 | + /// <summary> | |
| 129 | + /// 第一阶段目标人数 | |
| 130 | + /// </summary> | |
| 131 | + [SugarColumn(ColumnName = "F_Stage1TargetHeadCount")] | |
| 132 | + public int Stage1TargetHeadCount { get; set; } | |
| 133 | + | |
| 134 | + /// <summary> | |
| 135 | + /// 第二阶段目标人数 | |
| 136 | + /// </summary> | |
| 137 | + [SugarColumn(ColumnName = "F_Stage2TargetHeadCount")] | |
| 138 | + public int Stage2TargetHeadCount { get; set; } | |
| 139 | + | |
| 140 | + /// <summary> | |
| 141 | + /// 是否达到第一阶段 | |
| 142 | + /// </summary> | |
| 143 | + [SugarColumn(ColumnName = "F_ReachedStage1")] | |
| 144 | + public string ReachedStage1 { get; set; } | |
| 145 | + | |
| 146 | + /// <summary> | |
| 147 | + /// 是否达到第二阶段 | |
| 148 | + /// </summary> | |
| 149 | + [SugarColumn(ColumnName = "F_ReachedStage2")] | |
| 150 | + public string ReachedStage2 { get; set; } | |
| 151 | + | |
| 152 | + /// <summary> | |
| 153 | + /// 阶段奖励金额 | |
| 154 | + /// </summary> | |
| 155 | + [SugarColumn(ColumnName = "F_StageRewardAmount")] | |
| 156 | + public decimal StageRewardAmount { get; set; } | |
| 157 | + | |
| 158 | + /// <summary> | |
| 159 | + /// 第一阶段奖励 | |
| 160 | + /// </summary> | |
| 161 | + [SugarColumn(ColumnName = "F_Stage1Reward")] | |
| 162 | + public decimal Stage1Reward { get; set; } | |
| 163 | + | |
| 164 | + /// <summary> | |
| 165 | + /// 第二阶段奖励 | |
| 166 | + /// </summary> | |
| 167 | + [SugarColumn(ColumnName = "F_Stage2Reward")] | |
| 168 | + public decimal Stage2Reward { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 底薪金额 | |
| 172 | + /// </summary> | |
| 173 | + [SugarColumn(ColumnName = "F_BaseSalary")] | |
| 174 | + public decimal BaseSalary { get; set; } | |
| 175 | + | |
| 176 | + /// <summary> | |
| 177 | + /// 手机管理费 | |
| 178 | + /// </summary> | |
| 179 | + [SugarColumn(ColumnName = "F_PhoneManagementFee")] | |
| 180 | + public decimal PhoneManagementFee { get; set; } | |
| 181 | + | |
| 182 | + /// <summary> | |
| 183 | + /// 在店天数 | |
| 184 | + /// </summary> | |
| 185 | + [SugarColumn(ColumnName = "F_WorkingDays")] | |
| 186 | + public int WorkingDays { get; set; } | |
| 187 | + | |
| 188 | + /// <summary> | |
| 189 | + /// 请假天数 | |
| 190 | + /// </summary> | |
| 191 | + [SugarColumn(ColumnName = "F_LeaveDays")] | |
| 192 | + public int LeaveDays { get; set; } | |
| 193 | + | |
| 194 | + /// <summary> | |
| 195 | + /// 应发工资 | |
| 196 | + /// </summary> | |
| 197 | + [SugarColumn(ColumnName = "F_GrossSalary")] | |
| 198 | + public decimal GrossSalary { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 实发工资 | |
| 202 | + /// </summary> | |
| 203 | + [SugarColumn(ColumnName = "F_ActualSalary")] | |
| 204 | + public decimal ActualSalary { get; set; } | |
| 205 | + | |
| 206 | + /// <summary> | |
| 207 | + /// 缺卡扣款 | |
| 208 | + /// </summary> | |
| 209 | + [SugarColumn(ColumnName = "F_MissingCard")] | |
| 210 | + public decimal MissingCard { get; set; } | |
| 211 | + | |
| 212 | + /// <summary> | |
| 213 | + /// 迟到扣款 | |
| 214 | + /// </summary> | |
| 215 | + [SugarColumn(ColumnName = "F_LateArrival")] | |
| 216 | + public decimal LateArrival { get; set; } | |
| 217 | + | |
| 218 | + /// <summary> | |
| 219 | + /// 请假扣款 | |
| 220 | + /// </summary> | |
| 221 | + [SugarColumn(ColumnName = "F_LeaveDeduction")] | |
| 222 | + public decimal LeaveDeduction { get; set; } | |
| 223 | + | |
| 224 | + /// <summary> | |
| 225 | + /// 扣社保 | |
| 226 | + /// </summary> | |
| 227 | + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")] | |
| 228 | + public decimal SocialInsuranceDeduction { get; set; } | |
| 229 | + | |
| 230 | + /// <summary> | |
| 231 | + /// 扣除奖励 | |
| 232 | + /// </summary> | |
| 233 | + [SugarColumn(ColumnName = "F_RewardDeduction")] | |
| 234 | + public decimal RewardDeduction { get; set; } | |
| 235 | + | |
| 236 | + /// <summary> | |
| 237 | + /// 扣住宿费 | |
| 238 | + /// </summary> | |
| 239 | + [SugarColumn(ColumnName = "F_AccommodationDeduction")] | |
| 240 | + public decimal AccommodationDeduction { get; set; } | |
| 241 | + | |
| 242 | + /// <summary> | |
| 243 | + /// 扣学习期费用 | |
| 244 | + /// </summary> | |
| 245 | + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")] | |
| 246 | + public decimal StudyPeriodDeduction { get; set; } | |
| 247 | + | |
| 248 | + /// <summary> | |
| 249 | + /// 扣工作服费用 | |
| 250 | + /// </summary> | |
| 251 | + [SugarColumn(ColumnName = "F_WorkClothesDeduction")] | |
| 252 | + public decimal WorkClothesDeduction { get; set; } | |
| 253 | + | |
| 254 | + /// <summary> | |
| 255 | + /// 扣款合计 | |
| 256 | + /// </summary> | |
| 257 | + [SugarColumn(ColumnName = "F_TotalDeduction")] | |
| 258 | + public decimal TotalDeduction { get; set; } | |
| 259 | + | |
| 260 | + /// <summary> | |
| 261 | + /// 当月培训补贴 | |
| 262 | + /// </summary> | |
| 263 | + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")] | |
| 264 | + public decimal MonthlyTrainingSubsidy { get; set; } | |
| 265 | + | |
| 266 | + /// <summary> | |
| 267 | + /// 当月交通补贴 | |
| 268 | + /// </summary> | |
| 269 | + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")] | |
| 270 | + public decimal MonthlyTransportSubsidy { get; set; } | |
| 271 | + | |
| 272 | + /// <summary> | |
| 273 | + /// 上月培训补贴 | |
| 274 | + /// </summary> | |
| 275 | + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")] | |
| 276 | + public decimal LastMonthTrainingSubsidy { get; set; } | |
| 277 | + | |
| 278 | + /// <summary> | |
| 279 | + /// 上月交通补贴 | |
| 280 | + /// </summary> | |
| 281 | + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")] | |
| 282 | + public decimal LastMonthTransportSubsidy { get; set; } | |
| 283 | + | |
| 284 | + /// <summary> | |
| 285 | + /// 补贴合计 | |
| 286 | + /// </summary> | |
| 287 | + [SugarColumn(ColumnName = "F_TotalSubsidy")] | |
| 288 | + public decimal TotalSubsidy { get; set; } | |
| 289 | + | |
| 290 | + /// <summary> | |
| 291 | + /// 发奖金 | |
| 292 | + /// </summary> | |
| 293 | + [SugarColumn(ColumnName = "F_Bonus")] | |
| 294 | + public decimal Bonus { get; set; } | |
| 295 | + | |
| 296 | + /// <summary> | |
| 297 | + /// 退手机押金 | |
| 298 | + /// </summary> | |
| 299 | + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")] | |
| 300 | + public decimal ReturnPhoneDeposit { get; set; } | |
| 301 | + | |
| 302 | + /// <summary> | |
| 303 | + /// 退住宿押金 | |
| 304 | + /// </summary> | |
| 305 | + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")] | |
| 306 | + public decimal ReturnAccommodationDeposit { get; set; } | |
| 307 | + | |
| 308 | + /// <summary> | |
| 309 | + /// 当月是否发放 | |
| 310 | + /// </summary> | |
| 311 | + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")] | |
| 312 | + public string MonthlyPaymentStatus { get; set; } | |
| 313 | + | |
| 314 | + /// <summary> | |
| 315 | + /// 支付金额 | |
| 316 | + /// </summary> | |
| 317 | + [SugarColumn(ColumnName = "F_PaidAmount")] | |
| 318 | + public decimal PaidAmount { get; set; } | |
| 319 | + | |
| 320 | + /// <summary> | |
| 321 | + /// 待支付金额 | |
| 322 | + /// </summary> | |
| 323 | + [SugarColumn(ColumnName = "F_PendingAmount")] | |
| 324 | + public decimal PendingAmount { get; set; } | |
| 325 | + | |
| 326 | + /// <summary> | |
| 327 | + /// 补发上月 | |
| 328 | + /// </summary> | |
| 329 | + [SugarColumn(ColumnName = "F_LastMonthSupplement")] | |
| 330 | + public decimal LastMonthSupplement { get; set; } | |
| 331 | + | |
| 332 | + /// <summary> | |
| 333 | + /// 当月支付总额 | |
| 334 | + /// </summary> | |
| 335 | + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")] | |
| 336 | + public decimal MonthlyTotalPayment { get; set; } | |
| 337 | + | |
| 338 | + /// <summary> | |
| 339 | + /// 是否锁定(0未锁定,1已锁定) | |
| 340 | + /// </summary> | |
| 341 | + [SugarColumn(ColumnName = "F_IsLocked")] | |
| 342 | + public int IsLocked { get; set; } | |
| 343 | + | |
| 344 | + /// <summary> | |
| 345 | + /// 创建时间 | |
| 346 | + /// </summary> | |
| 347 | + [SugarColumn(ColumnName = "F_CreateTime")] | |
| 348 | + public DateTime CreateTime { get; set; } | |
| 349 | + | |
| 350 | + /// <summary> | |
| 351 | + /// 更新时间 | |
| 352 | + /// </summary> | |
| 353 | + [SugarColumn(ColumnName = "F_UpdateTime")] | |
| 354 | + public DateTime UpdateTime { get; set; } | |
| 355 | + | |
| 356 | + /// <summary> | |
| 357 | + /// 创建人 | |
| 358 | + /// </summary> | |
| 359 | + [SugarColumn(ColumnName = "F_CreateUser")] | |
| 360 | + public string CreateUser { get; set; } | |
| 361 | + | |
| 362 | + /// <summary> | |
| 363 | + /// 更新人 | |
| 364 | + /// </summary> | |
| 365 | + [SugarColumn(ColumnName = "F_UpdateUser")] | |
| 366 | + public string UpdateUser { get; set; } | |
| 367 | + } | |
| 368 | +} | |
| 369 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_director_salary_statistics/LqDirectorSalaryStatisticsEntity.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using NCC.Common.Const; | |
| 3 | +using SqlSugar; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.lq_director_salary_statistics | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 主任工资统计表 | |
| 9 | + /// </summary> | |
| 10 | + [SugarTable("lq_director_salary_statistics")] | |
| 11 | + [Tenant(ClaimConst.TENANT_ID)] | |
| 12 | + public class LqDirectorSalaryStatisticsEntity | |
| 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 | + /// 门店名称 | |
| 28 | + /// </summary> | |
| 29 | + [SugarColumn(ColumnName = "F_StoreName")] | |
| 30 | + public string StoreName { get; set; } | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 核算岗位 | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_Position")] | |
| 36 | + public string Position { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 员工姓名 | |
| 40 | + /// </summary> | |
| 41 | + [SugarColumn(ColumnName = "F_EmployeeName")] | |
| 42 | + public string EmployeeName { get; set; } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 员工ID | |
| 46 | + /// </summary> | |
| 47 | + [SugarColumn(ColumnName = "F_EmployeeId")] | |
| 48 | + public string EmployeeId { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 统计月份(YYYYMM) | |
| 52 | + /// </summary> | |
| 53 | + [SugarColumn(ColumnName = "F_StatisticsMonth")] | |
| 54 | + public string StatisticsMonth { get; set; } | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 门店类型 | |
| 58 | + /// </summary> | |
| 59 | + [SugarColumn(ColumnName = "F_StoreType")] | |
| 60 | + public int? StoreType { get; set; } | |
| 61 | + | |
| 62 | + /// <summary> | |
| 63 | + /// 门店类别 | |
| 64 | + /// </summary> | |
| 65 | + [SugarColumn(ColumnName = "F_StoreCategory")] | |
| 66 | + public int? StoreCategory { get; set; } | |
| 67 | + | |
| 68 | + /// <summary> | |
| 69 | + /// 是否新店 | |
| 70 | + /// </summary> | |
| 71 | + [SugarColumn(ColumnName = "F_IsNewStore")] | |
| 72 | + public string IsNewStore { get; set; } | |
| 73 | + | |
| 74 | + /// <summary> | |
| 75 | + /// 新店保护阶段 | |
| 76 | + /// </summary> | |
| 77 | + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")] | |
| 78 | + public int NewStoreProtectionStage { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 门店总业绩 | |
| 82 | + /// </summary> | |
| 83 | + [SugarColumn(ColumnName = "F_StoreTotalPerformance")] | |
| 84 | + public decimal StoreTotalPerformance { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 门店开单业绩 | |
| 88 | + /// </summary> | |
| 89 | + [SugarColumn(ColumnName = "F_StoreBillingPerformance")] | |
| 90 | + public decimal StoreBillingPerformance { get; set; } | |
| 91 | + | |
| 92 | + /// <summary> | |
| 93 | + /// 门店退卡业绩 | |
| 94 | + /// </summary> | |
| 95 | + [SugarColumn(ColumnName = "F_StoreRefundPerformance")] | |
| 96 | + public decimal StoreRefundPerformance { get; set; } | |
| 97 | + | |
| 98 | + /// <summary> | |
| 99 | + /// 门店生命线 | |
| 100 | + /// </summary> | |
| 101 | + [SugarColumn(ColumnName = "F_StoreLifeline")] | |
| 102 | + public decimal StoreLifeline { get; set; } | |
| 103 | + | |
| 104 | + /// <summary> | |
| 105 | + /// 业绩完成率 | |
| 106 | + /// </summary> | |
| 107 | + [SugarColumn(ColumnName = "F_PerformanceCompletionRate")] | |
| 108 | + public decimal PerformanceCompletionRate { get; set; } | |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// 业绩是否达标 | |
| 112 | + /// </summary> | |
| 113 | + [SugarColumn(ColumnName = "F_PerformanceReached")] | |
| 114 | + public string PerformanceReached { get; set; } | |
| 115 | + | |
| 116 | + /// <summary> | |
| 117 | + /// 人头是否达标 | |
| 118 | + /// </summary> | |
| 119 | + [SugarColumn(ColumnName = "F_HeadCountReached")] | |
| 120 | + public string HeadCountReached { get; set; } | |
| 121 | + | |
| 122 | + /// <summary> | |
| 123 | + /// 消耗是否达标 | |
| 124 | + /// </summary> | |
| 125 | + [SugarColumn(ColumnName = "F_ConsumeReached")] | |
| 126 | + public string ConsumeReached { get; set; } | |
| 127 | + | |
| 128 | + /// <summary> | |
| 129 | + /// 考核扣款金额 | |
| 130 | + /// </summary> | |
| 131 | + [SugarColumn(ColumnName = "F_AssessmentDeduction")] | |
| 132 | + public decimal AssessmentDeduction { get; set; } | |
| 133 | + | |
| 134 | + /// <summary> | |
| 135 | + /// 未达标指标数量 | |
| 136 | + /// </summary> | |
| 137 | + [SugarColumn(ColumnName = "F_UnreachedIndicatorCount")] | |
| 138 | + public int UnreachedIndicatorCount { get; set; } | |
| 139 | + | |
| 140 | + /// <summary> | |
| 141 | + /// 进店消耗人数 | |
| 142 | + /// </summary> | |
| 143 | + [SugarColumn(ColumnName = "F_HeadCount")] | |
| 144 | + public int HeadCount { get; set; } | |
| 145 | + | |
| 146 | + /// <summary> | |
| 147 | + /// 目标人头数 | |
| 148 | + /// </summary> | |
| 149 | + [SugarColumn(ColumnName = "F_TargetHeadCount")] | |
| 150 | + public decimal TargetHeadCount { get; set; } | |
| 151 | + | |
| 152 | + /// <summary> | |
| 153 | + /// 门店消耗金额 | |
| 154 | + /// </summary> | |
| 155 | + [SugarColumn(ColumnName = "F_StoreConsume")] | |
| 156 | + public decimal StoreConsume { get; set; } | |
| 157 | + | |
| 158 | + /// <summary> | |
| 159 | + /// 目标消耗金额 | |
| 160 | + /// </summary> | |
| 161 | + [SugarColumn(ColumnName = "F_TargetConsume")] | |
| 162 | + public decimal TargetConsume { get; set; } | |
| 163 | + | |
| 164 | + /// <summary> | |
| 165 | + /// ≤生命线部分提成比例 | |
| 166 | + /// </summary> | |
| 167 | + [SugarColumn(ColumnName = "F_CommissionRateBelowLifeline")] | |
| 168 | + public decimal CommissionRateBelowLifeline { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// >生命线部分提成比例 | |
| 172 | + /// </summary> | |
| 173 | + [SugarColumn(ColumnName = "F_CommissionRateAboveLifeline")] | |
| 174 | + public decimal CommissionRateAboveLifeline { get; set; } | |
| 175 | + | |
| 176 | + /// <summary> | |
| 177 | + /// ≤生命线部分提成金额 | |
| 178 | + /// </summary> | |
| 179 | + [SugarColumn(ColumnName = "F_CommissionAmountBelowLifeline")] | |
| 180 | + public decimal CommissionAmountBelowLifeline { get; set; } | |
| 181 | + | |
| 182 | + /// <summary> | |
| 183 | + /// >生命线部分提成金额 | |
| 184 | + /// </summary> | |
| 185 | + [SugarColumn(ColumnName = "F_CommissionAmountAboveLifeline")] | |
| 186 | + public decimal CommissionAmountAboveLifeline { get; set; } | |
| 187 | + | |
| 188 | + /// <summary> | |
| 189 | + /// 提成总金额 | |
| 190 | + /// </summary> | |
| 191 | + [SugarColumn(ColumnName = "F_TotalCommissionAmount")] | |
| 192 | + public decimal TotalCommissionAmount { get; set; } | |
| 193 | + | |
| 194 | + /// <summary> | |
| 195 | + /// 底薪金额 | |
| 196 | + /// </summary> | |
| 197 | + [SugarColumn(ColumnName = "F_BaseSalary")] | |
| 198 | + public decimal BaseSalary { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 实际底薪 | |
| 202 | + /// </summary> | |
| 203 | + [SugarColumn(ColumnName = "F_ActualBaseSalary")] | |
| 204 | + public decimal ActualBaseSalary { get; set; } | |
| 205 | + | |
| 206 | + /// <summary> | |
| 207 | + /// 在店天数 | |
| 208 | + /// </summary> | |
| 209 | + [SugarColumn(ColumnName = "F_WorkingDays")] | |
| 210 | + public int WorkingDays { get; set; } | |
| 211 | + | |
| 212 | + /// <summary> | |
| 213 | + /// 请假天数 | |
| 214 | + /// </summary> | |
| 215 | + [SugarColumn(ColumnName = "F_LeaveDays")] | |
| 216 | + public int LeaveDays { get; set; } | |
| 217 | + | |
| 218 | + /// <summary> | |
| 219 | + /// 应发工资 | |
| 220 | + /// </summary> | |
| 221 | + [SugarColumn(ColumnName = "F_GrossSalary")] | |
| 222 | + public decimal GrossSalary { get; set; } | |
| 223 | + | |
| 224 | + /// <summary> | |
| 225 | + /// 实发工资 | |
| 226 | + /// </summary> | |
| 227 | + [SugarColumn(ColumnName = "F_ActualSalary")] | |
| 228 | + public decimal ActualSalary { get; set; } | |
| 229 | + | |
| 230 | + /// <summary> | |
| 231 | + /// 缺卡扣款 | |
| 232 | + /// </summary> | |
| 233 | + [SugarColumn(ColumnName = "F_MissingCard")] | |
| 234 | + public decimal MissingCard { get; set; } | |
| 235 | + | |
| 236 | + /// <summary> | |
| 237 | + /// 迟到扣款 | |
| 238 | + /// </summary> | |
| 239 | + [SugarColumn(ColumnName = "F_LateArrival")] | |
| 240 | + public decimal LateArrival { get; set; } | |
| 241 | + | |
| 242 | + /// <summary> | |
| 243 | + /// 请假扣款 | |
| 244 | + /// </summary> | |
| 245 | + [SugarColumn(ColumnName = "F_LeaveDeduction")] | |
| 246 | + public decimal LeaveDeduction { get; set; } | |
| 247 | + | |
| 248 | + /// <summary> | |
| 249 | + /// 扣社保 | |
| 250 | + /// </summary> | |
| 251 | + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")] | |
| 252 | + public decimal SocialInsuranceDeduction { get; set; } | |
| 253 | + | |
| 254 | + /// <summary> | |
| 255 | + /// 扣除奖励 | |
| 256 | + /// </summary> | |
| 257 | + [SugarColumn(ColumnName = "F_RewardDeduction")] | |
| 258 | + public decimal RewardDeduction { get; set; } | |
| 259 | + | |
| 260 | + /// <summary> | |
| 261 | + /// 扣住宿费 | |
| 262 | + /// </summary> | |
| 263 | + [SugarColumn(ColumnName = "F_AccommodationDeduction")] | |
| 264 | + public decimal AccommodationDeduction { get; set; } | |
| 265 | + | |
| 266 | + /// <summary> | |
| 267 | + /// 扣学习期费用 | |
| 268 | + /// </summary> | |
| 269 | + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")] | |
| 270 | + public decimal StudyPeriodDeduction { get; set; } | |
| 271 | + | |
| 272 | + /// <summary> | |
| 273 | + /// 扣工作服费用 | |
| 274 | + /// </summary> | |
| 275 | + [SugarColumn(ColumnName = "F_WorkClothesDeduction")] | |
| 276 | + public decimal WorkClothesDeduction { get; set; } | |
| 277 | + | |
| 278 | + /// <summary> | |
| 279 | + /// 扣款合计 | |
| 280 | + /// </summary> | |
| 281 | + [SugarColumn(ColumnName = "F_TotalDeduction")] | |
| 282 | + public decimal TotalDeduction { get; set; } | |
| 283 | + | |
| 284 | + /// <summary> | |
| 285 | + /// 当月培训补贴 | |
| 286 | + /// </summary> | |
| 287 | + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")] | |
| 288 | + public decimal MonthlyTrainingSubsidy { get; set; } | |
| 289 | + | |
| 290 | + /// <summary> | |
| 291 | + /// 当月交通补贴 | |
| 292 | + /// </summary> | |
| 293 | + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")] | |
| 294 | + public decimal MonthlyTransportSubsidy { get; set; } | |
| 295 | + | |
| 296 | + /// <summary> | |
| 297 | + /// 上月培训补贴 | |
| 298 | + /// </summary> | |
| 299 | + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")] | |
| 300 | + public decimal LastMonthTrainingSubsidy { get; set; } | |
| 301 | + | |
| 302 | + /// <summary> | |
| 303 | + /// 上月交通补贴 | |
| 304 | + /// </summary> | |
| 305 | + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")] | |
| 306 | + public decimal LastMonthTransportSubsidy { get; set; } | |
| 307 | + | |
| 308 | + /// <summary> | |
| 309 | + /// 补贴合计 | |
| 310 | + /// </summary> | |
| 311 | + [SugarColumn(ColumnName = "F_TotalSubsidy")] | |
| 312 | + public decimal TotalSubsidy { get; set; } | |
| 313 | + | |
| 314 | + /// <summary> | |
| 315 | + /// 发奖金 | |
| 316 | + /// </summary> | |
| 317 | + [SugarColumn(ColumnName = "F_Bonus")] | |
| 318 | + public decimal Bonus { get; set; } | |
| 319 | + | |
| 320 | + /// <summary> | |
| 321 | + /// 退手机押金 | |
| 322 | + /// </summary> | |
| 323 | + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")] | |
| 324 | + public decimal ReturnPhoneDeposit { get; set; } | |
| 325 | + | |
| 326 | + /// <summary> | |
| 327 | + /// 退住宿押金 | |
| 328 | + /// </summary> | |
| 329 | + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")] | |
| 330 | + public decimal ReturnAccommodationDeposit { get; set; } | |
| 331 | + | |
| 332 | + /// <summary> | |
| 333 | + /// 当月是否发放 | |
| 334 | + /// </summary> | |
| 335 | + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")] | |
| 336 | + public string MonthlyPaymentStatus { get; set; } | |
| 337 | + | |
| 338 | + /// <summary> | |
| 339 | + /// 支付金额 | |
| 340 | + /// </summary> | |
| 341 | + [SugarColumn(ColumnName = "F_PaidAmount")] | |
| 342 | + public decimal PaidAmount { get; set; } | |
| 343 | + | |
| 344 | + /// <summary> | |
| 345 | + /// 待支付金额 | |
| 346 | + /// </summary> | |
| 347 | + [SugarColumn(ColumnName = "F_PendingAmount")] | |
| 348 | + public decimal PendingAmount { get; set; } | |
| 349 | + | |
| 350 | + /// <summary> | |
| 351 | + /// 补发上月 | |
| 352 | + /// </summary> | |
| 353 | + [SugarColumn(ColumnName = "F_LastMonthSupplement")] | |
| 354 | + public decimal LastMonthSupplement { get; set; } | |
| 355 | + | |
| 356 | + /// <summary> | |
| 357 | + /// 当月支付总额 | |
| 358 | + /// </summary> | |
| 359 | + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")] | |
| 360 | + public decimal MonthlyTotalPayment { get; set; } | |
| 361 | + | |
| 362 | + /// <summary> | |
| 363 | + /// 是否锁定(0未锁定,1已锁定) | |
| 364 | + /// </summary> | |
| 365 | + [SugarColumn(ColumnName = "F_IsLocked")] | |
| 366 | + public int IsLocked { get; set; } | |
| 367 | + | |
| 368 | + /// <summary> | |
| 369 | + /// 创建时间 | |
| 370 | + /// </summary> | |
| 371 | + [SugarColumn(ColumnName = "F_CreateTime")] | |
| 372 | + public DateTime CreateTime { get; set; } | |
| 373 | + | |
| 374 | + /// <summary> | |
| 375 | + /// 更新时间 | |
| 376 | + /// </summary> | |
| 377 | + [SugarColumn(ColumnName = "F_UpdateTime")] | |
| 378 | + public DateTime UpdateTime { get; set; } | |
| 379 | + | |
| 380 | + /// <summary> | |
| 381 | + /// 创建人 | |
| 382 | + /// </summary> | |
| 383 | + [SugarColumn(ColumnName = "F_CreateUser")] | |
| 384 | + public string CreateUser { get; set; } | |
| 385 | + | |
| 386 | + /// <summary> | |
| 387 | + /// 更新人 | |
| 388 | + /// </summary> | |
| 389 | + [SugarColumn(ColumnName = "F_UpdateUser")] | |
| 390 | + public string UpdateUser { get; set; } | |
| 391 | + } | |
| 392 | +} | |
| 393 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node/LqReimbursementApplicationNodeEntity.cs
0 → 100644
| 1 | +using NCC.Common.Const; | |
| 2 | +using SqlSugar; | |
| 3 | +using System; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.lq_reimbursement_application_node | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 报销申请节点表(每个报销申请的节点配置) | |
| 9 | + /// </summary> | |
| 10 | + [SugarTable("lq_reimbursement_application_node")] | |
| 11 | + [Tenant(ClaimConst.TENANT_ID)] | |
| 12 | + public class LqReimbursementApplicationNodeEntity | |
| 13 | + { | |
| 14 | + /// <summary> | |
| 15 | + /// 节点编号 | |
| 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_ApplicationId")] | |
| 24 | + public string ApplicationId { get; set; } | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 节点顺序(1,2,3,4,5) | |
| 28 | + /// </summary> | |
| 29 | + [SugarColumn(ColumnName = "F_NodeOrder")] | |
| 30 | + public int NodeOrder { get; set; } | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 节点名称 | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_NodeName")] | |
| 36 | + public string NodeName { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 审批类型(会签/或签) | |
| 40 | + /// </summary> | |
| 41 | + [SugarColumn(ColumnName = "F_ApprovalType")] | |
| 42 | + public string ApprovalType { get; set; } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 是否必审(1-必审,0-可选) | |
| 46 | + /// </summary> | |
| 47 | + [SugarColumn(ColumnName = "F_IsRequired")] | |
| 48 | + public int IsRequired { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 创建时间 | |
| 52 | + /// </summary> | |
| 53 | + [SugarColumn(ColumnName = "F_CreateTime")] | |
| 54 | + public DateTime? CreateTime { get; set; } | |
| 55 | + } | |
| 56 | +} | |
| 57 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_application_node_user/LqReimbursementApplicationNodeUserEntity.cs
0 → 100644
| 1 | +using NCC.Common.Const; | |
| 2 | +using SqlSugar; | |
| 3 | +using System; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.lq_reimbursement_application_node_user | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 报销申请节点审批人表(每个节点的审批人,在创建报销申请时指定) | |
| 9 | + /// </summary> | |
| 10 | + [SugarTable("lq_reimbursement_application_node_user")] | |
| 11 | + [Tenant(ClaimConst.TENANT_ID)] | |
| 12 | + public class LqReimbursementApplicationNodeUserEntity | |
| 13 | + { | |
| 14 | + /// <summary> | |
| 15 | + /// 记录编号 | |
| 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_ApplicationId")] | |
| 24 | + public string ApplicationId { get; set; } | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 节点编号 | |
| 28 | + /// </summary> | |
| 29 | + [SugarColumn(ColumnName = "F_NodeId")] | |
| 30 | + public string NodeId { get; set; } | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 节点顺序(1,2,3,4,5) | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_NodeOrder")] | |
| 36 | + public int NodeOrder { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 审批人ID | |
| 40 | + /// </summary> | |
| 41 | + [SugarColumn(ColumnName = "F_UserId")] | |
| 42 | + public string UserId { get; set; } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 审批人姓名 | |
| 46 | + /// </summary> | |
| 47 | + [SugarColumn(ColumnName = "F_UserName")] | |
| 48 | + public string UserName { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 排序 | |
| 52 | + /// </summary> | |
| 53 | + [SugarColumn(ColumnName = "F_SortOrder")] | |
| 54 | + public int SortOrder { get; set; } | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 创建时间 | |
| 58 | + /// </summary> | |
| 59 | + [SugarColumn(ColumnName = "F_CreateTime")] | |
| 60 | + public DateTime? CreateTime { get; set; } | |
| 61 | + } | |
| 62 | +} | |
| 63 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_reimbursement_approval_record/LqReimbursementApprovalRecordEntity.cs
0 → 100644
| 1 | +using NCC.Common.Const; | |
| 2 | +using SqlSugar; | |
| 3 | +using System; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.lq_reimbursement_approval_record | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 审批记录表 | |
| 9 | + /// </summary> | |
| 10 | + [SugarTable("lq_reimbursement_approval_record")] | |
| 11 | + [Tenant(ClaimConst.TENANT_ID)] | |
| 12 | + public class LqReimbursementApprovalRecordEntity | |
| 13 | + { | |
| 14 | + /// <summary> | |
| 15 | + /// 记录编号 | |
| 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_ApplicationId")] | |
| 24 | + public string ApplicationId { get; set; } | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 节点编号 | |
| 28 | + /// </summary> | |
| 29 | + [SugarColumn(ColumnName = "F_NodeId")] | |
| 30 | + public string NodeId { get; set; } | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 节点顺序(1,2,3,4,5) | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_NodeOrder")] | |
| 36 | + public int NodeOrder { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 审批人ID | |
| 40 | + /// </summary> | |
| 41 | + [SugarColumn(ColumnName = "F_ApproverId")] | |
| 42 | + public string ApproverId { get; set; } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 审批人姓名 | |
| 46 | + /// </summary> | |
| 47 | + [SugarColumn(ColumnName = "F_ApproverName")] | |
| 48 | + public string ApproverName { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 审批结果(通过/不通过/退回) | |
| 52 | + /// </summary> | |
| 53 | + [SugarColumn(ColumnName = "F_ApprovalResult")] | |
| 54 | + public string ApprovalResult { get; set; } | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 审批意见 | |
| 58 | + /// </summary> | |
| 59 | + [SugarColumn(ColumnName = "F_ApprovalOpinion")] | |
| 60 | + public string ApprovalOpinion { get; set; } | |
| 61 | + | |
| 62 | + /// <summary> | |
| 63 | + /// 审批时间 | |
| 64 | + /// </summary> | |
| 65 | + [SugarColumn(ColumnName = "F_ApprovalTime")] | |
| 66 | + public DateTime? ApprovalTime { get; set; } | |
| 67 | + | |
| 68 | + /// <summary> | |
| 69 | + /// 是否当前节点(1-是,0-否) | |
| 70 | + /// </summary> | |
| 71 | + [SugarColumn(ColumnName = "F_IsCurrentNode")] | |
| 72 | + public int IsCurrentNode { get; set; } | |
| 73 | + } | |
| 74 | +} | |
| 75 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_extra_calculation/LqSalaryExtraCalculationEntity.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using NCC.Common.Const; | |
| 3 | +using SqlSugar; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.lq_salary_extra_calculation | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 健康师工资额外计算表 | |
| 9 | + /// </summary> | |
| 10 | + [SugarTable("lq_salary_extra_calculation")] | |
| 11 | + [Tenant(ClaimConst.TENANT_ID)] | |
| 12 | + public class LqSalaryExtraCalculationEntity | |
| 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_EmployeeId")] | |
| 24 | + public string EmployeeId { get; set; } | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 年份 | |
| 28 | + /// </summary> | |
| 29 | + [SugarColumn(ColumnName = "F_Year")] | |
| 30 | + public int Year { get; set; } | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 月份 | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_Month")] | |
| 36 | + public int Month { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 基础奖励业绩 | |
| 40 | + /// </summary> | |
| 41 | + [SugarColumn(ColumnName = "F_BaseRewardPerformance")] | |
| 42 | + public decimal BaseRewardPerformance { get; set; } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 合作奖励业绩 | |
| 46 | + /// </summary> | |
| 47 | + [SugarColumn(ColumnName = "F_CooperationRewardPerformance")] | |
| 48 | + public decimal CooperationRewardPerformance { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 新客业绩 | |
| 52 | + /// </summary> | |
| 53 | + [SugarColumn(ColumnName = "F_NewCustomerPerformance")] | |
| 54 | + public decimal NewCustomerPerformance { get; set; } | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 新客成交率 | |
| 58 | + /// </summary> | |
| 59 | + [SugarColumn(ColumnName = "F_NewCustomerConversionRate")] | |
| 60 | + public decimal NewCustomerConversionRate { get; set; } | |
| 61 | + | |
| 62 | + /// <summary> | |
| 63 | + /// 升单业绩 | |
| 64 | + /// </summary> | |
| 65 | + [SugarColumn(ColumnName = "F_UpgradePerformance")] | |
| 66 | + public decimal UpgradePerformance { get; set; } | |
| 67 | + | |
| 68 | + /// <summary> | |
| 69 | + /// 升单成交率 | |
| 70 | + /// </summary> | |
| 71 | + [SugarColumn(ColumnName = "F_UpgradeConversionRate")] | |
| 72 | + public decimal UpgradeConversionRate { get; set; } | |
| 73 | + | |
| 74 | + /// <summary> | |
| 75 | + /// 升单人头数 | |
| 76 | + /// </summary> | |
| 77 | + [SugarColumn(ColumnName = "F_UpgradeCustomerCount")] | |
| 78 | + public decimal UpgradeCustomerCount { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 其他业绩加 | |
| 82 | + /// </summary> | |
| 83 | + [SugarColumn(ColumnName = "F_OtherPerformanceAdd")] | |
| 84 | + public decimal OtherPerformanceAdd { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 其他业绩减 | |
| 88 | + /// </summary> | |
| 89 | + [SugarColumn(ColumnName = "F_OtherPerformanceSubtract")] | |
| 90 | + public decimal OtherPerformanceSubtract { get; set; } | |
| 91 | + } | |
| 92 | +} | |
| 93 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_salary_statistics/LqSalaryStatisticsEntity.cs
| ... | ... | @@ -78,6 +78,30 @@ namespace NCC.Extend.Entitys.lq_salary_statistics |
| 78 | 78 | public decimal CooperationPerformance { get; set; } |
| 79 | 79 | |
| 80 | 80 | /// <summary> |
| 81 | + /// 基础奖励业绩 | |
| 82 | + /// </summary> | |
| 83 | + [SugarColumn(ColumnName = "F_BaseRewardPerformance")] | |
| 84 | + public decimal BaseRewardPerformance { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 合作奖励业绩 | |
| 88 | + /// </summary> | |
| 89 | + [SugarColumn(ColumnName = "F_CooperationRewardPerformance")] | |
| 90 | + public decimal CooperationRewardPerformance { get; set; } | |
| 91 | + | |
| 92 | + /// <summary> | |
| 93 | + /// 实际基础业绩 | |
| 94 | + /// </summary> | |
| 95 | + [SugarColumn(ColumnName = "F_ActualBasePerformance")] | |
| 96 | + public decimal ActualBasePerformance { get; set; } | |
| 97 | + | |
| 98 | + /// <summary> | |
| 99 | + /// 实际合作业绩 | |
| 100 | + /// </summary> | |
| 101 | + [SugarColumn(ColumnName = "F_ActualCooperationPerformance")] | |
| 102 | + public decimal ActualCooperationPerformance { get; set; } | |
| 103 | + | |
| 104 | + /// <summary> | |
| 81 | 105 | /// 奖励业绩 |
| 82 | 106 | /// </summary> |
| 83 | 107 | [SugarColumn(ColumnName = "F_RewardPerformance")] |
| ... | ... | @@ -120,6 +144,12 @@ namespace NCC.Extend.Entitys.lq_salary_statistics |
| 120 | 144 | public decimal NewCustomerPoint { get; set; } |
| 121 | 145 | |
| 122 | 146 | /// <summary> |
| 147 | + /// 升单人头数 | |
| 148 | + /// </summary> | |
| 149 | + [SugarColumn(ColumnName = "F_UpgradeCustomerCount")] | |
| 150 | + public decimal UpgradeCustomerCount { get; set; } | |
| 151 | + | |
| 152 | + /// <summary> | |
| 123 | 153 | /// 升单业绩 |
| 124 | 154 | /// </summary> |
| 125 | 155 | [SugarColumn(ColumnName = "F_UpgradePerformance")] |
| ... | ... | @@ -132,6 +162,30 @@ namespace NCC.Extend.Entitys.lq_salary_statistics |
| 132 | 162 | public decimal UpgradePoint { get; set; } |
| 133 | 163 | |
| 134 | 164 | /// <summary> |
| 165 | + /// 新客业绩提成金额 | |
| 166 | + /// </summary> | |
| 167 | + [SugarColumn(ColumnName = "F_NewCustomerPerformanceCommission")] | |
| 168 | + public decimal NewCustomerPerformanceCommission { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 升单业绩提成金额 | |
| 172 | + /// </summary> | |
| 173 | + [SugarColumn(ColumnName = "F_UpgradePerformanceCommission")] | |
| 174 | + public decimal UpgradePerformanceCommission { get; set; } | |
| 175 | + | |
| 176 | + /// <summary> | |
| 177 | + /// 其他业绩加 | |
| 178 | + /// </summary> | |
| 179 | + [SugarColumn(ColumnName = "F_OtherPerformanceAdd")] | |
| 180 | + public decimal OtherPerformanceAdd { get; set; } | |
| 181 | + | |
| 182 | + /// <summary> | |
| 183 | + /// 其他业绩减 | |
| 184 | + /// </summary> | |
| 185 | + [SugarColumn(ColumnName = "F_OtherPerformanceSubtract")] | |
| 186 | + public decimal OtherPerformanceSubtract { get; set; } | |
| 187 | + | |
| 188 | + /// <summary> | |
| 135 | 189 | /// 消耗 |
| 136 | 190 | /// </summary> |
| 137 | 191 | [SugarColumn(ColumnName = "F_Consumption")] |
| ... | ... | @@ -442,5 +496,47 @@ namespace NCC.Extend.Entitys.lq_salary_statistics |
| 442 | 496 | /// </summary> |
| 443 | 497 | [SugarColumn(ColumnName = "F_UpdateUser")] |
| 444 | 498 | public string UpdateUser { get; set; } |
| 499 | + | |
| 500 | + /// <summary> | |
| 501 | + /// 是否新店 | |
| 502 | + /// </summary> | |
| 503 | + [SugarColumn(ColumnName = "F_IsNewStore")] | |
| 504 | + public string IsNewStore { get; set; } | |
| 505 | + | |
| 506 | + /// <summary> | |
| 507 | + /// 新店保护阶段 | |
| 508 | + /// </summary> | |
| 509 | + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")] | |
| 510 | + public int NewStoreProtectionStage { get; set; } | |
| 511 | + | |
| 512 | + /// <summary> | |
| 513 | + /// 门店类型 | |
| 514 | + /// </summary> | |
| 515 | + [SugarColumn(ColumnName = "F_StoreType")] | |
| 516 | + public int? StoreType { get; set; } | |
| 517 | + | |
| 518 | + /// <summary> | |
| 519 | + /// 门店类别 | |
| 520 | + /// </summary> | |
| 521 | + [SugarColumn(ColumnName = "F_StoreCategory")] | |
| 522 | + public int? StoreCategory { get; set; } | |
| 523 | + | |
| 524 | + /// <summary> | |
| 525 | + /// 日均消耗 | |
| 526 | + /// </summary> | |
| 527 | + [SugarColumn(ColumnName = "F_DailyAverageConsumption")] | |
| 528 | + public decimal DailyAverageConsumption { get; set; } | |
| 529 | + | |
| 530 | + /// <summary> | |
| 531 | + /// 日均项目数 | |
| 532 | + /// </summary> | |
| 533 | + [SugarColumn(ColumnName = "F_DailyAverageProjectCount")] | |
| 534 | + public decimal DailyAverageProjectCount { get; set; } | |
| 535 | + | |
| 536 | + /// <summary> | |
| 537 | + /// 战队总消耗 | |
| 538 | + /// </summary> | |
| 539 | + [SugarColumn(ColumnName = "F_TeamTotalConsumption")] | |
| 540 | + public decimal TeamTotalConsumption { get; set; } | |
| 445 | 541 | } |
| 446 | 542 | } |
| 447 | 543 | \ No newline at end of file | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.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.LqAssistantSalary; | |
| 8 | +using NCC.Extend.Entitys.lq_assistant_salary_statistics; | |
| 9 | +using NCC.Extend.Entitys.lq_attendance_summary; | |
| 10 | +using NCC.Extend.Entitys.lq_hytk_hytk; | |
| 11 | +using NCC.Extend.Entitys.lq_kd_kdjlb; | |
| 12 | +using NCC.Extend.Entitys.lq_md_target; | |
| 13 | +using NCC.Extend.Entitys.lq_md_xdbhsj; | |
| 14 | +using NCC.Extend.Entitys.lq_mdxx; | |
| 15 | +using NCC.Extend.Entitys.lq_xh_hyhk; | |
| 16 | +using NCC.Extend.Entitys.lq_xh_jksyj; | |
| 17 | +using NCC.System.Entitys.Permission; | |
| 18 | +using SqlSugar; | |
| 19 | +using System; | |
| 20 | +using System.Collections.Generic; | |
| 21 | +using System.Linq; | |
| 22 | +using System.Threading.Tasks; | |
| 23 | +using Yitter.IdGenerator; | |
| 24 | + | |
| 25 | +namespace NCC.Extend | |
| 26 | +{ | |
| 27 | + /// <summary> | |
| 28 | + /// 店助薪酬服务 | |
| 29 | + /// </summary> | |
| 30 | + [ApiDescriptionSettings(Tag = "店助薪酬服务", Name = "LqAssistantSalary", Order = 301)] | |
| 31 | + [Route("api/Extend/[controller]")] | |
| 32 | + public class LqAssistantSalaryService : IDynamicApiController, ITransient | |
| 33 | + { | |
| 34 | + private readonly ISqlSugarClient _db; | |
| 35 | + | |
| 36 | + /// <summary> | |
| 37 | + /// 初始化一个<see cref="LqAssistantSalaryService"/>类型的新实例 | |
| 38 | + /// </summary> | |
| 39 | + public LqAssistantSalaryService(ISqlSugarClient db) | |
| 40 | + { | |
| 41 | + _db = db; | |
| 42 | + } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 获取店助工资列表 | |
| 46 | + /// </summary> | |
| 47 | + /// <param name="input">查询参数</param> | |
| 48 | + /// <returns>店助工资分页列表</returns> | |
| 49 | + [HttpGet("assistant")] | |
| 50 | + public async Task<dynamic> GetAssistantSalaryList([FromQuery] AssistantSalaryInput input) | |
| 51 | + { | |
| 52 | + var monthStr = $"{input.Year}{input.Month:D2}"; | |
| 53 | + | |
| 54 | + // 1. 检查当月是否已生成工资数据 | |
| 55 | + var exists = await _db.Queryable<LqAssistantSalaryStatisticsEntity>() | |
| 56 | + .AnyAsync(x => x.StatisticsMonth == monthStr); | |
| 57 | + | |
| 58 | + // 2. 如果没有数据,则进行计算 | |
| 59 | + if (!exists) | |
| 60 | + { | |
| 61 | + await CalculateAssistantSalary(input.Year, input.Month); | |
| 62 | + } | |
| 63 | + | |
| 64 | + // 3. 查询数据 | |
| 65 | + var query = _db.Queryable<LqAssistantSalaryStatisticsEntity>() | |
| 66 | + .Where(x => x.StatisticsMonth == monthStr); | |
| 67 | + | |
| 68 | + if (!string.IsNullOrEmpty(input.StoreId)) | |
| 69 | + { | |
| 70 | + query = query.Where(x => x.StoreId == input.StoreId); | |
| 71 | + } | |
| 72 | + | |
| 73 | + if (!string.IsNullOrEmpty(input.Keyword)) | |
| 74 | + { | |
| 75 | + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword)); | |
| 76 | + } | |
| 77 | + | |
| 78 | + var list = await query.Select(x => new AssistantSalaryOutput | |
| 79 | + { | |
| 80 | + Id = x.Id, | |
| 81 | + StoreName = x.StoreName, | |
| 82 | + EmployeeName = x.EmployeeName, | |
| 83 | + Position = x.Position, | |
| 84 | + StoreTotalPerformance = x.StoreTotalPerformance, | |
| 85 | + StoreBillingPerformance = x.StoreBillingPerformance, | |
| 86 | + StoreRefundPerformance = x.StoreRefundPerformance, | |
| 87 | + StoreLifeline = x.StoreLifeline, | |
| 88 | + PerformanceCompletionRate = x.PerformanceCompletionRate, | |
| 89 | + CommissionRate = x.CommissionRate, | |
| 90 | + CommissionAmount = x.CommissionAmount, | |
| 91 | + HeadCount = x.HeadCount, | |
| 92 | + Stage1TargetHeadCount = x.Stage1TargetHeadCount, | |
| 93 | + Stage2TargetHeadCount = x.Stage2TargetHeadCount, | |
| 94 | + ReachedStage1 = x.ReachedStage1, | |
| 95 | + ReachedStage2 = x.ReachedStage2, | |
| 96 | + StageRewardAmount = x.StageRewardAmount, | |
| 97 | + Stage1Reward = x.Stage1Reward, | |
| 98 | + Stage2Reward = x.Stage2Reward, | |
| 99 | + BaseSalary = x.BaseSalary, | |
| 100 | + PhoneManagementFee = x.PhoneManagementFee, | |
| 101 | + WorkingDays = x.WorkingDays, | |
| 102 | + LeaveDays = x.LeaveDays, | |
| 103 | + GrossSalary = x.GrossSalary, | |
| 104 | + ActualSalary = x.ActualSalary, | |
| 105 | + TotalDeduction = x.TotalDeduction, | |
| 106 | + TotalSubsidy = x.TotalSubsidy, | |
| 107 | + Bonus = x.Bonus, | |
| 108 | + ReturnPhoneDeposit = x.ReturnPhoneDeposit, | |
| 109 | + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit, | |
| 110 | + MonthlyPaymentStatus = x.MonthlyPaymentStatus, | |
| 111 | + PaidAmount = x.PaidAmount, | |
| 112 | + PendingAmount = x.PendingAmount, | |
| 113 | + LastMonthSupplement = x.LastMonthSupplement, | |
| 114 | + MonthlyTotalPayment = x.MonthlyTotalPayment, | |
| 115 | + IsLocked = x.IsLocked, | |
| 116 | + UpdateTime = x.UpdateTime, | |
| 117 | + StoreType = x.StoreType, | |
| 118 | + StoreCategory = x.StoreCategory, | |
| 119 | + IsNewStore = x.IsNewStore, | |
| 120 | + NewStoreProtectionStage = x.NewStoreProtectionStage | |
| 121 | + }) | |
| 122 | + .ToPagedListAsync(input.currentPage, input.pageSize); | |
| 123 | + | |
| 124 | + return PageResult<AssistantSalaryOutput>.SqlSugarPageResult(list); | |
| 125 | + } | |
| 126 | + | |
| 127 | + /// <summary> | |
| 128 | + /// 计算店助工资 | |
| 129 | + /// </summary> | |
| 130 | + /// <param name="year">年份</param> | |
| 131 | + /// <param name="month">月份</param> | |
| 132 | + /// <returns></returns> | |
| 133 | + [HttpPost("calculate/assistant")] | |
| 134 | + public async Task CalculateAssistantSalary(int year, int month) | |
| 135 | + { | |
| 136 | + var startDate = new DateTime(year, month, 1); | |
| 137 | + var endDate = startDate.AddMonths(1).AddDays(-1); | |
| 138 | + var monthStr = $"{year}{month:D2}"; | |
| 139 | + var daysInMonth = DateTime.DaysInMonth(year, month); // 当月天数 | |
| 140 | + | |
| 141 | + // 1. 获取基础数据 | |
| 142 | + | |
| 143 | + // 1.1 获取店助员工列表(从BASE_USER表,岗位为"店助"或"店助主任") | |
| 144 | + var assistantUserList = await _db.Queryable<UserEntity>() | |
| 145 | + .Where(x => (x.Gw == "店助" || x.Gw == "店助主任") && x.DeleteMark == null && x.EnabledMark == 1) | |
| 146 | + .Select(x => new { x.Id, x.RealName, x.Mdid, x.Gw }) | |
| 147 | + .ToListAsync(); | |
| 148 | + | |
| 149 | + if (!assistantUserList.Any()) | |
| 150 | + { | |
| 151 | + // 如果没有店助员工,直接返回 | |
| 152 | + return; | |
| 153 | + } | |
| 154 | + | |
| 155 | + // 1.2 门店信息 (lq_mdxx) | |
| 156 | + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync(); | |
| 157 | + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); | |
| 158 | + | |
| 159 | + // 1.3 门店目标信息 (lq_md_target) - 包含门店生命线和阶段目标 | |
| 160 | + var storeTargets = await _db.Queryable<LqMdTargetEntity>() | |
| 161 | + .Where(x => x.Month == monthStr) | |
| 162 | + .ToListAsync(); | |
| 163 | + var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId)) | |
| 164 | + .ToDictionary(x => x.StoreId, x => x); | |
| 165 | + | |
| 166 | + // 1.4 门店新店保护信息 (lq_md_xdbhsj) | |
| 167 | + var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>() | |
| 168 | + .Where(x => x.Sfqy == 1) | |
| 169 | + .ToListAsync(); | |
| 170 | + var newStoreProtectionDict = newStoreProtectionList | |
| 171 | + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate) | |
| 172 | + .GroupBy(x => x.Mdid) | |
| 173 | + .ToDictionary(g => g.Key, g => g.First()); | |
| 174 | + | |
| 175 | + // 1.5 门店总业绩计算 (开单实付 - 退卡金额) | |
| 176 | + // 开单实付 | |
| 177 | + var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>() | |
| 178 | + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1) | |
| 179 | + .Select(x => new { x.Djmd, x.Sfyj }) | |
| 180 | + .ToListAsync(); | |
| 181 | + var storeBillingDict = storeBillingList | |
| 182 | + .Where(x => !string.IsNullOrEmpty(x.Djmd)) | |
| 183 | + .GroupBy(x => x.Djmd) | |
| 184 | + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj)); | |
| 185 | + | |
| 186 | + // 退卡金额(使用F_ActualRefundAmount,如果没有则使用tkje) | |
| 187 | + var storeRefundList = await _db.Queryable<LqHytkHytkEntity>() | |
| 188 | + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) | |
| 189 | + .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje }) | |
| 190 | + .ToListAsync(); | |
| 191 | + var storeRefundDict = storeRefundList | |
| 192 | + .Where(x => !string.IsNullOrEmpty(x.Md)) | |
| 193 | + .GroupBy(x => x.Md) | |
| 194 | + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0)); | |
| 195 | + | |
| 196 | + // 1.6 进店消耗人数统计(有消费金额的,按门店按月去重客户数) | |
| 197 | + // 使用SQL查询优化性能 | |
| 198 | + var headcountSql = $@" | |
| 199 | + SELECT | |
| 200 | + hyhk.md as StoreId, | |
| 201 | + COUNT(DISTINCT hyhk.hy) as HeadCount | |
| 202 | + FROM lq_xh_hyhk hyhk | |
| 203 | + WHERE hyhk.F_IsEffective = 1 | |
| 204 | + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @monthStr | |
| 205 | + AND EXISTS ( | |
| 206 | + SELECT 1 | |
| 207 | + FROM lq_xh_jksyj jksyj | |
| 208 | + WHERE jksyj.glkdbh = hyhk.F_Id | |
| 209 | + AND jksyj.F_IsEffective = 1 | |
| 210 | + AND jksyj.jksyj > 0 | |
| 211 | + ) | |
| 212 | + GROUP BY hyhk.md"; | |
| 213 | + | |
| 214 | + var headcountData = await _db.Ado.SqlQueryAsync<dynamic>(headcountSql, new { monthStr }); | |
| 215 | + var headcountDict = headcountData | |
| 216 | + .Where(x => x.StoreId != null) | |
| 217 | + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount)); | |
| 218 | + | |
| 219 | + // 1.7 考勤数据 (lq_attendance_summary) | |
| 220 | + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>() | |
| 221 | + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) | |
| 222 | + .ToListAsync(); | |
| 223 | + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x); | |
| 224 | + | |
| 225 | + // 2. 计算每个店助的工资 | |
| 226 | + var assistantSalaryList = new List<LqAssistantSalaryStatisticsEntity>(); | |
| 227 | + | |
| 228 | + foreach (var assistantUser in assistantUserList) | |
| 229 | + { | |
| 230 | + var salary = new LqAssistantSalaryStatisticsEntity | |
| 231 | + { | |
| 232 | + Id = YitIdHelper.NextId().ToString(), | |
| 233 | + EmployeeId = assistantUser.Id, | |
| 234 | + EmployeeName = assistantUser.RealName, | |
| 235 | + StatisticsMonth = monthStr, | |
| 236 | + Position = assistantUser.Gw ?? "店助", // 使用Gw字段,如果为空则默认为"店助" | |
| 237 | + CreateTime = DateTime.Now, | |
| 238 | + UpdateTime = DateTime.Now, | |
| 239 | + IsLocked = 0, | |
| 240 | + MonthlyPaymentStatus = "未发放" | |
| 241 | + }; | |
| 242 | + | |
| 243 | + // 2.1 填充门店信息 | |
| 244 | + string storeId = assistantUser.Mdid; | |
| 245 | + if (string.IsNullOrEmpty(storeId)) | |
| 246 | + { | |
| 247 | + // 如果用户没有门店ID,跳过 | |
| 248 | + continue; | |
| 249 | + } | |
| 250 | + | |
| 251 | + salary.StoreId = storeId; | |
| 252 | + | |
| 253 | + if (storeDict.ContainsKey(storeId)) | |
| 254 | + { | |
| 255 | + var store = storeDict[storeId]; | |
| 256 | + salary.StoreName = store.Dm; | |
| 257 | + salary.StoreType = store.StoreType; | |
| 258 | + salary.StoreCategory = store.StoreCategory; | |
| 259 | + | |
| 260 | + // 数据校验:门店分类必须设置 | |
| 261 | + if (!salary.StoreCategory.HasValue) | |
| 262 | + { | |
| 263 | + throw new Exception($"门店【{store.Dm}】的门店分类未设置,无法计算店助工资"); | |
| 264 | + } | |
| 265 | + } | |
| 266 | + | |
| 267 | + // 2.2 填充新店保护信息 | |
| 268 | + if (newStoreProtectionDict.ContainsKey(storeId)) | |
| 269 | + { | |
| 270 | + var protection = newStoreProtectionDict[storeId]; | |
| 271 | + salary.IsNewStore = "是"; | |
| 272 | + salary.NewStoreProtectionStage = protection.Stage; | |
| 273 | + } | |
| 274 | + else | |
| 275 | + { | |
| 276 | + salary.IsNewStore = "否"; | |
| 277 | + salary.NewStoreProtectionStage = 0; | |
| 278 | + } | |
| 279 | + | |
| 280 | + // 2.3 获取门店目标信息(门店生命线和阶段目标) | |
| 281 | + if (!storeTargetDict.ContainsKey(storeId)) | |
| 282 | + { | |
| 283 | + throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算店助工资"); | |
| 284 | + } | |
| 285 | + | |
| 286 | + var storeTarget = storeTargetDict[storeId]; | |
| 287 | + salary.StoreLifeline = storeTarget.StoreLifeline; | |
| 288 | + | |
| 289 | + // 阶段目标设置(如果未设置,则奖励金额为0) | |
| 290 | + salary.Stage1TargetHeadCount = storeTarget.AssistantHeadcountTargetStage1 > 0 | |
| 291 | + ? (int)storeTarget.AssistantHeadcountTargetStage1 | |
| 292 | + : 0; | |
| 293 | + salary.Stage2TargetHeadCount = storeTarget.AssistantHeadcountTargetStage2 > 0 | |
| 294 | + ? (int)storeTarget.AssistantHeadcountTargetStage2 | |
| 295 | + : 0; | |
| 296 | + | |
| 297 | + // 2.4 计算门店业绩 | |
| 298 | + decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; | |
| 299 | + decimal refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0; | |
| 300 | + salary.StoreBillingPerformance = billing; | |
| 301 | + salary.StoreRefundPerformance = refund; | |
| 302 | + salary.StoreTotalPerformance = billing - refund; | |
| 303 | + | |
| 304 | + // 计算业绩完成率 | |
| 305 | + if (salary.StoreLifeline > 0) | |
| 306 | + { | |
| 307 | + salary.PerformanceCompletionRate = salary.StoreTotalPerformance / salary.StoreLifeline; | |
| 308 | + } | |
| 309 | + else | |
| 310 | + { | |
| 311 | + salary.PerformanceCompletionRate = 0; | |
| 312 | + } | |
| 313 | + | |
| 314 | + // 2.5 计算门店总提成(先计算门店级别的提成) | |
| 315 | + decimal storeTotalCommission = 0; | |
| 316 | + decimal commissionRate = 0; | |
| 317 | + | |
| 318 | + // 如果门店生命线未设置(<=0),则没有提成 | |
| 319 | + if (salary.StoreLifeline <= 0) | |
| 320 | + { | |
| 321 | + commissionRate = 0; | |
| 322 | + storeTotalCommission = 0; | |
| 323 | + } | |
| 324 | + else | |
| 325 | + { | |
| 326 | + commissionRate = CalculateCommissionRate(salary.StoreTotalPerformance, salary.StoreLifeline); | |
| 327 | + storeTotalCommission = salary.StoreTotalPerformance * commissionRate; | |
| 328 | + } | |
| 329 | + | |
| 330 | + salary.CommissionRate = commissionRate; | |
| 331 | + | |
| 332 | + // 2.6 统计进店消耗人数 | |
| 333 | + salary.HeadCount = headcountDict.ContainsKey(storeId) ? headcountDict[storeId] : 0; | |
| 334 | + | |
| 335 | + // 2.7 计算门店总阶段奖励(先计算门店级别的奖励) | |
| 336 | + decimal storeTotalStageReward = 0; | |
| 337 | + bool reachedStage1 = false; | |
| 338 | + bool reachedStage2 = false; | |
| 339 | + | |
| 340 | + // 如果阶段目标未设置(为0),则没有奖励考核,奖励金额为0 | |
| 341 | + if (salary.Stage1TargetHeadCount <= 0 && salary.Stage2TargetHeadCount <= 0) | |
| 342 | + { | |
| 343 | + // 阶段目标未设置,没有奖励考核 | |
| 344 | + salary.ReachedStage1 = "否"; | |
| 345 | + salary.ReachedStage2 = "否"; | |
| 346 | + storeTotalStageReward = 0m; | |
| 347 | + } | |
| 348 | + else | |
| 349 | + { | |
| 350 | + // 阶段目标已设置,进行奖励考核 | |
| 351 | + reachedStage1 = salary.Stage1TargetHeadCount > 0 && salary.HeadCount >= salary.Stage1TargetHeadCount; | |
| 352 | + reachedStage2 = salary.Stage2TargetHeadCount > 0 && salary.HeadCount >= salary.Stage2TargetHeadCount; | |
| 353 | + | |
| 354 | + salary.ReachedStage1 = reachedStage1 ? "是" : "否"; | |
| 355 | + salary.ReachedStage2 = reachedStage2 ? "是" : "否"; | |
| 356 | + | |
| 357 | + // 阶段奖励计算规则: | |
| 358 | + // - 如果达到第二阶段,获得400元(第一阶段200 + 第二阶段200) | |
| 359 | + // - 如果只达到第一阶段,获得200元 | |
| 360 | + // - 如果都没达到,获得0元 | |
| 361 | + if (reachedStage2) | |
| 362 | + { | |
| 363 | + storeTotalStageReward = 400m; | |
| 364 | + } | |
| 365 | + else if (reachedStage1) | |
| 366 | + { | |
| 367 | + storeTotalStageReward = 200m; | |
| 368 | + } | |
| 369 | + else | |
| 370 | + { | |
| 371 | + storeTotalStageReward = 0m; | |
| 372 | + } | |
| 373 | + } | |
| 374 | + | |
| 375 | + // 2.8 计算底薪(根据门店分类) | |
| 376 | + salary.BaseSalary = CalculateBaseSalary(salary.StoreCategory.Value); | |
| 377 | + | |
| 378 | + // 2.9 固定奖励(手机管理费) | |
| 379 | + salary.PhoneManagementFee = 150m; | |
| 380 | + | |
| 381 | + // 2.10 考勤数据 | |
| 382 | + int workingDays = 0; | |
| 383 | + if (attendanceDict.ContainsKey(assistantUser.Id)) | |
| 384 | + { | |
| 385 | + var attendance = attendanceDict[assistantUser.Id]; | |
| 386 | + workingDays = (int)attendance.WorkDays; | |
| 387 | + salary.WorkingDays = workingDays; | |
| 388 | + salary.LeaveDays = (int)attendance.LeaveDays; | |
| 389 | + } | |
| 390 | + else | |
| 391 | + { | |
| 392 | + salary.WorkingDays = 0; | |
| 393 | + salary.LeaveDays = 0; | |
| 394 | + } | |
| 395 | + | |
| 396 | + // 2.11 按在店天数比例计算店助的提成和奖励 | |
| 397 | + // 逻辑:门店总提成 / 当月天数 * 在店天数 = 店助提成 | |
| 398 | + // 门店总奖励 / 当月天数 * 在店天数 = 店助奖励 | |
| 399 | + if (daysInMonth > 0 && workingDays > 0) | |
| 400 | + { | |
| 401 | + // 按比例计算提成 | |
| 402 | + salary.CommissionAmount = storeTotalCommission / daysInMonth * workingDays; | |
| 403 | + | |
| 404 | + // 按比例计算奖励 | |
| 405 | + salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays; | |
| 406 | + | |
| 407 | + // 计算阶段奖励的明细(按比例分配) | |
| 408 | + if (reachedStage2) | |
| 409 | + { | |
| 410 | + // 达到第二阶段:第一阶段200 + 第二阶段200 | |
| 411 | + salary.Stage1Reward = 200m / daysInMonth * workingDays; | |
| 412 | + salary.Stage2Reward = 200m / daysInMonth * workingDays; | |
| 413 | + } | |
| 414 | + else if (reachedStage1) | |
| 415 | + { | |
| 416 | + // 只达到第一阶段:第一阶段200 | |
| 417 | + salary.Stage1Reward = 200m / daysInMonth * workingDays; | |
| 418 | + salary.Stage2Reward = 0m; | |
| 419 | + } | |
| 420 | + else | |
| 421 | + { | |
| 422 | + // 都没达到 | |
| 423 | + salary.Stage1Reward = 0m; | |
| 424 | + salary.Stage2Reward = 0m; | |
| 425 | + } | |
| 426 | + } | |
| 427 | + else | |
| 428 | + { | |
| 429 | + // 如果当月天数为0或在店天数为0,则提成和奖励为0 | |
| 430 | + salary.CommissionAmount = 0; | |
| 431 | + salary.StageRewardAmount = 0; | |
| 432 | + salary.Stage1Reward = 0; | |
| 433 | + salary.Stage2Reward = 0; | |
| 434 | + } | |
| 435 | + | |
| 436 | + // 2.12 计算应发工资 | |
| 437 | + salary.GrossSalary = salary.BaseSalary + salary.CommissionAmount + salary.StageRewardAmount + salary.PhoneManagementFee; | |
| 438 | + | |
| 439 | + // 2.13 初始化扣款、补贴、奖金字段(默认值为0) | |
| 440 | + salary.MissingCard = 0; | |
| 441 | + salary.LateArrival = 0; | |
| 442 | + salary.LeaveDeduction = 0; | |
| 443 | + salary.SocialInsuranceDeduction = 0; | |
| 444 | + salary.RewardDeduction = 0; | |
| 445 | + salary.AccommodationDeduction = 0; | |
| 446 | + salary.StudyPeriodDeduction = 0; | |
| 447 | + salary.WorkClothesDeduction = 0; | |
| 448 | + salary.TotalDeduction = 0; | |
| 449 | + | |
| 450 | + salary.MonthlyTrainingSubsidy = 0; | |
| 451 | + salary.MonthlyTransportSubsidy = 0; | |
| 452 | + salary.LastMonthTrainingSubsidy = 0; | |
| 453 | + salary.LastMonthTransportSubsidy = 0; | |
| 454 | + salary.TotalSubsidy = 0; | |
| 455 | + | |
| 456 | + salary.Bonus = 0; | |
| 457 | + salary.ReturnPhoneDeposit = 0; | |
| 458 | + salary.ReturnAccommodationDeposit = 0; | |
| 459 | + | |
| 460 | + // 2.14 计算实发工资 | |
| 461 | + salary.ActualSalary = salary.GrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus; | |
| 462 | + | |
| 463 | + // 2.15 初始化支付相关字段 | |
| 464 | + salary.PaidAmount = 0; | |
| 465 | + salary.PendingAmount = salary.ActualSalary; | |
| 466 | + salary.LastMonthSupplement = 0; | |
| 467 | + salary.MonthlyTotalPayment = 0; | |
| 468 | + | |
| 469 | + assistantSalaryList.Add(salary); | |
| 470 | + } | |
| 471 | + | |
| 472 | + // 3. 保存数据 | |
| 473 | + if (assistantSalaryList.Any()) | |
| 474 | + { | |
| 475 | + // 先删除当月旧数据 (防止重复) | |
| 476 | + await _db.Deleteable<LqAssistantSalaryStatisticsEntity>() | |
| 477 | + .Where(x => x.StatisticsMonth == monthStr) | |
| 478 | + .ExecuteCommandAsync(); | |
| 479 | + | |
| 480 | + await _db.Insertable(assistantSalaryList).ExecuteCommandAsync(); | |
| 481 | + } | |
| 482 | + } | |
| 483 | + | |
| 484 | + /// <summary> | |
| 485 | + /// 计算提成比例 | |
| 486 | + /// </summary> | |
| 487 | + /// <param name="storePerformance">门店业绩</param> | |
| 488 | + /// <param name="storeLifeline">门店生命线</param> | |
| 489 | + /// <returns>提成比例(0%/0.4%/0.6%)</returns> | |
| 490 | + private decimal CalculateCommissionRate(decimal storePerformance, decimal storeLifeline) | |
| 491 | + { | |
| 492 | + if (storeLifeline <= 0) | |
| 493 | + { | |
| 494 | + return 0; | |
| 495 | + } | |
| 496 | + | |
| 497 | + decimal ratio = storePerformance / storeLifeline; | |
| 498 | + | |
| 499 | + if (ratio < 0.7m) | |
| 500 | + { | |
| 501 | + // 门店业绩 < 门店生命线 × 70% → 0% | |
| 502 | + return 0; | |
| 503 | + } | |
| 504 | + else if (ratio < 1.0m) | |
| 505 | + { | |
| 506 | + // 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% → 0.4% | |
| 507 | + return 0.004m; | |
| 508 | + } | |
| 509 | + else | |
| 510 | + { | |
| 511 | + // 门店业绩 ≥ 门店生命线 × 100% → 0.6% | |
| 512 | + return 0.006m; | |
| 513 | + } | |
| 514 | + } | |
| 515 | + | |
| 516 | + /// <summary> | |
| 517 | + /// 计算底薪 | |
| 518 | + /// </summary> | |
| 519 | + /// <param name="storeCategory">门店分类(1=A类,2=B类,3=C类)</param> | |
| 520 | + /// <returns>底薪金额</returns> | |
| 521 | + private decimal CalculateBaseSalary(int storeCategory) | |
| 522 | + { | |
| 523 | + return storeCategory switch | |
| 524 | + { | |
| 525 | + 1 => 3000m, // A类门店 | |
| 526 | + 2 => 3100m, // B类门店 | |
| 527 | + 3 => 3200m, // C类门店 | |
| 528 | + _ => throw new Exception($"门店分类值无效:{storeCategory},有效值为1(A类)、2(B类)、3(C类)") | |
| 529 | + }; | |
| 530 | + } | |
| 531 | + } | |
| 532 | +} | |
| 533 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.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.LqDirectorSalary; | |
| 8 | +using NCC.Extend.Entitys.lq_attendance_summary; | |
| 9 | +using NCC.Extend.Entitys.lq_director_salary_statistics; | |
| 10 | +using NCC.Extend.Entitys.lq_hytk_hytk; | |
| 11 | +using NCC.Extend.Entitys.lq_kd_kdjlb; | |
| 12 | +using NCC.Extend.Entitys.lq_md_target; | |
| 13 | +using NCC.Extend.Entitys.lq_md_xdbhsj; | |
| 14 | +using NCC.Extend.Entitys.lq_mdxx; | |
| 15 | +using NCC.Extend.Entitys.lq_xh_hyhk; | |
| 16 | +using NCC.Extend.Entitys.lq_xh_jksyj; | |
| 17 | +using NCC.System.Entitys.Permission; | |
| 18 | +using SqlSugar; | |
| 19 | +using System; | |
| 20 | +using System.Collections.Generic; | |
| 21 | +using System.Linq; | |
| 22 | +using System.Threading.Tasks; | |
| 23 | +using Yitter.IdGenerator; | |
| 24 | + | |
| 25 | +namespace NCC.Extend | |
| 26 | +{ | |
| 27 | + /// <summary> | |
| 28 | + /// 主任薪酬服务 | |
| 29 | + /// </summary> | |
| 30 | + [ApiDescriptionSettings(Tag = "主任薪酬服务", Name = "LqDirectorSalary", Order = 302)] | |
| 31 | + [Route("api/Extend/[controller]")] | |
| 32 | + public class LqDirectorSalaryService : IDynamicApiController, ITransient | |
| 33 | + { | |
| 34 | + private readonly ISqlSugarClient _db; | |
| 35 | + | |
| 36 | + /// <summary> | |
| 37 | + /// 初始化一个<see cref="LqDirectorSalaryService"/>类型的新实例 | |
| 38 | + /// </summary> | |
| 39 | + public LqDirectorSalaryService(ISqlSugarClient db) | |
| 40 | + { | |
| 41 | + _db = db; | |
| 42 | + } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 获取主任工资列表 | |
| 46 | + /// </summary> | |
| 47 | + /// <param name="input">查询参数</param> | |
| 48 | + /// <returns>主任工资分页列表</returns> | |
| 49 | + [HttpGet("director")] | |
| 50 | + public async Task<dynamic> GetDirectorSalaryList([FromQuery] DirectorSalaryInput input) | |
| 51 | + { | |
| 52 | + var monthStr = $"{input.Year}{input.Month:D2}"; | |
| 53 | + | |
| 54 | + // 1. 检查当月是否已生成工资数据 | |
| 55 | + var exists = await _db.Queryable<LqDirectorSalaryStatisticsEntity>() | |
| 56 | + .AnyAsync(x => x.StatisticsMonth == monthStr); | |
| 57 | + | |
| 58 | + // 2. 如果没有数据,则进行计算 | |
| 59 | + if (!exists) | |
| 60 | + { | |
| 61 | + await CalculateDirectorSalary(input.Year, input.Month); | |
| 62 | + } | |
| 63 | + | |
| 64 | + // 3. 查询数据 | |
| 65 | + var query = _db.Queryable<LqDirectorSalaryStatisticsEntity>() | |
| 66 | + .Where(x => x.StatisticsMonth == monthStr); | |
| 67 | + | |
| 68 | + if (!string.IsNullOrEmpty(input.StoreId)) | |
| 69 | + { | |
| 70 | + query = query.Where(x => x.StoreId == input.StoreId); | |
| 71 | + } | |
| 72 | + | |
| 73 | + if (!string.IsNullOrEmpty(input.Keyword)) | |
| 74 | + { | |
| 75 | + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword)); | |
| 76 | + } | |
| 77 | + | |
| 78 | + var list = await query.Select(x => new DirectorSalaryOutput | |
| 79 | + { | |
| 80 | + Id = x.Id, | |
| 81 | + StoreName = x.StoreName, | |
| 82 | + EmployeeName = x.EmployeeName, | |
| 83 | + Position = x.Position, | |
| 84 | + StoreTotalPerformance = x.StoreTotalPerformance, | |
| 85 | + StoreBillingPerformance = x.StoreBillingPerformance, | |
| 86 | + StoreRefundPerformance = x.StoreRefundPerformance, | |
| 87 | + StoreLifeline = x.StoreLifeline, | |
| 88 | + PerformanceCompletionRate = x.PerformanceCompletionRate, | |
| 89 | + PerformanceReached = x.PerformanceReached, | |
| 90 | + HeadCountReached = x.HeadCountReached, | |
| 91 | + ConsumeReached = x.ConsumeReached, | |
| 92 | + AssessmentDeduction = x.AssessmentDeduction, | |
| 93 | + UnreachedIndicatorCount = x.UnreachedIndicatorCount, | |
| 94 | + HeadCount = x.HeadCount, | |
| 95 | + TargetHeadCount = x.TargetHeadCount, | |
| 96 | + StoreConsume = x.StoreConsume, | |
| 97 | + TargetConsume = x.TargetConsume, | |
| 98 | + CommissionRateBelowLifeline = x.CommissionRateBelowLifeline, | |
| 99 | + CommissionRateAboveLifeline = x.CommissionRateAboveLifeline, | |
| 100 | + CommissionAmountBelowLifeline = x.CommissionAmountBelowLifeline, | |
| 101 | + CommissionAmountAboveLifeline = x.CommissionAmountAboveLifeline, | |
| 102 | + TotalCommissionAmount = x.TotalCommissionAmount, | |
| 103 | + BaseSalary = x.BaseSalary, | |
| 104 | + ActualBaseSalary = x.ActualBaseSalary, | |
| 105 | + WorkingDays = x.WorkingDays, | |
| 106 | + LeaveDays = x.LeaveDays, | |
| 107 | + GrossSalary = x.GrossSalary, | |
| 108 | + ActualSalary = x.ActualSalary, | |
| 109 | + TotalDeduction = x.TotalDeduction, | |
| 110 | + TotalSubsidy = x.TotalSubsidy, | |
| 111 | + Bonus = x.Bonus, | |
| 112 | + ReturnPhoneDeposit = x.ReturnPhoneDeposit, | |
| 113 | + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit, | |
| 114 | + MonthlyPaymentStatus = x.MonthlyPaymentStatus, | |
| 115 | + PaidAmount = x.PaidAmount, | |
| 116 | + PendingAmount = x.PendingAmount, | |
| 117 | + LastMonthSupplement = x.LastMonthSupplement, | |
| 118 | + MonthlyTotalPayment = x.MonthlyTotalPayment, | |
| 119 | + IsLocked = x.IsLocked, | |
| 120 | + UpdateTime = x.UpdateTime, | |
| 121 | + StoreType = x.StoreType, | |
| 122 | + StoreCategory = x.StoreCategory, | |
| 123 | + IsNewStore = x.IsNewStore, | |
| 124 | + NewStoreProtectionStage = x.NewStoreProtectionStage | |
| 125 | + }) | |
| 126 | + .ToPagedListAsync(input.currentPage, input.pageSize); | |
| 127 | + | |
| 128 | + return PageResult<DirectorSalaryOutput>.SqlSugarPageResult(list); | |
| 129 | + } | |
| 130 | + | |
| 131 | + /// <summary> | |
| 132 | + /// 计算主任工资 | |
| 133 | + /// </summary> | |
| 134 | + /// <param name="year">年份</param> | |
| 135 | + /// <param name="month">月份</param> | |
| 136 | + /// <returns></returns> | |
| 137 | + [HttpPost("calculate/director")] | |
| 138 | + public async Task CalculateDirectorSalary(int year, int month) | |
| 139 | + { | |
| 140 | + var startDate = new DateTime(year, month, 1); | |
| 141 | + var endDate = startDate.AddMonths(1).AddDays(-1); | |
| 142 | + var monthStr = $"{year}{month:D2}"; | |
| 143 | + | |
| 144 | + // 1. 获取基础数据 | |
| 145 | + | |
| 146 | + // 1.1 获取主任员工列表(从BASE_USER表,岗位为"主任") | |
| 147 | + var directorUserList = await _db.Queryable<UserEntity>() | |
| 148 | + .Where(x => x.Gw == "主任" && x.DeleteMark == null && x.EnabledMark == 1) | |
| 149 | + .Select(x => new { x.Id, x.RealName, x.Mdid, x.Gw }) | |
| 150 | + .ToListAsync(); | |
| 151 | + | |
| 152 | + if (!directorUserList.Any()) | |
| 153 | + { | |
| 154 | + // 如果没有主任员工,直接返回 | |
| 155 | + return; | |
| 156 | + } | |
| 157 | + | |
| 158 | + // 1.2 门店信息 (lq_mdxx) | |
| 159 | + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync(); | |
| 160 | + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); | |
| 161 | + | |
| 162 | + // 1.3 门店目标信息 (lq_md_target) - 包含门店生命线、目标人头、目标消耗 | |
| 163 | + var storeTargets = await _db.Queryable<LqMdTargetEntity>() | |
| 164 | + .Where(x => x.Month == monthStr) | |
| 165 | + .ToListAsync(); | |
| 166 | + var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId)) | |
| 167 | + .ToDictionary(x => x.StoreId, x => x); | |
| 168 | + | |
| 169 | + // 1.4 门店新店保护信息 (lq_md_xdbhsj) | |
| 170 | + var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>() | |
| 171 | + .Where(x => x.Sfqy == 1) | |
| 172 | + .ToListAsync(); | |
| 173 | + var newStoreProtectionDict = newStoreProtectionList | |
| 174 | + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate) | |
| 175 | + .GroupBy(x => x.Mdid) | |
| 176 | + .ToDictionary(g => g.Key, g => g.First()); | |
| 177 | + | |
| 178 | + // 1.5 门店总业绩计算 (开单实付 - 退卡金额) | |
| 179 | + // 开单实付 | |
| 180 | + var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>() | |
| 181 | + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1) | |
| 182 | + .Select(x => new { x.Djmd, x.Sfyj }) | |
| 183 | + .ToListAsync(); | |
| 184 | + var storeBillingDict = storeBillingList | |
| 185 | + .Where(x => !string.IsNullOrEmpty(x.Djmd)) | |
| 186 | + .GroupBy(x => x.Djmd) | |
| 187 | + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj)); | |
| 188 | + | |
| 189 | + // 退卡金额(使用F_ActualRefundAmount,如果没有则使用tkje) | |
| 190 | + var storeRefundList = await _db.Queryable<LqHytkHytkEntity>() | |
| 191 | + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) | |
| 192 | + .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje }) | |
| 193 | + .ToListAsync(); | |
| 194 | + var storeRefundDict = storeRefundList | |
| 195 | + .Where(x => !string.IsNullOrEmpty(x.Md)) | |
| 196 | + .GroupBy(x => x.Md) | |
| 197 | + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0)); | |
| 198 | + | |
| 199 | + // 1.6 门店消耗金额统计(按门店统计当月总消耗) | |
| 200 | + // 使用SQL查询优化性能 | |
| 201 | + var storeConsumeSql = $@" | |
| 202 | + SELECT | |
| 203 | + hyhk.md as StoreId, | |
| 204 | + COALESCE(SUM(jksyj.jksyj), 0) as ConsumeAmount | |
| 205 | + FROM lq_xh_jksyj jksyj | |
| 206 | + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | |
| 207 | + WHERE jksyj.F_IsEffective = 1 | |
| 208 | + AND hyhk.F_IsEffective = 1 | |
| 209 | + AND hyhk.hksj >= @startDate | |
| 210 | + AND hyhk.hksj <= @endDate | |
| 211 | + GROUP BY hyhk.md"; | |
| 212 | + | |
| 213 | + var storeConsumeData = await _db.Ado.SqlQueryAsync<dynamic>(storeConsumeSql, new { startDate, endDate = endDate.AddDays(1) }); | |
| 214 | + var storeConsumeDict = storeConsumeData | |
| 215 | + .Where(x => x.StoreId != null) | |
| 216 | + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ConsumeAmount ?? 0)); | |
| 217 | + | |
| 218 | + // 1.7 进店消耗人数统计(有消费金额的,按门店按月去重客户数) | |
| 219 | + // 使用SQL查询优化性能 | |
| 220 | + var headcountSql = $@" | |
| 221 | + SELECT | |
| 222 | + hyhk.md as StoreId, | |
| 223 | + COUNT(DISTINCT hyhk.hy) as HeadCount | |
| 224 | + FROM lq_xh_hyhk hyhk | |
| 225 | + WHERE hyhk.F_IsEffective = 1 | |
| 226 | + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @monthStr | |
| 227 | + AND EXISTS ( | |
| 228 | + SELECT 1 | |
| 229 | + FROM lq_xh_jksyj jksyj | |
| 230 | + WHERE jksyj.glkdbh = hyhk.F_Id | |
| 231 | + AND jksyj.F_IsEffective = 1 | |
| 232 | + AND jksyj.jksyj > 0 | |
| 233 | + ) | |
| 234 | + GROUP BY hyhk.md"; | |
| 235 | + | |
| 236 | + var headcountData = await _db.Ado.SqlQueryAsync<dynamic>(headcountSql, new { monthStr }); | |
| 237 | + var headcountDict = headcountData | |
| 238 | + .Where(x => x.StoreId != null) | |
| 239 | + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount)); | |
| 240 | + | |
| 241 | + // 1.8 考勤数据 (lq_attendance_summary) | |
| 242 | + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>() | |
| 243 | + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) | |
| 244 | + .ToListAsync(); | |
| 245 | + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x); | |
| 246 | + | |
| 247 | + // 2. 计算每个主任的工资 | |
| 248 | + var directorSalaryList = new List<LqDirectorSalaryStatisticsEntity>(); | |
| 249 | + | |
| 250 | + foreach (var directorUser in directorUserList) | |
| 251 | + { | |
| 252 | + var salary = new LqDirectorSalaryStatisticsEntity | |
| 253 | + { | |
| 254 | + Id = YitIdHelper.NextId().ToString(), | |
| 255 | + EmployeeId = directorUser.Id, | |
| 256 | + EmployeeName = directorUser.RealName, | |
| 257 | + StatisticsMonth = monthStr, | |
| 258 | + Position = "主任", | |
| 259 | + CreateTime = DateTime.Now, | |
| 260 | + UpdateTime = DateTime.Now, | |
| 261 | + IsLocked = 0, | |
| 262 | + MonthlyPaymentStatus = "未发放" | |
| 263 | + }; | |
| 264 | + | |
| 265 | + // 2.1 填充门店信息 | |
| 266 | + string storeId = directorUser.Mdid; | |
| 267 | + if (string.IsNullOrEmpty(storeId)) | |
| 268 | + { | |
| 269 | + // 如果用户没有门店ID,跳过 | |
| 270 | + continue; | |
| 271 | + } | |
| 272 | + | |
| 273 | + salary.StoreId = storeId; | |
| 274 | + | |
| 275 | + if (storeDict.ContainsKey(storeId)) | |
| 276 | + { | |
| 277 | + var store = storeDict[storeId]; | |
| 278 | + salary.StoreName = store.Dm; | |
| 279 | + salary.StoreType = store.StoreType; | |
| 280 | + salary.StoreCategory = store.StoreCategory; | |
| 281 | + | |
| 282 | + // 数据校验:门店分类必须设置 | |
| 283 | + if (!salary.StoreCategory.HasValue) | |
| 284 | + { | |
| 285 | + throw new Exception($"门店【{store.Dm}】的门店分类未设置,无法计算主任工资"); | |
| 286 | + } | |
| 287 | + } | |
| 288 | + | |
| 289 | + // 2.2 填充新店保护信息 | |
| 290 | + bool isNewStore = false; | |
| 291 | + if (newStoreProtectionDict.ContainsKey(storeId)) | |
| 292 | + { | |
| 293 | + var protection = newStoreProtectionDict[storeId]; | |
| 294 | + salary.IsNewStore = "是"; | |
| 295 | + salary.NewStoreProtectionStage = protection.Stage; | |
| 296 | + isNewStore = true; | |
| 297 | + } | |
| 298 | + else | |
| 299 | + { | |
| 300 | + salary.IsNewStore = "否"; | |
| 301 | + salary.NewStoreProtectionStage = 0; | |
| 302 | + } | |
| 303 | + | |
| 304 | + // 2.3 获取门店目标信息(门店生命线、目标人头、目标消耗) | |
| 305 | + if (!storeTargetDict.ContainsKey(storeId)) | |
| 306 | + { | |
| 307 | + throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算主任工资"); | |
| 308 | + } | |
| 309 | + | |
| 310 | + var storeTarget = storeTargetDict[storeId]; | |
| 311 | + salary.StoreLifeline = storeTarget.StoreLifeline; | |
| 312 | + salary.TargetHeadCount = storeTarget.StoreHeadcountTarget; | |
| 313 | + salary.TargetConsume = storeTarget.StoreConsumeTarget; | |
| 314 | + | |
| 315 | + // 数据校验:门店生命线、目标人头、目标消耗必须设置 | |
| 316 | + if (salary.StoreLifeline <= 0) | |
| 317 | + { | |
| 318 | + throw new Exception($"门店【{salary.StoreName ?? storeId}】的门店生命线未设置,无法计算主任工资"); | |
| 319 | + } | |
| 320 | + if (salary.TargetHeadCount <= 0) | |
| 321 | + { | |
| 322 | + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标人头数未设置,无法计算主任工资"); | |
| 323 | + } | |
| 324 | + if (!isNewStore && salary.TargetConsume <= 0) | |
| 325 | + { | |
| 326 | + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标消耗未设置,无法计算主任工资(老店需要考核消耗)"); | |
| 327 | + } | |
| 328 | + | |
| 329 | + // 2.4 计算门店业绩 | |
| 330 | + decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; | |
| 331 | + decimal refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0; | |
| 332 | + salary.StoreBillingPerformance = billing; | |
| 333 | + salary.StoreRefundPerformance = refund; | |
| 334 | + salary.StoreTotalPerformance = billing - refund; | |
| 335 | + | |
| 336 | + // 计算业绩完成率 | |
| 337 | + if (salary.StoreLifeline > 0) | |
| 338 | + { | |
| 339 | + salary.PerformanceCompletionRate = salary.StoreTotalPerformance / salary.StoreLifeline; | |
| 340 | + } | |
| 341 | + else | |
| 342 | + { | |
| 343 | + salary.PerformanceCompletionRate = 0; | |
| 344 | + } | |
| 345 | + | |
| 346 | + // 2.5 统计门店消耗金额 | |
| 347 | + salary.StoreConsume = storeConsumeDict.ContainsKey(storeId) ? storeConsumeDict[storeId] : 0; | |
| 348 | + | |
| 349 | + // 2.6 统计进店消耗人数 | |
| 350 | + salary.HeadCount = headcountDict.ContainsKey(storeId) ? headcountDict[storeId] : 0; | |
| 351 | + | |
| 352 | + // 2.7 计算考核指标(业绩、人头、消耗是否达标) | |
| 353 | + bool performanceReached = salary.StoreTotalPerformance >= salary.StoreLifeline; | |
| 354 | + bool headCountReached = salary.HeadCount >= salary.TargetHeadCount; | |
| 355 | + bool consumeReached = salary.StoreConsume >= salary.TargetConsume; | |
| 356 | + | |
| 357 | + salary.PerformanceReached = performanceReached ? "是" : "否"; | |
| 358 | + salary.HeadCountReached = headCountReached ? "是" : "否"; | |
| 359 | + salary.ConsumeReached = consumeReached ? "是" : "否"; | |
| 360 | + | |
| 361 | + // 计算未达标指标数量 | |
| 362 | + int unreachedCount = 0; | |
| 363 | + if (!performanceReached) unreachedCount++; | |
| 364 | + if (!headCountReached) unreachedCount++; | |
| 365 | + // 新店不考核消耗 | |
| 366 | + if (!isNewStore && !consumeReached) unreachedCount++; | |
| 367 | + | |
| 368 | + salary.UnreachedIndicatorCount = unreachedCount; | |
| 369 | + salary.AssessmentDeduction = unreachedCount * 500m; | |
| 370 | + | |
| 371 | + // 2.8 计算底薪 | |
| 372 | + salary.BaseSalary = 3500m; // 固定底薪3500元 | |
| 373 | + salary.ActualBaseSalary = salary.BaseSalary - salary.AssessmentDeduction; // 实际底薪 = 底薪 - 考核扣款 | |
| 374 | + | |
| 375 | + // 2.9 计算阶梯提成 | |
| 376 | + CalculateCommission(salary, isNewStore); | |
| 377 | + | |
| 378 | + // 2.10 考勤数据 | |
| 379 | + if (attendanceDict.ContainsKey(directorUser.Id)) | |
| 380 | + { | |
| 381 | + var attendance = attendanceDict[directorUser.Id]; | |
| 382 | + salary.WorkingDays = (int)attendance.WorkDays; | |
| 383 | + salary.LeaveDays = (int)attendance.LeaveDays; | |
| 384 | + } | |
| 385 | + else | |
| 386 | + { | |
| 387 | + salary.WorkingDays = 0; | |
| 388 | + salary.LeaveDays = 0; | |
| 389 | + } | |
| 390 | + | |
| 391 | + // 2.11 计算应发工资 | |
| 392 | + salary.GrossSalary = salary.ActualBaseSalary + salary.TotalCommissionAmount; | |
| 393 | + | |
| 394 | + // 2.12 初始化扣款、补贴、奖金字段(默认值为0) | |
| 395 | + salary.MissingCard = 0; | |
| 396 | + salary.LateArrival = 0; | |
| 397 | + salary.LeaveDeduction = 0; | |
| 398 | + salary.SocialInsuranceDeduction = 0; | |
| 399 | + salary.RewardDeduction = 0; | |
| 400 | + salary.AccommodationDeduction = 0; | |
| 401 | + salary.StudyPeriodDeduction = 0; | |
| 402 | + salary.WorkClothesDeduction = 0; | |
| 403 | + salary.TotalDeduction = 0; | |
| 404 | + | |
| 405 | + salary.MonthlyTrainingSubsidy = 0; | |
| 406 | + salary.MonthlyTransportSubsidy = 0; | |
| 407 | + salary.LastMonthTrainingSubsidy = 0; | |
| 408 | + salary.LastMonthTransportSubsidy = 0; | |
| 409 | + salary.TotalSubsidy = 0; | |
| 410 | + | |
| 411 | + salary.Bonus = 0; | |
| 412 | + salary.ReturnPhoneDeposit = 0; | |
| 413 | + salary.ReturnAccommodationDeposit = 0; | |
| 414 | + | |
| 415 | + // 2.13 计算实发工资 | |
| 416 | + salary.ActualSalary = salary.GrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus; | |
| 417 | + | |
| 418 | + // 2.14 初始化支付相关字段 | |
| 419 | + salary.PaidAmount = 0; | |
| 420 | + salary.PendingAmount = salary.ActualSalary; | |
| 421 | + salary.LastMonthSupplement = 0; | |
| 422 | + salary.MonthlyTotalPayment = 0; | |
| 423 | + | |
| 424 | + directorSalaryList.Add(salary); | |
| 425 | + } | |
| 426 | + | |
| 427 | + // 3. 保存数据 | |
| 428 | + if (directorSalaryList.Any()) | |
| 429 | + { | |
| 430 | + // 先删除当月旧数据 (防止重复) | |
| 431 | + await _db.Deleteable<LqDirectorSalaryStatisticsEntity>() | |
| 432 | + .Where(x => x.StatisticsMonth == monthStr) | |
| 433 | + .ExecuteCommandAsync(); | |
| 434 | + | |
| 435 | + await _db.Insertable(directorSalaryList).ExecuteCommandAsync(); | |
| 436 | + } | |
| 437 | + } | |
| 438 | + | |
| 439 | + /// <summary> | |
| 440 | + /// 计算阶梯提成 | |
| 441 | + /// </summary> | |
| 442 | + /// <param name="salary">工资实体</param> | |
| 443 | + /// <param name="isNewStore">是否新店</param> | |
| 444 | + private void CalculateCommission(LqDirectorSalaryStatisticsEntity salary, bool isNewStore) | |
| 445 | + { | |
| 446 | + if (salary.StoreLifeline <= 0) | |
| 447 | + { | |
| 448 | + // 如果门店生命线未设置,则没有提成 | |
| 449 | + salary.CommissionRateBelowLifeline = 0; | |
| 450 | + salary.CommissionRateAboveLifeline = 0; | |
| 451 | + salary.CommissionAmountBelowLifeline = 0; | |
| 452 | + salary.CommissionAmountAboveLifeline = 0; | |
| 453 | + salary.TotalCommissionAmount = 0; | |
| 454 | + return; | |
| 455 | + } | |
| 456 | + | |
| 457 | + decimal performance = salary.StoreTotalPerformance; | |
| 458 | + decimal lifeline = salary.StoreLifeline; | |
| 459 | + | |
| 460 | + // 确定提成比例(根据新店/老店和门店分类) | |
| 461 | + decimal rateBelowLifeline; | |
| 462 | + decimal rateAboveLifeline; | |
| 463 | + | |
| 464 | + if (isNewStore) | |
| 465 | + { | |
| 466 | + // 新店:统一标准,不区分门店分类 | |
| 467 | + rateBelowLifeline = 0.02m; // 2% | |
| 468 | + rateAboveLifeline = 0.025m; // 2.5% | |
| 469 | + } | |
| 470 | + else | |
| 471 | + { | |
| 472 | + // 老店:根据门店分类确定提成比例 | |
| 473 | + int? storeCategory = salary.StoreCategory; | |
| 474 | + if (!storeCategory.HasValue) | |
| 475 | + { | |
| 476 | + throw new Exception($"门店【{salary.StoreName}】的门店分类未设置,无法计算提成"); | |
| 477 | + } | |
| 478 | + | |
| 479 | + switch (storeCategory.Value) | |
| 480 | + { | |
| 481 | + case 1: // A类门店 | |
| 482 | + rateBelowLifeline = 0.02m; // 2% | |
| 483 | + rateAboveLifeline = 0.025m; // 2.5% | |
| 484 | + break; | |
| 485 | + case 2: // B类门店 | |
| 486 | + rateBelowLifeline = 0.025m; // 2.5% | |
| 487 | + rateAboveLifeline = 0.03m; // 3% | |
| 488 | + break; | |
| 489 | + case 3: // C类门店 | |
| 490 | + rateBelowLifeline = 0.03m; // 3% | |
| 491 | + rateAboveLifeline = 0.035m; // 3.5% | |
| 492 | + break; | |
| 493 | + default: | |
| 494 | + throw new Exception($"门店【{salary.StoreName}】的门店分类值无效:{storeCategory.Value},有效值为1(A类)、2(B类)、3(C类)"); | |
| 495 | + } | |
| 496 | + } | |
| 497 | + | |
| 498 | + salary.CommissionRateBelowLifeline = rateBelowLifeline; | |
| 499 | + salary.CommissionRateAboveLifeline = rateAboveLifeline; | |
| 500 | + | |
| 501 | + // 计算阶梯提成 | |
| 502 | + if (performance <= lifeline) | |
| 503 | + { | |
| 504 | + // 业绩 ≤ 生命线:只计算≤生命线部分的提成 | |
| 505 | + salary.CommissionAmountBelowLifeline = performance * rateBelowLifeline; | |
| 506 | + salary.CommissionAmountAboveLifeline = 0; | |
| 507 | + } | |
| 508 | + else | |
| 509 | + { | |
| 510 | + // 业绩 > 生命线:分别计算≤生命线部分和>生命线部分的提成 | |
| 511 | + salary.CommissionAmountBelowLifeline = lifeline * rateBelowLifeline; | |
| 512 | + salary.CommissionAmountAboveLifeline = (performance - lifeline) * rateAboveLifeline; | |
| 513 | + } | |
| 514 | + | |
| 515 | + salary.TotalCommissionAmount = salary.CommissionAmountBelowLifeline + salary.CommissionAmountAboveLifeline; | |
| 516 | + } | |
| 517 | + } | |
| 518 | +} | |
| 519 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
| ... | ... | @@ -130,6 +130,7 @@ namespace NCC.Extend.LqKdKdjlb |
| 130 | 130 | output.upgradeLifeBeauty = entity.UpgradeLifeBeauty; |
| 131 | 131 | output.upgradeTechBeauty = entity.UpgradeTechBeauty; |
| 132 | 132 | output.upgradeMedicalBeauty = entity.UpgradeMedicalBeauty; |
| 133 | + output.fileUrl = entity.F_FIleUrl; | |
| 133 | 134 | if (!string.IsNullOrEmpty(entity.AppointmentId)) |
| 134 | 135 | { |
| 135 | 136 | output.appointmentTime = await _db.Queryable<LqYyjlEntity>().Where(x => x.Id == entity.AppointmentId).Select(x => x.Yysj).FirstAsync(); | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs
| ... | ... | @@ -111,7 +111,7 @@ namespace NCC.Extend |
| 111 | 111 | LaundrySupplierId = input.LaundrySupplierId, |
| 112 | 112 | Quantity = input.Quantity, |
| 113 | 113 | LaundryPrice = supplier.LaundryPrice, // 记录历史价格 |
| 114 | - TotalPrice = 0, // 送出时总费用为0 | |
| 114 | + TotalPrice = input.Quantity * supplier.LaundryPrice, // 送出时总费用为数量 * 单价 | |
| 115 | 115 | Remark = input.Remark, |
| 116 | 116 | IsEffective = StatusEnum.有效.GetHashCode(), |
| 117 | 117 | CreateUser = _userManager.UserId, | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
| ... | ... | @@ -15,6 +15,9 @@ using System.Linq; |
| 15 | 15 | using System.Threading.Tasks; |
| 16 | 16 | using NCC.Extend.Entitys; |
| 17 | 17 | using NCC.Extend.Entitys.Dto.LqReimbursementApplication; |
| 18 | +using NCC.Extend.Entitys.lq_reimbursement_application_node; | |
| 19 | +using NCC.Extend.Entitys.lq_reimbursement_application_node_user; | |
| 20 | +using NCC.Extend.Entitys.lq_reimbursement_approval_record; | |
| 18 | 21 | using Yitter.IdGenerator; |
| 19 | 22 | using NCC.Common.Helper; |
| 20 | 23 | using NCC.JsonSerialization; |
| ... | ... | @@ -49,16 +52,85 @@ namespace NCC.Extend.LqReimbursementApplication |
| 49 | 52 | } |
| 50 | 53 | |
| 51 | 54 | /// <summary> |
| 52 | - /// 获取报销申请表 | |
| 55 | + /// 获取报销申请表详情(包含表单和流程信息) | |
| 53 | 56 | /// </summary> |
| 54 | - /// <param name="id">参数</param> | |
| 57 | + /// <param name="id">申请编号</param> | |
| 55 | 58 | /// <returns></returns> |
| 56 | 59 | [HttpGet("{id}")] |
| 57 | 60 | public async Task<dynamic> GetInfo(string id) |
| 58 | 61 | { |
| 59 | 62 | var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id); |
| 63 | + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); | |
| 64 | + | |
| 60 | 65 | var output = entity.Adapt<LqReimbursementApplicationInfoOutput>(); |
| 61 | - return output; | |
| 66 | + | |
| 67 | + // 获取节点配置 | |
| 68 | + var nodes = await _db.Queryable<LqReimbursementApplicationNodeEntity>() | |
| 69 | + .Where(x => x.ApplicationId == id) | |
| 70 | + .OrderBy(x => x.NodeOrder) | |
| 71 | + .ToListAsync(); | |
| 72 | + | |
| 73 | + // 获取每个节点的审批人 | |
| 74 | + var nodeUsers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 75 | + .Where(x => x.ApplicationId == id) | |
| 76 | + .OrderBy(x => x.NodeOrder) | |
| 77 | + .OrderBy(x => x.SortOrder) | |
| 78 | + .ToListAsync(); | |
| 79 | + | |
| 80 | + // 获取审批历史 | |
| 81 | + var approvalRecords = await _db.Queryable<LqReimbursementApprovalRecordEntity>() | |
| 82 | + .Where(x => x.ApplicationId == id) | |
| 83 | + .OrderBy(x => x.NodeOrder) | |
| 84 | + .OrderBy(x => x.ApprovalTime) | |
| 85 | + .ToListAsync(); | |
| 86 | + | |
| 87 | + // 组装节点信息 | |
| 88 | + var nodeList = nodes.Select(n => new | |
| 89 | + { | |
| 90 | + nodeId = n.Id, | |
| 91 | + nodeOrder = n.NodeOrder, | |
| 92 | + nodeName = n.NodeName, | |
| 93 | + approvalType = n.ApprovalType, | |
| 94 | + isRequired = n.IsRequired, | |
| 95 | + approvers = nodeUsers.Where(u => u.NodeId == n.Id).Select(u => new | |
| 96 | + { | |
| 97 | + userId = u.UserId, | |
| 98 | + userName = u.UserName, | |
| 99 | + sortOrder = u.SortOrder | |
| 100 | + }).ToList(), | |
| 101 | + approvalRecords = approvalRecords.Where(r => r.NodeId == n.Id).Select(r => new | |
| 102 | + { | |
| 103 | + approverName = r.ApproverName, | |
| 104 | + approvalResult = r.ApprovalResult, | |
| 105 | + approvalOpinion = r.ApprovalOpinion, | |
| 106 | + approvalTime = r.ApprovalTime | |
| 107 | + }).ToList() | |
| 108 | + }).ToList(); | |
| 109 | + | |
| 110 | + // 获取当前节点审批人 | |
| 111 | + var currentApprovers = new List<object>(); | |
| 112 | + if (entity.CurrentNodeOrder.HasValue && entity.CurrentNodeOrder > 0) | |
| 113 | + { | |
| 114 | + currentApprovers = nodeUsers | |
| 115 | + .Where(u => u.NodeOrder == entity.CurrentNodeOrder.Value) | |
| 116 | + .Select(u => new | |
| 117 | + { | |
| 118 | + userId = u.UserId, | |
| 119 | + userName = u.UserName | |
| 120 | + }) | |
| 121 | + .Cast<object>() | |
| 122 | + .ToList(); | |
| 123 | + } | |
| 124 | + | |
| 125 | + return new | |
| 126 | + { | |
| 127 | + form = output, | |
| 128 | + nodes = nodeList, | |
| 129 | + currentApprovers = currentApprovers, | |
| 130 | + currentNodeOrder = entity.CurrentNodeOrder, | |
| 131 | + approvalStatus = entity.ApprovalStatus ?? entity.ApproveStatus, | |
| 132 | + returnedReason = entity.ReturnedReason | |
| 133 | + }; | |
| 62 | 134 | } |
| 63 | 135 | |
| 64 | 136 | /// <summary> |
| ... | ... | @@ -76,7 +148,7 @@ namespace NCC.Extend.LqReimbursementApplication |
| 76 | 148 | List<string> queryApproveTime = input.approveTime != null ? input.approveTime.Split(',').ToObeject<List<string>>() : null; |
| 77 | 149 | DateTime? startApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.First()) : null; |
| 78 | 150 | DateTime? endApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.Last()) : null; |
| 79 | - var data = await _db.Queryable<LqReimbursementApplicationEntity>() | |
| 151 | + var query = _db.Queryable<LqReimbursementApplicationEntity>() | |
| 80 | 152 | .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id)) |
| 81 | 153 | .WhereIF(!string.IsNullOrEmpty(input.applicationUserId), p => p.ApplicationUserId.Contains(input.applicationUserId)) |
| 82 | 154 | .WhereIF(!string.IsNullOrEmpty(input.applicationUserName), p => p.ApplicationUserName.Contains(input.applicationUserName)) |
| ... | ... | @@ -85,24 +157,60 @@ namespace NCC.Extend.LqReimbursementApplication |
| 85 | 157 | .WhereIF(queryApplicationTime != null, p => p.ApplicationTime <= new DateTime(endApplicationTime.ToDate().Year, endApplicationTime.ToDate().Month, endApplicationTime.ToDate().Day, 23, 59, 59)) |
| 86 | 158 | .WhereIF(!string.IsNullOrEmpty(input.amount), p => p.Amount.Contains(input.amount)) |
| 87 | 159 | .WhereIF(!string.IsNullOrEmpty(input.approveUser), p => p.ApproveUser.Equals(input.approveUser)) |
| 88 | - .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => p.ApproveStatus.Contains(input.approveStatus)) | |
| 160 | + .WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => (p.ApprovalStatus ?? p.ApproveStatus).Contains(input.approveStatus)) | |
| 89 | 161 | // .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0)) |
| 90 | 162 | //.WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59)) |
| 91 | 163 | .WhereIF(!string.IsNullOrEmpty(input.purchaseRecordsId), p => p.PurchaseRecordsId.Contains(input.purchaseRecordsId)) |
| 92 | - .Select(it => new LqReimbursementApplicationListOutput | |
| 164 | + .OrderBy(sidx + " " + input.sort); | |
| 165 | + | |
| 166 | + var total = await query.CountAsync(); | |
| 167 | + var entities = await query.ToPageListAsync(input.currentPage, input.pageSize); | |
| 168 | + | |
| 169 | + // 获取当前审批人信息 | |
| 170 | + var applicationIds = entities.Select(x => x.Id).ToList(); | |
| 171 | + var currentApprovers = new List<dynamic>(); | |
| 172 | + if (applicationIds.Any()) | |
| 173 | + { | |
| 174 | + var approverList = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 175 | + .Where(x => applicationIds.Contains(x.ApplicationId)) | |
| 176 | + .InnerJoin<LqReimbursementApplicationEntity>((u, a) => u.ApplicationId == a.Id && u.NodeOrder == a.CurrentNodeOrder) | |
| 177 | + .Select((u, a) => new | |
| 178 | + { | |
| 179 | + applicationId = a.Id, | |
| 180 | + approverName = u.UserName | |
| 181 | + }) | |
| 182 | + .ToListAsync(); | |
| 183 | + currentApprovers = approverList.Cast<dynamic>().ToList(); | |
| 184 | + } | |
| 185 | + | |
| 186 | + var approverDict = currentApprovers | |
| 187 | + .GroupBy(x => (string)x.applicationId) | |
| 188 | + .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName))); | |
| 189 | + | |
| 190 | + // 组装返回数据 | |
| 191 | + var result = entities.Select(item => new LqReimbursementApplicationListOutput | |
| 192 | + { | |
| 193 | + id = item.Id, | |
| 194 | + applicationUserId = item.ApplicationUserId, | |
| 195 | + applicationUserName = item.ApplicationUserName, | |
| 196 | + applicationStoreId = item.ApplicationStoreId, | |
| 197 | + applicationTime = item.ApplicationTime, | |
| 198 | + amount = item.Amount, | |
| 199 | + approveUser = item.ApproveUser, | |
| 200 | + approveStatus = item.ApprovalStatus ?? item.ApproveStatus, | |
| 201 | + approveTime = item.ApproveTime, | |
| 202 | + purchaseRecordsId = item.PurchaseRecordsId, | |
| 203 | + currentApprovers = approverDict.ContainsKey(item.Id) ? approverDict[item.Id] : null, | |
| 204 | + currentNodeOrder = item.CurrentNodeOrder, | |
| 205 | + nodeCount = item.NodeCount | |
| 206 | + }).ToList(); | |
| 207 | + | |
| 208 | + return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult( | |
| 209 | + new SqlSugarPagedList<LqReimbursementApplicationListOutput> | |
| 93 | 210 | { |
| 94 | - id = it.Id, | |
| 95 | - applicationUserId = it.ApplicationUserId, | |
| 96 | - applicationUserName = it.ApplicationUserName, | |
| 97 | - applicationStoreId = it.ApplicationStoreId, | |
| 98 | - applicationTime = it.ApplicationTime, | |
| 99 | - amount = it.Amount, | |
| 100 | - approveUser = it.ApproveUser, | |
| 101 | - approveStatus = it.ApproveStatus, | |
| 102 | - approveTime = it.ApproveTime, | |
| 103 | - purchaseRecordsId = it.PurchaseRecordsId, | |
| 104 | - }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize); | |
| 105 | - return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult(data); | |
| 211 | + list = result, | |
| 212 | + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total } | |
| 213 | + }); | |
| 106 | 214 | } |
| 107 | 215 | |
| 108 | 216 | /// <summary> |
| ... | ... | @@ -122,11 +230,99 @@ namespace NCC.Extend.LqReimbursementApplication |
| 122 | 230 | //开启事务 |
| 123 | 231 | _db.BeginTran(); |
| 124 | 232 | |
| 125 | - // 保存报销申请表 | |
| 126 | - var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); | |
| 233 | + // 1. 验证节点配置 | |
| 234 | + if (input.nodes == null || input.nodes.Count < 3 || input.nodes.Count > 5) | |
| 235 | + { | |
| 236 | + throw new Exception("节点数量必须在3-5个之间"); | |
| 237 | + } | |
| 238 | + | |
| 239 | + // 验证节点顺序是否连续(1, 2, 3, ...) | |
| 240 | + var nodeOrders = input.nodes.Select(n => n.nodeOrder).OrderBy(x => x).ToList(); | |
| 241 | + for (int i = 0; i < nodeOrders.Count; i++) | |
| 242 | + { | |
| 243 | + if (nodeOrders[i] != i + 1) | |
| 244 | + { | |
| 245 | + throw new Exception($"节点顺序必须连续,从1开始"); | |
| 246 | + } | |
| 247 | + } | |
| 248 | + | |
| 249 | + // 验证每个节点至少有一个审批人 | |
| 250 | + foreach (var node in input.nodes) | |
| 251 | + { | |
| 252 | + if (node.approverIds == null || node.approverIds.Count == 0) | |
| 253 | + { | |
| 254 | + throw new Exception($"节点{node.nodeOrder}({node.nodeName})必须至少指定一个审批人"); | |
| 255 | + } | |
| 256 | + } | |
| 257 | + | |
| 258 | + // 2. 设置报销申请初始状态 | |
| 259 | + entity.NodeCount = input.nodes.Count; | |
| 260 | + entity.CurrentNodeOrder = 0; | |
| 261 | + entity.ApprovalStatus = "待审批"; | |
| 262 | + entity.ApplicationTime = DateTime.Now; | |
| 263 | + if (string.IsNullOrEmpty(entity.ApplicationUserId)) | |
| 264 | + { | |
| 265 | + entity.ApplicationUserId = userInfo.userId; | |
| 266 | + } | |
| 267 | + if (string.IsNullOrEmpty(entity.ApplicationUserName)) | |
| 268 | + { | |
| 269 | + entity.ApplicationUserName = userInfo.userName; | |
| 270 | + } | |
| 271 | + | |
| 272 | + // 3. 保存报销申请表(不使用IgnoreColumns,确保新字段被保存) | |
| 273 | + var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); | |
| 127 | 274 | if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); |
| 128 | 275 | |
| 129 | - // 更新购买记录的审批单编号和审批状态为"待审批" | |
| 276 | + // 4. 创建节点配置 | |
| 277 | + if (input.nodes != null && input.nodes.Count > 0) | |
| 278 | + { | |
| 279 | + foreach (var nodeConfig in input.nodes) | |
| 280 | + { | |
| 281 | + var node = new LqReimbursementApplicationNodeEntity | |
| 282 | + { | |
| 283 | + Id = YitIdHelper.NextId().ToString(), | |
| 284 | + ApplicationId = entity.Id, | |
| 285 | + NodeOrder = nodeConfig.nodeOrder, | |
| 286 | + NodeName = nodeConfig.nodeName, | |
| 287 | + ApprovalType = nodeConfig.approvalType ?? "会签", | |
| 288 | + IsRequired = 1, | |
| 289 | + CreateTime = DateTime.Now | |
| 290 | + }; | |
| 291 | + var nodeResult = await _db.Insertable(node).ExecuteCommandAsync(); | |
| 292 | + if (nodeResult <= 0) | |
| 293 | + { | |
| 294 | + throw new Exception($"创建节点{nodeConfig.nodeOrder}失败"); | |
| 295 | + } | |
| 296 | + | |
| 297 | + // 5. 创建节点审批人 | |
| 298 | + if (nodeConfig.approverIds != null && nodeConfig.approverIds.Count > 0) | |
| 299 | + { | |
| 300 | + for (int i = 0; i < nodeConfig.approverIds.Count; i++) | |
| 301 | + { | |
| 302 | + var nodeUser = new LqReimbursementApplicationNodeUserEntity | |
| 303 | + { | |
| 304 | + Id = YitIdHelper.NextId().ToString(), | |
| 305 | + ApplicationId = entity.Id, | |
| 306 | + NodeId = node.Id, | |
| 307 | + NodeOrder = nodeConfig.nodeOrder, | |
| 308 | + UserId = nodeConfig.approverIds[i], | |
| 309 | + UserName = nodeConfig.approverNames != null && i < nodeConfig.approverNames.Count | |
| 310 | + ? nodeConfig.approverNames[i] | |
| 311 | + : null, | |
| 312 | + SortOrder = i + 1, | |
| 313 | + CreateTime = DateTime.Now | |
| 314 | + }; | |
| 315 | + var userResult = await _db.Insertable(nodeUser).ExecuteCommandAsync(); | |
| 316 | + if (userResult <= 0) | |
| 317 | + { | |
| 318 | + throw new Exception($"创建节点{nodeConfig.nodeOrder}的审批人{nodeConfig.approverIds[i]}失败"); | |
| 319 | + } | |
| 320 | + } | |
| 321 | + } | |
| 322 | + } | |
| 323 | + } | |
| 324 | + | |
| 325 | + // 6. 更新购买记录的审批单编号和审批状态为"待审批" | |
| 130 | 326 | if (input.selectedPurchaseRecordIds != null && input.selectedPurchaseRecordIds.Count > 0) |
| 131 | 327 | { |
| 132 | 328 | // 先更新ApplicationId |
| ... | ... | @@ -370,107 +566,582 @@ namespace NCC.Extend.LqReimbursementApplication |
| 370 | 566 | } |
| 371 | 567 | |
| 372 | 568 | /// <summary> |
| 373 | - /// 通过审批 | |
| 569 | + /// 提交审批(进入第一个节点) | |
| 374 | 570 | /// </summary> |
| 375 | 571 | /// <param name="id">申请编号</param> |
| 376 | 572 | /// <returns></returns> |
| 377 | - [HttpPost("{id}/Actions/Approve")] | |
| 378 | - public async Task Approve(string id) | |
| 573 | + [HttpPost("{id}/Actions/SubmitApproval")] | |
| 574 | + public async Task SubmitApproval(string id) | |
| 379 | 575 | { |
| 380 | - var userInfo = await _userManager.GetUserInfo(); | |
| 381 | 576 | var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id); |
| 382 | 577 | _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); |
| 383 | 578 | |
| 579 | + // 允许"待审批"和"已退回"状态的申请提交审批 | |
| 580 | + if (entity.CurrentNodeOrder != 0 && entity.ApprovalStatus != "已退回") | |
| 581 | + { | |
| 582 | + throw new Exception("该申请已经提交审批,不能重复提交"); | |
| 583 | + } | |
| 584 | + | |
| 585 | + if (entity.NodeCount == null || entity.NodeCount < 3 || entity.NodeCount > 5) | |
| 586 | + { | |
| 587 | + throw new Exception("节点配置异常,无法提交审批"); | |
| 588 | + } | |
| 589 | + | |
| 384 | 590 | try |
| 385 | 591 | { |
| 386 | - //开启事务 | |
| 387 | 592 | _db.BeginTran(); |
| 388 | 593 | |
| 389 | - // 更新申请表的审批状态 | |
| 390 | - entity.ApproveStatus = "已审批"; | |
| 391 | - entity.ApproveTime = DateTime.Now; | |
| 392 | - entity.ApproveUser = userInfo.userId; | |
| 594 | + // 获取第一个节点 | |
| 595 | + var firstNode = await _db.Queryable<LqReimbursementApplicationNodeEntity>() | |
| 596 | + .Where(x => x.ApplicationId == id && x.NodeOrder == 1) | |
| 597 | + .FirstAsync(); | |
| 598 | + | |
| 599 | + if (firstNode == null) | |
| 600 | + { | |
| 601 | + throw new Exception("未找到第一个审批节点配置"); | |
| 602 | + } | |
| 603 | + | |
| 604 | + // 更新报销申请状态 | |
| 605 | + entity.CurrentNodeOrder = 1; | |
| 606 | + entity.CurrentNodeId = firstNode.Id; | |
| 607 | + entity.ApprovalStatus = "审批中"; | |
| 393 | 608 | await _db.Updateable(entity).ExecuteCommandAsync(); |
| 394 | 609 | |
| 395 | - // 更新关联的购买记录 | |
| 396 | - if (!string.IsNullOrEmpty(entity.PurchaseRecordsId)) | |
| 610 | + // 为第一个节点的每个审批人创建待审批记录 | |
| 611 | + var firstNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 612 | + .Where(x => x.ApplicationId == id && x.NodeOrder == 1) | |
| 613 | + .ToListAsync(); | |
| 614 | + | |
| 615 | + foreach (var approver in firstNodeApprovers) | |
| 397 | 616 | { |
| 398 | - var purchaseIds = entity.PurchaseRecordsId.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToList(); | |
| 399 | - if (purchaseIds.Count > 0) | |
| 617 | + var record = new LqReimbursementApprovalRecordEntity | |
| 400 | 618 | { |
| 401 | - await _db.Updateable<LqPurchaseRecordsEntity>() | |
| 402 | - .SetColumns(it => new LqPurchaseRecordsEntity | |
| 403 | - { | |
| 404 | - ApproveStatus = "已审批", | |
| 405 | - ApproveTime = DateTime.Now, | |
| 406 | - ApproveUser = userInfo.userId | |
| 407 | - }) | |
| 408 | - .Where(it => purchaseIds.Contains(it.Id)) | |
| 409 | - .ExecuteCommandAsync(); | |
| 410 | - } | |
| 619 | + Id = YitIdHelper.NextId().ToString(), | |
| 620 | + ApplicationId = id, | |
| 621 | + NodeId = firstNode.Id, | |
| 622 | + NodeOrder = 1, | |
| 623 | + ApproverId = approver.UserId, | |
| 624 | + ApproverName = approver.UserName, | |
| 625 | + ApprovalResult = "待审批", | |
| 626 | + IsCurrentNode = 1, | |
| 627 | + ApprovalTime = null | |
| 628 | + }; | |
| 629 | + await _db.Insertable(record).ExecuteCommandAsync(); | |
| 411 | 630 | } |
| 412 | 631 | |
| 413 | - //关闭事务 | |
| 414 | 632 | _db.CommitTran(); |
| 415 | 633 | } |
| 416 | 634 | catch (Exception) |
| 417 | 635 | { |
| 418 | - //回滚事务 | |
| 419 | 636 | _db.RollbackTran(); |
| 420 | 637 | throw; |
| 421 | 638 | } |
| 422 | 639 | } |
| 423 | 640 | |
| 424 | 641 | /// <summary> |
| 425 | - /// 拒绝审批 | |
| 642 | + /// 审批操作(通过/不通过/退回) | |
| 426 | 643 | /// </summary> |
| 427 | 644 | /// <param name="id">申请编号</param> |
| 645 | + /// <param name="result">审批结果:通过/不通过/退回</param> | |
| 646 | + /// <param name="opinion">审批意见</param> | |
| 428 | 647 | /// <returns></returns> |
| 429 | - [HttpPost("{id}/Actions/Reject")] | |
| 430 | - public async Task Reject(string id) | |
| 648 | + [HttpPost("{id}/Actions/Approve")] | |
| 649 | + public async Task Approve(string id, [FromQuery] string result, [FromQuery] string opinion = "") | |
| 431 | 650 | { |
| 432 | 651 | var userInfo = await _userManager.GetUserInfo(); |
| 433 | 652 | var entity = await _db.Queryable<LqReimbursementApplicationEntity>().FirstAsync(p => p.Id == id); |
| 434 | 653 | _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); |
| 435 | 654 | |
| 655 | + if (entity.CurrentNodeOrder == null || entity.CurrentNodeOrder == 0) | |
| 656 | + { | |
| 657 | + throw new Exception("该申请尚未提交审批"); | |
| 658 | + } | |
| 659 | + | |
| 660 | + if (entity.ApprovalStatus != "审批中") | |
| 661 | + { | |
| 662 | + throw new Exception($"该申请当前状态为{entity.ApprovalStatus},无法进行审批操作"); | |
| 663 | + } | |
| 664 | + | |
| 665 | + // 验证当前用户是否有审批权限(管理员可以审批所有节点) | |
| 666 | + var isAdmin = userInfo.isAdministrator; | |
| 667 | + if (!isAdmin) | |
| 668 | + { | |
| 669 | + var hasPermission = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 670 | + .Where(x => x.ApplicationId == id | |
| 671 | + && x.NodeOrder == entity.CurrentNodeOrder | |
| 672 | + && x.UserId == userInfo.userId) | |
| 673 | + .AnyAsync(); | |
| 674 | + | |
| 675 | + if (!hasPermission) | |
| 676 | + { | |
| 677 | + throw new Exception("您没有当前节点的审批权限"); | |
| 678 | + } | |
| 679 | + } | |
| 680 | + | |
| 681 | + // 检查是否已经审批过 | |
| 682 | + var hasApproved = await _db.Queryable<LqReimbursementApprovalRecordEntity>() | |
| 683 | + .Where(x => x.ApplicationId == id | |
| 684 | + && x.NodeOrder == entity.CurrentNodeOrder | |
| 685 | + && x.ApproverId == userInfo.userId | |
| 686 | + && x.ApprovalResult != "待审批") | |
| 687 | + .AnyAsync(); | |
| 688 | + | |
| 689 | + if (hasApproved) | |
| 690 | + { | |
| 691 | + throw new Exception("您已经审批过该节点"); | |
| 692 | + } | |
| 693 | + | |
| 436 | 694 | try |
| 437 | 695 | { |
| 438 | - //开启事务 | |
| 439 | 696 | _db.BeginTran(); |
| 440 | 697 | |
| 441 | - // 更新申请表的审批状态 | |
| 442 | - entity.ApproveStatus = "未通过"; | |
| 443 | - entity.ApproveTime = DateTime.Now; | |
| 444 | - entity.ApproveUser = userInfo.userId; | |
| 445 | - await _db.Updateable(entity).ExecuteCommandAsync(); | |
| 698 | + // 获取当前节点信息 | |
| 699 | + var currentNode = await _db.Queryable<LqReimbursementApplicationNodeEntity>() | |
| 700 | + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) | |
| 701 | + .FirstAsync(); | |
| 702 | + | |
| 703 | + if (currentNode == null) | |
| 704 | + { | |
| 705 | + throw new Exception("未找到当前节点配置"); | |
| 706 | + } | |
| 446 | 707 | |
| 447 | - // 更新关联的购买记录 | |
| 448 | - if (!string.IsNullOrEmpty(entity.PurchaseRecordsId)) | |
| 708 | + // 更新待审批记录为已审批(如果存在待审批记录) | |
| 709 | + var existingRecord = await _db.Queryable<LqReimbursementApprovalRecordEntity>() | |
| 710 | + .Where(x => x.ApplicationId == id | |
| 711 | + && x.NodeOrder == entity.CurrentNodeOrder | |
| 712 | + && x.ApproverId == userInfo.userId | |
| 713 | + && x.ApprovalResult == "待审批") | |
| 714 | + .FirstAsync(); | |
| 715 | + | |
| 716 | + if (existingRecord != null) | |
| 717 | + { | |
| 718 | + // 更新现有记录 | |
| 719 | + existingRecord.ApprovalResult = result; | |
| 720 | + existingRecord.ApprovalOpinion = opinion; | |
| 721 | + existingRecord.ApprovalTime = DateTime.Now; | |
| 722 | + await _db.Updateable(existingRecord).ExecuteCommandAsync(); | |
| 723 | + } | |
| 724 | + else | |
| 449 | 725 | { |
| 450 | - var purchaseIds = entity.PurchaseRecordsId.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToList(); | |
| 451 | - if (purchaseIds.Count > 0) | |
| 726 | + // 创建新记录(如果不存在) | |
| 727 | + var approvalRecord = new LqReimbursementApprovalRecordEntity | |
| 452 | 728 | { |
| 453 | - await _db.Updateable<LqPurchaseRecordsEntity>() | |
| 454 | - .SetColumns(it => new LqPurchaseRecordsEntity | |
| 455 | - { | |
| 456 | - ApproveStatus = "未通过", | |
| 457 | - ApproveTime = DateTime.Now, | |
| 458 | - ApproveUser = userInfo.userId | |
| 459 | - }) | |
| 460 | - .Where(it => purchaseIds.Contains(it.Id)) | |
| 729 | + Id = YitIdHelper.NextId().ToString(), | |
| 730 | + ApplicationId = id, | |
| 731 | + NodeId = currentNode.Id, | |
| 732 | + NodeOrder = entity.CurrentNodeOrder.Value, | |
| 733 | + ApproverId = userInfo.userId, | |
| 734 | + ApproverName = userInfo.userName, | |
| 735 | + ApprovalResult = result, | |
| 736 | + ApprovalOpinion = opinion, | |
| 737 | + ApprovalTime = DateTime.Now, | |
| 738 | + IsCurrentNode = 1 | |
| 739 | + }; | |
| 740 | + await _db.Insertable(approvalRecord).ExecuteCommandAsync(); | |
| 741 | + } | |
| 742 | + | |
| 743 | + // 根据审批结果处理 | |
| 744 | + if (result == "不通过") | |
| 745 | + { | |
| 746 | + // 不通过:审批结束 | |
| 747 | + entity.ApprovalStatus = "未通过"; | |
| 748 | + await _db.Updateable(entity).ExecuteCommandAsync(); | |
| 749 | + | |
| 750 | + // 更新所有购买记录状态为"未通过"(通过ApplicationId关联更新) | |
| 751 | + await _db.Updateable<LqPurchaseRecordsEntity>() | |
| 752 | + .SetColumns(it => new LqPurchaseRecordsEntity | |
| 753 | + { | |
| 754 | + ApproveStatus = "未通过", | |
| 755 | + ApproveTime = DateTime.Now, | |
| 756 | + ApproveUser = userInfo.userId | |
| 757 | + }) | |
| 758 | + .Where(it => it.ApplicationId == id) | |
| 759 | + .ExecuteCommandAsync(); | |
| 760 | + } | |
| 761 | + else if (result == "退回") | |
| 762 | + { | |
| 763 | + // 退回:退回到上一节点 | |
| 764 | + if (entity.CurrentNodeOrder == 1) | |
| 765 | + { | |
| 766 | + // 退回到申请人 | |
| 767 | + entity.CurrentNodeOrder = 0; | |
| 768 | + entity.ApprovalStatus = "已退回"; | |
| 769 | + entity.ReturnedNodeOrder = 0; | |
| 770 | + entity.ReturnedReason = opinion; | |
| 771 | + entity.CurrentNodeId = null; | |
| 772 | + } | |
| 773 | + else | |
| 774 | + { | |
| 775 | + // 退回到上一节点 | |
| 776 | + entity.CurrentNodeOrder -= 1; | |
| 777 | + entity.ReturnedNodeOrder = entity.CurrentNodeOrder; | |
| 778 | + entity.ReturnedReason = opinion; | |
| 779 | + | |
| 780 | + // 获取上一节点信息 | |
| 781 | + var prevNode = await _db.Queryable<LqReimbursementApplicationNodeEntity>() | |
| 782 | + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) | |
| 783 | + .FirstAsync(); | |
| 784 | + | |
| 785 | + if (prevNode != null) | |
| 786 | + { | |
| 787 | + entity.CurrentNodeId = prevNode.Id; | |
| 788 | + } | |
| 789 | + | |
| 790 | + // 清除上一节点的所有审批记录(重新审批) | |
| 791 | + await _db.Deleteable<LqReimbursementApprovalRecordEntity>() | |
| 792 | + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) | |
| 461 | 793 | .ExecuteCommandAsync(); |
| 794 | + | |
| 795 | + // 为上一节点的每个审批人创建新的待审批记录 | |
| 796 | + var prevNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 797 | + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) | |
| 798 | + .ToListAsync(); | |
| 799 | + | |
| 800 | + foreach (var approver in prevNodeApprovers) | |
| 801 | + { | |
| 802 | + var record = new LqReimbursementApprovalRecordEntity | |
| 803 | + { | |
| 804 | + Id = YitIdHelper.NextId().ToString(), | |
| 805 | + ApplicationId = id, | |
| 806 | + NodeId = prevNode.Id, | |
| 807 | + NodeOrder = entity.CurrentNodeOrder.Value, | |
| 808 | + ApproverId = approver.UserId, | |
| 809 | + ApproverName = approver.UserName, | |
| 810 | + ApprovalResult = "待审批", | |
| 811 | + IsCurrentNode = 1, | |
| 812 | + ApprovalTime = null | |
| 813 | + }; | |
| 814 | + await _db.Insertable(record).ExecuteCommandAsync(); | |
| 815 | + } | |
| 816 | + } | |
| 817 | + | |
| 818 | + await _db.Updateable(entity).ExecuteCommandAsync(); | |
| 819 | + } | |
| 820 | + else if (result == "通过") | |
| 821 | + { | |
| 822 | + // 通过:判断是否需要进入下一个节点 | |
| 823 | + bool shouldMoveToNext = false; | |
| 824 | + | |
| 825 | + if (currentNode.ApprovalType == "或签") | |
| 826 | + { | |
| 827 | + // 或签:任意一个审批人通过,立即进入下一个节点 | |
| 828 | + shouldMoveToNext = true; | |
| 829 | + } | |
| 830 | + else // 会签 | |
| 831 | + { | |
| 832 | + // 会签:检查是否所有审批人都已通过 | |
| 833 | + var approvers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 834 | + .Where(x => x.ApplicationId == id && x.NodeOrder == entity.CurrentNodeOrder) | |
| 835 | + .Select(x => x.UserId) | |
| 836 | + .ToListAsync(); | |
| 837 | + | |
| 838 | + var approvedUsers = await _db.Queryable<LqReimbursementApprovalRecordEntity>() | |
| 839 | + .Where(x => x.ApplicationId == id | |
| 840 | + && x.NodeOrder == entity.CurrentNodeOrder | |
| 841 | + && x.ApprovalResult == "通过") | |
| 842 | + .Select(x => x.ApproverId) | |
| 843 | + .Distinct() | |
| 844 | + .ToListAsync(); | |
| 845 | + | |
| 846 | + if (approvers.Count == approvedUsers.Count) | |
| 847 | + { | |
| 848 | + // 所有人都已通过 | |
| 849 | + shouldMoveToNext = true; | |
| 850 | + } | |
| 851 | + } | |
| 852 | + | |
| 853 | + if (shouldMoveToNext) | |
| 854 | + { | |
| 855 | + // 进入下一个节点或完成审批 | |
| 856 | + await MoveToNextNode(id, userInfo.userId); | |
| 462 | 857 | } |
| 463 | 858 | } |
| 464 | 859 | |
| 465 | - //关闭事务 | |
| 466 | 860 | _db.CommitTran(); |
| 467 | 861 | } |
| 468 | 862 | catch (Exception) |
| 469 | 863 | { |
| 470 | - //回滚事务 | |
| 471 | 864 | _db.RollbackTran(); |
| 472 | 865 | throw; |
| 473 | 866 | } |
| 474 | 867 | } |
| 868 | + | |
| 869 | + /// <summary> | |
| 870 | + /// 进入下一个节点 | |
| 871 | + /// </summary> | |
| 872 | + private async Task MoveToNextNode(string applicationId, string approveUserId) | |
| 873 | + { | |
| 874 | + var entity = await _db.Queryable<LqReimbursementApplicationEntity>() | |
| 875 | + .FirstAsync(x => x.Id == applicationId); | |
| 876 | + | |
| 877 | + // 判断是否是最后一个节点 | |
| 878 | + if (entity.CurrentNodeOrder >= entity.NodeCount) | |
| 879 | + { | |
| 880 | + // 审批完成 | |
| 881 | + entity.CurrentNodeOrder = entity.NodeCount + 1; | |
| 882 | + entity.ApprovalStatus = "已通过"; | |
| 883 | + entity.CurrentNodeId = null; | |
| 884 | + await _db.Updateable(entity).ExecuteCommandAsync(); | |
| 885 | + | |
| 886 | + // 更新所有购买记录状态为"已审批" | |
| 887 | + // 通过ApplicationId关联更新,因为PurchaseRecordsId可能为空 | |
| 888 | + await _db.Updateable<LqPurchaseRecordsEntity>() | |
| 889 | + .SetColumns(it => new LqPurchaseRecordsEntity | |
| 890 | + { | |
| 891 | + ApproveStatus = "已审批", | |
| 892 | + ApproveTime = DateTime.Now, | |
| 893 | + ApproveUser = approveUserId | |
| 894 | + }) | |
| 895 | + .Where(it => it.ApplicationId == applicationId) | |
| 896 | + .ExecuteCommandAsync(); | |
| 897 | + } | |
| 898 | + else | |
| 899 | + { | |
| 900 | + // 进入下一个节点 | |
| 901 | + entity.CurrentNodeOrder += 1; | |
| 902 | + | |
| 903 | + // 获取下一个节点信息 | |
| 904 | + var nextNode = await _db.Queryable<LqReimbursementApplicationNodeEntity>() | |
| 905 | + .Where(x => x.ApplicationId == applicationId && x.NodeOrder == entity.CurrentNodeOrder) | |
| 906 | + .FirstAsync(); | |
| 907 | + | |
| 908 | + if (nextNode == null) | |
| 909 | + { | |
| 910 | + throw new Exception($"未找到节点{entity.CurrentNodeOrder}的配置"); | |
| 911 | + } | |
| 912 | + | |
| 913 | + entity.CurrentNodeId = nextNode.Id; | |
| 914 | + entity.ApprovalStatus = "审批中"; | |
| 915 | + await _db.Updateable(entity).ExecuteCommandAsync(); | |
| 916 | + | |
| 917 | + // 清除之前的当前节点标记 | |
| 918 | + await _db.Updateable<LqReimbursementApprovalRecordEntity>() | |
| 919 | + .SetColumns(it => new LqReimbursementApprovalRecordEntity { IsCurrentNode = 0 }) | |
| 920 | + .Where(it => it.ApplicationId == applicationId) | |
| 921 | + .ExecuteCommandAsync(); | |
| 922 | + | |
| 923 | + // 为下一个节点的每个审批人创建待审批记录 | |
| 924 | + var nextNodeApprovers = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 925 | + .Where(x => x.ApplicationId == applicationId && x.NodeOrder == entity.CurrentNodeOrder) | |
| 926 | + .ToListAsync(); | |
| 927 | + | |
| 928 | + foreach (var approver in nextNodeApprovers) | |
| 929 | + { | |
| 930 | + var record = new LqReimbursementApprovalRecordEntity | |
| 931 | + { | |
| 932 | + Id = YitIdHelper.NextId().ToString(), | |
| 933 | + ApplicationId = applicationId, | |
| 934 | + NodeId = nextNode.Id, | |
| 935 | + NodeOrder = entity.CurrentNodeOrder.Value, | |
| 936 | + ApproverId = approver.UserId, | |
| 937 | + ApproverName = approver.UserName, | |
| 938 | + ApprovalResult = "待审批", | |
| 939 | + IsCurrentNode = 1, | |
| 940 | + ApprovalTime = null | |
| 941 | + }; | |
| 942 | + await _db.Insertable(record).ExecuteCommandAsync(); | |
| 943 | + } | |
| 944 | + } | |
| 945 | + } | |
| 946 | + | |
| 947 | + /// <summary> | |
| 948 | + /// 获取所有待办列表(管理员用,所有待审批的申请) | |
| 949 | + /// </summary> | |
| 950 | + /// <param name="input">查询参数</param> | |
| 951 | + /// <returns></returns> | |
| 952 | + [HttpGet("Actions/AllPendingApproval")] | |
| 953 | + public async Task<dynamic> GetAllPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input) | |
| 954 | + { | |
| 955 | + var sidx = input.sidx == null ? "id" : input.sidx; | |
| 956 | + | |
| 957 | + // 管理员可以查看所有待审批的申请 | |
| 958 | + var query = _db.Queryable<LqReimbursementApplicationEntity>() | |
| 959 | + .Where(x => x.ApprovalStatus == "审批中") | |
| 960 | + .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId)) | |
| 961 | + .OrderBy(sidx + " " + input.sort); | |
| 962 | + | |
| 963 | + var total = await query.CountAsync(); | |
| 964 | + var entities = await query.ToPageListAsync(input.currentPage, input.pageSize); | |
| 965 | + | |
| 966 | + // 获取当前审批人信息 | |
| 967 | + var applicationIds = entities.Select(x => x.Id).ToList(); | |
| 968 | + var currentApprovers = new List<dynamic>(); | |
| 969 | + if (applicationIds.Any()) | |
| 970 | + { | |
| 971 | + var approverList = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 972 | + .Where(x => applicationIds.Contains(x.ApplicationId)) | |
| 973 | + .InnerJoin<LqReimbursementApplicationEntity>((u, a) => u.ApplicationId == a.Id && u.NodeOrder == a.CurrentNodeOrder) | |
| 974 | + .Select((u, a) => new | |
| 975 | + { | |
| 976 | + applicationId = a.Id, | |
| 977 | + approverName = u.UserName | |
| 978 | + }) | |
| 979 | + .ToListAsync(); | |
| 980 | + currentApprovers = approverList.Cast<dynamic>().ToList(); | |
| 981 | + } | |
| 982 | + | |
| 983 | + var approverDict = currentApprovers | |
| 984 | + .GroupBy(x => (string)x.applicationId) | |
| 985 | + .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName))); | |
| 986 | + | |
| 987 | + // 组装返回数据 | |
| 988 | + var result = entities.Select(item => new LqReimbursementApplicationListOutput | |
| 989 | + { | |
| 990 | + id = item.Id, | |
| 991 | + applicationUserId = item.ApplicationUserId, | |
| 992 | + applicationUserName = item.ApplicationUserName, | |
| 993 | + applicationStoreId = item.ApplicationStoreId, | |
| 994 | + applicationTime = item.ApplicationTime, | |
| 995 | + amount = item.Amount, | |
| 996 | + approveUser = item.ApproveUser, | |
| 997 | + approveStatus = item.ApprovalStatus ?? item.ApproveStatus, | |
| 998 | + approveTime = item.ApproveTime, | |
| 999 | + purchaseRecordsId = item.PurchaseRecordsId, | |
| 1000 | + currentApprovers = approverDict.ContainsKey(item.Id) ? approverDict[item.Id] : null, | |
| 1001 | + currentNodeOrder = item.CurrentNodeOrder, | |
| 1002 | + nodeCount = item.NodeCount | |
| 1003 | + }).ToList(); | |
| 1004 | + | |
| 1005 | + return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult( | |
| 1006 | + new SqlSugarPagedList<LqReimbursementApplicationListOutput> | |
| 1007 | + { | |
| 1008 | + list = result, | |
| 1009 | + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total } | |
| 1010 | + }); | |
| 1011 | + } | |
| 1012 | + | |
| 1013 | + /// <summary> | |
| 1014 | + /// 获取待审批列表(当前用户作为审批人的申请) | |
| 1015 | + /// </summary> | |
| 1016 | + /// <param name="input">查询参数</param> | |
| 1017 | + /// <returns></returns> | |
| 1018 | + [HttpGet("Actions/PendingApproval")] | |
| 1019 | + public async Task<dynamic> GetPendingApprovalList([FromQuery] LqReimbursementApplicationListQueryInput input) | |
| 1020 | + { | |
| 1021 | + var userInfo = await _userManager.GetUserInfo(); | |
| 1022 | + var sidx = input.sidx == null ? "id" : input.sidx; | |
| 1023 | + | |
| 1024 | + // 查询当前用户作为审批人的节点 | |
| 1025 | + var userNodeOrders = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 1026 | + .Where(x => x.UserId == userInfo.userId) | |
| 1027 | + .Select(x => new { x.ApplicationId, x.NodeOrder }) | |
| 1028 | + .ToListAsync(); | |
| 1029 | + | |
| 1030 | + if (!userNodeOrders.Any()) | |
| 1031 | + { | |
| 1032 | + return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult( | |
| 1033 | + new SqlSugarPagedList<LqReimbursementApplicationListOutput> | |
| 1034 | + { | |
| 1035 | + list = new List<LqReimbursementApplicationListOutput>(), | |
| 1036 | + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = 0 } | |
| 1037 | + }); | |
| 1038 | + } | |
| 1039 | + | |
| 1040 | + // 获取用户有权限的申请ID和节点顺序 | |
| 1041 | + var userApplications = userNodeOrders | |
| 1042 | + .GroupBy(x => x.ApplicationId) | |
| 1043 | + .ToDictionary(g => g.Key, g => g.Select(x => x.NodeOrder).ToList()); | |
| 1044 | + | |
| 1045 | + var applicationIds = userApplications.Keys.ToList(); | |
| 1046 | + | |
| 1047 | + // 查询这些申请中,当前节点是用户有权限的节点,且状态为"审批中"的申请 | |
| 1048 | + var query = _db.Queryable<LqReimbursementApplicationEntity>() | |
| 1049 | + .Where(x => applicationIds.Contains(x.Id) && x.ApprovalStatus == "审批中") | |
| 1050 | + .Where(x => userApplications.ContainsKey(x.Id) | |
| 1051 | + && userApplications[x.Id].Contains(x.CurrentNodeOrder ?? 0)) | |
| 1052 | + .WhereIF(!string.IsNullOrEmpty(input.applicationStoreId), p => p.ApplicationStoreId.Contains(input.applicationStoreId)) | |
| 1053 | + .OrderBy(sidx + " " + input.sort); | |
| 1054 | + | |
| 1055 | + var total = await query.CountAsync(); | |
| 1056 | + var entities = await query.ToPageListAsync(input.currentPage, input.pageSize); | |
| 1057 | + | |
| 1058 | + // 获取当前审批人信息 | |
| 1059 | + var resultApplicationIds = entities.Select(x => x.Id).ToList(); | |
| 1060 | + var currentApprovers = new List<dynamic>(); | |
| 1061 | + if (resultApplicationIds.Any()) | |
| 1062 | + { | |
| 1063 | + var approverList = await _db.Queryable<LqReimbursementApplicationNodeUserEntity>() | |
| 1064 | + .Where(x => resultApplicationIds.Contains(x.ApplicationId)) | |
| 1065 | + .InnerJoin<LqReimbursementApplicationEntity>((u, a) => u.ApplicationId == a.Id && u.NodeOrder == a.CurrentNodeOrder) | |
| 1066 | + .Select((u, a) => new | |
| 1067 | + { | |
| 1068 | + applicationId = a.Id, | |
| 1069 | + approverName = u.UserName | |
| 1070 | + }) | |
| 1071 | + .ToListAsync(); | |
| 1072 | + currentApprovers = approverList.Cast<dynamic>().ToList(); | |
| 1073 | + } | |
| 1074 | + | |
| 1075 | + var approverDict = currentApprovers | |
| 1076 | + .GroupBy(x => (string)x.applicationId) | |
| 1077 | + .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName))); | |
| 1078 | + | |
| 1079 | + // 组装返回数据 | |
| 1080 | + var result = entities.Select(item => new LqReimbursementApplicationListOutput | |
| 1081 | + { | |
| 1082 | + id = item.Id, | |
| 1083 | + applicationUserId = item.ApplicationUserId, | |
| 1084 | + applicationUserName = item.ApplicationUserName, | |
| 1085 | + applicationStoreId = item.ApplicationStoreId, | |
| 1086 | + applicationTime = item.ApplicationTime, | |
| 1087 | + amount = item.Amount, | |
| 1088 | + approveUser = item.ApproveUser, | |
| 1089 | + approveStatus = item.ApprovalStatus ?? item.ApproveStatus, | |
| 1090 | + approveTime = item.ApproveTime, | |
| 1091 | + purchaseRecordsId = item.PurchaseRecordsId, | |
| 1092 | + currentApprovers = approverDict.ContainsKey(item.Id) ? approverDict[item.Id] : null, | |
| 1093 | + currentNodeOrder = item.CurrentNodeOrder, | |
| 1094 | + nodeCount = item.NodeCount | |
| 1095 | + }).ToList(); | |
| 1096 | + | |
| 1097 | + return PageResult<LqReimbursementApplicationListOutput>.SqlSugarPageResult( | |
| 1098 | + new SqlSugarPagedList<LqReimbursementApplicationListOutput> | |
| 1099 | + { | |
| 1100 | + list = result, | |
| 1101 | + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total } | |
| 1102 | + }); | |
| 1103 | + } | |
| 1104 | + | |
| 1105 | + /// <summary> | |
| 1106 | + /// 获取审批历史 | |
| 1107 | + /// </summary> | |
| 1108 | + /// <param name="id">申请编号</param> | |
| 1109 | + /// <returns></returns> | |
| 1110 | + [HttpGet("{id}/Actions/ApprovalHistory")] | |
| 1111 | + public async Task<dynamic> GetApprovalHistory(string id) | |
| 1112 | + { | |
| 1113 | + // 先查询审批记录 | |
| 1114 | + var approvalRecords = await _db.Queryable<LqReimbursementApprovalRecordEntity>() | |
| 1115 | + .Where(x => x.ApplicationId == id) | |
| 1116 | + .OrderBy(x => x.NodeOrder) | |
| 1117 | + .OrderBy(x => x.ApprovalTime) | |
| 1118 | + .ToListAsync(); | |
| 1119 | + | |
| 1120 | + // 获取节点信息 | |
| 1121 | + if (approvalRecords.Any()) | |
| 1122 | + { | |
| 1123 | + var nodeIds = approvalRecords.Select(x => x.NodeId).Distinct().ToList(); | |
| 1124 | + var nodes = await _db.Queryable<LqReimbursementApplicationNodeEntity>() | |
| 1125 | + .Where(x => nodeIds.Contains(x.Id)) | |
| 1126 | + .ToListAsync(); | |
| 1127 | + | |
| 1128 | + var nodeDict = nodes.ToDictionary(x => x.Id, x => x.NodeName); | |
| 1129 | + | |
| 1130 | + // 组装结果 | |
| 1131 | + var records = approvalRecords.Select(r => new | |
| 1132 | + { | |
| 1133 | + nodeOrder = r.NodeOrder, | |
| 1134 | + nodeName = nodeDict.ContainsKey(r.NodeId) ? nodeDict[r.NodeId] : null, | |
| 1135 | + approverName = r.ApproverName, | |
| 1136 | + approvalResult = r.ApprovalResult, | |
| 1137 | + approvalOpinion = r.ApprovalOpinion, | |
| 1138 | + approvalTime = r.ApprovalTime | |
| 1139 | + }).ToList(); | |
| 1140 | + | |
| 1141 | + return records; | |
| 1142 | + } | |
| 1143 | + | |
| 1144 | + return new List<object>(); | |
| 1145 | + } | |
| 475 | 1146 | } |
| 476 | 1147 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
0 → 100644
| 1 | +using Microsoft.AspNetCore.Authorization; | |
| 2 | +using Microsoft.AspNetCore.Http; | |
| 3 | +using Microsoft.AspNetCore.Mvc; | |
| 4 | +using NCC.Common.Enum; | |
| 5 | +using NCC.Common.Filter; | |
| 6 | +using NCC.Common.Helper; | |
| 7 | +using NCC.Dependency; | |
| 8 | +using NCC.DynamicApiController; | |
| 9 | +using NCC.Extend.Entitys.Dto.LqSalaryExtraCalculation; | |
| 10 | +using Yitter.IdGenerator; | |
| 11 | +using NCC.Extend.Entitys.lq_salary_extra_calculation; | |
| 12 | +using NCC.Extend.Entitys.lq_md_xdbhsj; | |
| 13 | +using NCC.System.Entitys.Permission; | |
| 14 | +using SqlSugar; | |
| 15 | +using System; | |
| 16 | +using System.Collections.Generic; | |
| 17 | +using System.IO; | |
| 18 | +using System.Linq; | |
| 19 | +using System.Threading.Tasks; | |
| 20 | +using NCC.Common.Core.Manager; | |
| 21 | +using NCC.FriendlyException; | |
| 22 | + | |
| 23 | +namespace NCC.Extend | |
| 24 | +{ | |
| 25 | + /// <summary> | |
| 26 | + /// 健康师工资额外计算服务 | |
| 27 | + /// </summary> | |
| 28 | + [ApiDescriptionSettings(Tag = "健康师工资额外计算", Name = "LqSalaryExtraCalculation", Order = 301)] | |
| 29 | + [Route("api/Extend/[controller]")] | |
| 30 | + public class LqSalaryExtraCalculationService : IDynamicApiController, ITransient | |
| 31 | + { | |
| 32 | + private readonly ISqlSugarClient _db; | |
| 33 | + private readonly IUserManager _userManager; | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 初始化一个<see cref="LqSalaryExtraCalculationService"/>类型的新实例 | |
| 37 | + /// </summary> | |
| 38 | + public LqSalaryExtraCalculationService(ISqlSugarClient db, IUserManager userManager) | |
| 39 | + { | |
| 40 | + _db = db; | |
| 41 | + _userManager = userManager; | |
| 42 | + } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 获取健康师工资额外计算列表 | |
| 46 | + /// </summary> | |
| 47 | + /// <param name="input">查询参数</param> | |
| 48 | + /// <returns>健康师工资额外计算分页列表</returns> | |
| 49 | + [HttpGet] | |
| 50 | + public async Task<dynamic> GetList([FromQuery] SalaryExtraCalculationInput input) | |
| 51 | + { | |
| 52 | + var query = _db.Queryable<LqSalaryExtraCalculationEntity, UserEntity>((ec, u) => new JoinQueryInfos( | |
| 53 | + JoinType.Left, u.Id == SqlFunc.ToString(ec.EmployeeId))) | |
| 54 | + .Where((ec, u) => u.Id != null && (u.DeleteMark == null || u.DeleteMark == 0)); | |
| 55 | + | |
| 56 | + // 年份筛选 | |
| 57 | + if (input.Year.HasValue) | |
| 58 | + { | |
| 59 | + query = query.Where((ec, u) => ec.Year == input.Year.Value); | |
| 60 | + } | |
| 61 | + | |
| 62 | + // 月份筛选 | |
| 63 | + if (input.Month.HasValue) | |
| 64 | + { | |
| 65 | + query = query.Where((ec, u) => ec.Month == input.Month.Value); | |
| 66 | + } | |
| 67 | + | |
| 68 | + // 健康师ID筛选 | |
| 69 | + if (!string.IsNullOrEmpty(input.EmployeeId)) | |
| 70 | + { | |
| 71 | + query = query.Where((ec, u) => ec.EmployeeId == input.EmployeeId); | |
| 72 | + } | |
| 73 | + | |
| 74 | + // 关键词搜索(姓名或电话) | |
| 75 | + if (!string.IsNullOrEmpty(input.Keyword)) | |
| 76 | + { | |
| 77 | + query = query.Where((ec, u) => u.RealName.Contains(input.Keyword) || u.MobilePhone.Contains(input.Keyword)); | |
| 78 | + } | |
| 79 | + | |
| 80 | + var data = await query.Select((ec, u) => new SalaryExtraCalculationOutput | |
| 81 | + { | |
| 82 | + Id = ec.Id, | |
| 83 | + EmployeeId = ec.EmployeeId, | |
| 84 | + EmployeeName = u.RealName, | |
| 85 | + EmployeePhone = u.MobilePhone, | |
| 86 | + Year = ec.Year, | |
| 87 | + Month = ec.Month, | |
| 88 | + BaseRewardPerformance = ec.BaseRewardPerformance, | |
| 89 | + CooperationRewardPerformance = ec.CooperationRewardPerformance, | |
| 90 | + NewCustomerPerformance = ec.NewCustomerPerformance, | |
| 91 | + NewCustomerConversionRate = ec.NewCustomerConversionRate, | |
| 92 | + UpgradePerformance = ec.UpgradePerformance, | |
| 93 | + UpgradeConversionRate = ec.UpgradeConversionRate, | |
| 94 | + UpgradeCustomerCount = ec.UpgradeCustomerCount, | |
| 95 | + OtherPerformanceAdd = ec.OtherPerformanceAdd, | |
| 96 | + OtherPerformanceSubtract = ec.OtherPerformanceSubtract | |
| 97 | + }) | |
| 98 | + .MergeTable() | |
| 99 | + .OrderByIF(!string.IsNullOrEmpty(input.sidx), input.sidx + " " + input.sort) | |
| 100 | + .ToPagedListAsync(input.currentPage, input.pageSize); | |
| 101 | + | |
| 102 | + return PageResult<SalaryExtraCalculationOutput>.SqlSugarPageResult(data); | |
| 103 | + } | |
| 104 | + | |
| 105 | + /// <summary> | |
| 106 | + /// 从Excel导入健康师工资额外计算数据 | |
| 107 | + /// </summary> | |
| 108 | + /// <param name="file">Excel文件</param> | |
| 109 | + /// <returns>导入结果</returns> | |
| 110 | + [HttpPost("ImportFromExcel")] | |
| 111 | + public async Task<dynamic> ImportFromExcel(IFormFile file) | |
| 112 | + { | |
| 113 | + try | |
| 114 | + { | |
| 115 | + if (file == null || file.Length == 0) | |
| 116 | + { | |
| 117 | + throw NCCException.Oh("请选择要上传的Excel文件"); | |
| 118 | + } | |
| 119 | + | |
| 120 | + // 检查文件格式 | |
| 121 | + var allowedExtensions = new[] { ".xlsx", ".xls" }; | |
| 122 | + var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant(); | |
| 123 | + if (!allowedExtensions.Contains(fileExtension)) | |
| 124 | + { | |
| 125 | + throw NCCException.Oh("只支持.xlsx和.xls格式的Excel文件"); | |
| 126 | + } | |
| 127 | + | |
| 128 | + var importData = new List<SalaryExtraCalculationImportInput>(); | |
| 129 | + | |
| 130 | + // 保存临时文件 | |
| 131 | + var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName)); | |
| 132 | + try | |
| 133 | + { | |
| 134 | + using (var stream = new FileStream(tempFilePath, FileMode.Create)) | |
| 135 | + { | |
| 136 | + await file.CopyToAsync(stream); | |
| 137 | + } | |
| 138 | + | |
| 139 | + // 使用ExcelImportHelper读取Excel文件 | |
| 140 | + // 参数说明:0表示第一个工作表,0表示第一行是标题行 | |
| 141 | + var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0); | |
| 142 | + | |
| 143 | + if (dataTable.Rows.Count == 0) | |
| 144 | + { | |
| 145 | + throw NCCException.Oh("Excel文件中没有数据行"); | |
| 146 | + } | |
| 147 | + | |
| 148 | + // Excel字段顺序:id, 健康师姓名, 健康师电话, 年份, 月份, 基础奖励业绩, 合作奖励业绩, 新客业绩, 新客成交率, 升单业绩, 升单成交率, 升单人头数, 其他业绩加, 其他业绩减 | |
| 149 | + // 从第1行开始读取数据(跳过标题行) | |
| 150 | + for (int i = 1; i < dataTable.Rows.Count; i++) | |
| 151 | + { | |
| 152 | + try | |
| 153 | + { | |
| 154 | + var row = dataTable.Rows[i]; | |
| 155 | + var id = row[0]?.ToString()?.Trim(); | |
| 156 | + var employeeName = row[1]?.ToString()?.Trim(); | |
| 157 | + var employeePhone = row[2]?.ToString()?.Trim(); | |
| 158 | + var yearText = row[3]?.ToString()?.Trim(); | |
| 159 | + var monthText = row[4]?.ToString()?.Trim(); | |
| 160 | + var baseRewardPerformanceText = row[5]?.ToString()?.Trim(); | |
| 161 | + var cooperationRewardPerformanceText = row[6]?.ToString()?.Trim(); | |
| 162 | + var newCustomerPerformanceText = row[7]?.ToString()?.Trim(); | |
| 163 | + var newCustomerConversionRateText = row[8]?.ToString()?.Trim(); | |
| 164 | + var upgradePerformanceText = row[9]?.ToString()?.Trim(); | |
| 165 | + var upgradeConversionRateText = row[10]?.ToString()?.Trim(); | |
| 166 | + var upgradeCustomerCountText = row[11]?.ToString()?.Trim(); | |
| 167 | + var otherPerformanceAddText = row[12]?.ToString()?.Trim(); | |
| 168 | + var otherPerformanceSubtractText = row[13]?.ToString()?.Trim(); | |
| 169 | + | |
| 170 | + // 跳过空行 | |
| 171 | + if (string.IsNullOrEmpty(employeeName) && string.IsNullOrEmpty(employeePhone)) | |
| 172 | + { | |
| 173 | + continue; | |
| 174 | + } | |
| 175 | + | |
| 176 | + // 验证必填字段 | |
| 177 | + if (string.IsNullOrEmpty(employeeName)) | |
| 178 | + { | |
| 179 | + throw new Exception($"第{i + 1}行:健康师姓名不能为空"); | |
| 180 | + } | |
| 181 | + if (string.IsNullOrEmpty(employeePhone)) | |
| 182 | + { | |
| 183 | + throw new Exception($"第{i + 1}行:健康师电话不能为空"); | |
| 184 | + } | |
| 185 | + | |
| 186 | + // 解析年份 | |
| 187 | + if (!int.TryParse(yearText, out int year)) | |
| 188 | + { | |
| 189 | + throw new Exception($"第{i + 1}行:年份格式错误"); | |
| 190 | + } | |
| 191 | + | |
| 192 | + // 解析月份 | |
| 193 | + if (!int.TryParse(monthText, out int month)) | |
| 194 | + { | |
| 195 | + throw new Exception($"第{i + 1}行:月份格式错误"); | |
| 196 | + } | |
| 197 | + | |
| 198 | + // 验证月份范围 | |
| 199 | + if (month < 1 || month > 12) | |
| 200 | + { | |
| 201 | + throw new Exception($"第{i + 1}行:月份必须在1-12之间"); | |
| 202 | + } | |
| 203 | + | |
| 204 | + // 解析数值字段(允许为空,默认为0) | |
| 205 | + decimal.TryParse(baseRewardPerformanceText, out decimal baseRewardPerformance); | |
| 206 | + decimal.TryParse(cooperationRewardPerformanceText, out decimal cooperationRewardPerformance); | |
| 207 | + decimal.TryParse(newCustomerPerformanceText, out decimal newCustomerPerformance); | |
| 208 | + decimal.TryParse(newCustomerConversionRateText, out decimal newCustomerConversionRate); | |
| 209 | + decimal.TryParse(upgradePerformanceText, out decimal upgradePerformance); | |
| 210 | + decimal.TryParse(upgradeConversionRateText, out decimal upgradeConversionRate); | |
| 211 | + decimal.TryParse(upgradeCustomerCountText, out decimal upgradeCustomerCount); | |
| 212 | + decimal.TryParse(otherPerformanceAddText, out decimal otherPerformanceAdd); | |
| 213 | + decimal.TryParse(otherPerformanceSubtractText, out decimal otherPerformanceSubtract); | |
| 214 | + | |
| 215 | + var item = new SalaryExtraCalculationImportInput | |
| 216 | + { | |
| 217 | + Id = id, | |
| 218 | + EmployeeName = employeeName, | |
| 219 | + EmployeePhone = employeePhone, | |
| 220 | + Year = year, | |
| 221 | + Month = month, | |
| 222 | + BaseRewardPerformance = baseRewardPerformance, | |
| 223 | + CooperationRewardPerformance = cooperationRewardPerformance, | |
| 224 | + NewCustomerPerformance = newCustomerPerformance, | |
| 225 | + NewCustomerConversionRate = newCustomerConversionRate, | |
| 226 | + UpgradePerformance = upgradePerformance, | |
| 227 | + UpgradeConversionRate = upgradeConversionRate, | |
| 228 | + UpgradeCustomerCount = upgradeCustomerCount, | |
| 229 | + OtherPerformanceAdd = otherPerformanceAdd, | |
| 230 | + OtherPerformanceSubtract = otherPerformanceSubtract | |
| 231 | + }; | |
| 232 | + | |
| 233 | + importData.Add(item); | |
| 234 | + } | |
| 235 | + catch (Exception ex) | |
| 236 | + { | |
| 237 | + throw new Exception($"第{i + 1}行数据解析失败: {ex.Message}"); | |
| 238 | + } | |
| 239 | + } | |
| 240 | + } | |
| 241 | + finally | |
| 242 | + { | |
| 243 | + // 清理临时文件 | |
| 244 | + if (File.Exists(tempFilePath)) | |
| 245 | + { | |
| 246 | + File.Delete(tempFilePath); | |
| 247 | + } | |
| 248 | + } | |
| 249 | + | |
| 250 | + if (!importData.Any()) | |
| 251 | + { | |
| 252 | + throw NCCException.Oh("Excel文件中没有有效的数据行"); | |
| 253 | + } | |
| 254 | + | |
| 255 | + // 处理导入数据 | |
| 256 | + return await ProcessImportData(importData); | |
| 257 | + } | |
| 258 | + catch (Exception ex) | |
| 259 | + { | |
| 260 | + throw NCCException.Oh($"上传Excel文件导入健康师工资额外计算数据失败: {ex.Message}"); | |
| 261 | + } | |
| 262 | + } | |
| 263 | + | |
| 264 | + #region 处理导入数据 | |
| 265 | + /// <summary> | |
| 266 | + /// 处理导入数据 | |
| 267 | + /// </summary> | |
| 268 | + /// <param name="importData">导入数据列表</param> | |
| 269 | + /// <returns>导入结果</returns> | |
| 270 | + private async Task<dynamic> ProcessImportData(List<SalaryExtraCalculationImportInput> importData) | |
| 271 | + { | |
| 272 | + var successCount = 0; | |
| 273 | + var failCount = 0; | |
| 274 | + var errorMessages = new List<string>(); | |
| 275 | + var entitiesToInsert = new List<LqSalaryExtraCalculationEntity>(); | |
| 276 | + var entitiesToUpdate = new List<LqSalaryExtraCalculationEntity>(); | |
| 277 | + | |
| 278 | + foreach (var item in importData) | |
| 279 | + { | |
| 280 | + try | |
| 281 | + { | |
| 282 | + // 1. 根据健康师姓名和电话查找用户ID | |
| 283 | + var user = await _db.Queryable<UserEntity>() | |
| 284 | + .Where(u => u.RealName == item.EmployeeName && u.MobilePhone == item.EmployeePhone) | |
| 285 | + .FirstAsync(); | |
| 286 | + | |
| 287 | + if (user == null) | |
| 288 | + { | |
| 289 | + errorMessages.Add($"健康师 {item.EmployeeName}({item.EmployeePhone}) 不存在"); | |
| 290 | + failCount++; | |
| 291 | + continue; | |
| 292 | + } | |
| 293 | + | |
| 294 | + // 2. 检查是否已存在相同记录(根据健康师ID、年份、月份) | |
| 295 | + LqSalaryExtraCalculationEntity existingRecord = null; | |
| 296 | + | |
| 297 | + // 如果提供了ID,先尝试根据ID查找 | |
| 298 | + if (!string.IsNullOrEmpty(item.Id)) | |
| 299 | + { | |
| 300 | + existingRecord = await _db.Queryable<LqSalaryExtraCalculationEntity>() | |
| 301 | + .Where(x => x.Id == item.Id) | |
| 302 | + .FirstAsync(); | |
| 303 | + } | |
| 304 | + | |
| 305 | + // 如果没有找到,则根据健康师ID、年份、月份查找 | |
| 306 | + if (existingRecord == null) | |
| 307 | + { | |
| 308 | + existingRecord = await _db.Queryable<LqSalaryExtraCalculationEntity>() | |
| 309 | + .Where(x => x.EmployeeId == user.Id && x.Year == item.Year && x.Month == item.Month) | |
| 310 | + .FirstAsync(); | |
| 311 | + } | |
| 312 | + | |
| 313 | + if (existingRecord != null) | |
| 314 | + { | |
| 315 | + // 更新现有记录 | |
| 316 | + existingRecord.BaseRewardPerformance = item.BaseRewardPerformance; | |
| 317 | + existingRecord.CooperationRewardPerformance = item.CooperationRewardPerformance; | |
| 318 | + existingRecord.NewCustomerPerformance = item.NewCustomerPerformance; | |
| 319 | + existingRecord.NewCustomerConversionRate = item.NewCustomerConversionRate; | |
| 320 | + existingRecord.UpgradePerformance = item.UpgradePerformance; | |
| 321 | + existingRecord.UpgradeConversionRate = item.UpgradeConversionRate; | |
| 322 | + existingRecord.UpgradeCustomerCount = item.UpgradeCustomerCount; | |
| 323 | + existingRecord.OtherPerformanceAdd = item.OtherPerformanceAdd; | |
| 324 | + existingRecord.OtherPerformanceSubtract = item.OtherPerformanceSubtract; | |
| 325 | + entitiesToUpdate.Add(existingRecord); | |
| 326 | + } | |
| 327 | + else | |
| 328 | + { | |
| 329 | + // 创建新记录 | |
| 330 | + var entity = new LqSalaryExtraCalculationEntity | |
| 331 | + { | |
| 332 | + Id = YitIdHelper.NextId().ToString(), | |
| 333 | + EmployeeId = user.Id, | |
| 334 | + Year = item.Year, | |
| 335 | + Month = item.Month, | |
| 336 | + BaseRewardPerformance = item.BaseRewardPerformance, | |
| 337 | + CooperationRewardPerformance = item.CooperationRewardPerformance, | |
| 338 | + NewCustomerPerformance = item.NewCustomerPerformance, | |
| 339 | + NewCustomerConversionRate = item.NewCustomerConversionRate, | |
| 340 | + UpgradePerformance = item.UpgradePerformance, | |
| 341 | + UpgradeConversionRate = item.UpgradeConversionRate, | |
| 342 | + UpgradeCustomerCount = item.UpgradeCustomerCount, | |
| 343 | + OtherPerformanceAdd = item.OtherPerformanceAdd, | |
| 344 | + OtherPerformanceSubtract = item.OtherPerformanceSubtract | |
| 345 | + }; | |
| 346 | + entitiesToInsert.Add(entity); | |
| 347 | + } | |
| 348 | + | |
| 349 | + successCount++; | |
| 350 | + } | |
| 351 | + catch (Exception ex) | |
| 352 | + { | |
| 353 | + errorMessages.Add($"健康师 {item.EmployeeName}({item.EmployeePhone}) 处理失败: {ex.Message}"); | |
| 354 | + failCount++; | |
| 355 | + } | |
| 356 | + } | |
| 357 | + | |
| 358 | + // 批量插入新记录 | |
| 359 | + if (entitiesToInsert.Any()) | |
| 360 | + { | |
| 361 | + await _db.Insertable(entitiesToInsert).ExecuteCommandAsync(); | |
| 362 | + } | |
| 363 | + | |
| 364 | + // 批量更新现有记录 | |
| 365 | + if (entitiesToUpdate.Any()) | |
| 366 | + { | |
| 367 | + await _db.Updateable(entitiesToUpdate).ExecuteCommandAsync(); | |
| 368 | + } | |
| 369 | + | |
| 370 | + var result = new | |
| 371 | + { | |
| 372 | + successCount, | |
| 373 | + failCount, | |
| 374 | + totalCount = importData.Count, | |
| 375 | + errorMessages = errorMessages.Take(50).ToList() // 最多返回50条错误信息 | |
| 376 | + }; | |
| 377 | + | |
| 378 | + if (failCount > 0) | |
| 379 | + { | |
| 380 | + throw NCCException.Oh($"导入完成,成功:{successCount}条,失败:{failCount}条。{string.Join("; ", errorMessages.Take(10))}"); | |
| 381 | + } | |
| 382 | + | |
| 383 | + return result; | |
| 384 | + } | |
| 385 | + #endregion | |
| 386 | + | |
| 387 | + /// <summary> | |
| 388 | + /// 为新店健康师生成模拟数据 | |
| 389 | + /// </summary> | |
| 390 | + /// <param name="year">年份</param> | |
| 391 | + /// <param name="month">月份</param> | |
| 392 | + /// <returns>生成结果</returns> | |
| 393 | + [HttpPost("GenerateMockData")] | |
| 394 | + public async Task<dynamic> GenerateMockData(int year, int month) | |
| 395 | + { | |
| 396 | + try | |
| 397 | + { | |
| 398 | + // 验证月份范围 | |
| 399 | + if (month < 1 || month > 12) | |
| 400 | + { | |
| 401 | + throw NCCException.Oh("月份必须在1-12之间"); | |
| 402 | + } | |
| 403 | + | |
| 404 | + var startDate = new DateTime(year, month, 1); | |
| 405 | + var endDate = startDate.AddMonths(1).AddDays(-1); | |
| 406 | + | |
| 407 | + // 1. 查询新店保护信息 | |
| 408 | + var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>() | |
| 409 | + .Where(x => x.Sfqy == 1) | |
| 410 | + .ToListAsync(); | |
| 411 | + | |
| 412 | + // 筛选出在统计月份处于保护期内的门店 | |
| 413 | + var newStoreIds = newStoreProtectionList | |
| 414 | + .Where(x => x.Bhkssj <= endDate && x.Bhjssj >= startDate) | |
| 415 | + .Select(x => x.Mdid) | |
| 416 | + .Distinct() | |
| 417 | + .ToList(); | |
| 418 | + | |
| 419 | + if (!newStoreIds.Any()) | |
| 420 | + { | |
| 421 | + throw NCCException.Oh("当前月份没有新店"); | |
| 422 | + } | |
| 423 | + | |
| 424 | + // 2. 查询新店下的健康师 | |
| 425 | + // 健康师的岗位字段是F_GW,值为"健康师" | |
| 426 | + var healthCoaches = await _db.Queryable<UserEntity>() | |
| 427 | + .Where(u => newStoreIds.Contains(u.Mdid) && u.Gw == "健康师" && u.EnabledMark == 1 && (u.DeleteMark == null || u.DeleteMark == 0)) | |
| 428 | + .Select(u => new { u.Id, u.RealName, u.Mdid }) | |
| 429 | + .ToListAsync(); | |
| 430 | + | |
| 431 | + if (!healthCoaches.Any()) | |
| 432 | + { | |
| 433 | + throw NCCException.Oh("新店下没有健康师"); | |
| 434 | + } | |
| 435 | + | |
| 436 | + // 3. 检查是否已存在数据 | |
| 437 | + var existingEmployeeIds = await _db.Queryable<LqSalaryExtraCalculationEntity>() | |
| 438 | + .Where(x => x.Year == year && x.Month == month) | |
| 439 | + .Select(x => x.EmployeeId) | |
| 440 | + .ToListAsync(); | |
| 441 | + | |
| 442 | + var employeesToProcess = healthCoaches | |
| 443 | + .Where(hc => !existingEmployeeIds.Contains(hc.Id)) | |
| 444 | + .ToList(); | |
| 445 | + | |
| 446 | + if (!employeesToProcess.Any()) | |
| 447 | + { | |
| 448 | + throw NCCException.Oh("所有新店健康师已存在该月份的数据"); | |
| 449 | + } | |
| 450 | + | |
| 451 | + // 4. 生成模拟数据 | |
| 452 | + var random = new Random(); | |
| 453 | + var entitiesToInsert = new List<LqSalaryExtraCalculationEntity>(); | |
| 454 | + var successCount = 0; | |
| 455 | + | |
| 456 | + foreach (var employee in employeesToProcess) | |
| 457 | + { | |
| 458 | + try | |
| 459 | + { | |
| 460 | + // 生成金额 | |
| 461 | + // 基础业绩奖励和合作业绩奖励:0-20000之间 | |
| 462 | + var baseRewardPerformance = (decimal)(random.NextDouble() * 10000); | |
| 463 | + var cooperationRewardPerformance = (decimal)(random.NextDouble() * 10000); | |
| 464 | + // 其他金额字段:10000-30000之间 | |
| 465 | + var newCustomerPerformance = (decimal)(random.NextDouble() * 10000); | |
| 466 | + var upgradePerformance = (decimal)(random.NextDouble() * 10000); | |
| 467 | + var otherPerformanceAdd = (decimal)(random.NextDouble() * 10000); | |
| 468 | + var otherPerformanceSubtract = (decimal)(random.NextDouble() * 10000); | |
| 469 | + | |
| 470 | + // 生成转化率(0-60%之间,转换为0-0.6) | |
| 471 | + var newCustomerConversionRate = (decimal)(random.NextDouble() * 0.6); | |
| 472 | + var upgradeConversionRate = (decimal)(random.NextDouble() * 0.6); | |
| 473 | + | |
| 474 | + // 生成升单人头数(0-10之间) | |
| 475 | + var upgradeCustomerCount = (decimal)random.Next(0, 11); | |
| 476 | + | |
| 477 | + var entity = new LqSalaryExtraCalculationEntity | |
| 478 | + { | |
| 479 | + Id = YitIdHelper.NextId().ToString(), | |
| 480 | + EmployeeId = employee.Id, | |
| 481 | + Year = year, | |
| 482 | + Month = month, | |
| 483 | + BaseRewardPerformance = Math.Round(baseRewardPerformance, 2), | |
| 484 | + CooperationRewardPerformance = Math.Round(cooperationRewardPerformance, 2), | |
| 485 | + NewCustomerPerformance = Math.Round(newCustomerPerformance, 2), | |
| 486 | + NewCustomerConversionRate = Math.Round(newCustomerConversionRate, 4), | |
| 487 | + UpgradePerformance = Math.Round(upgradePerformance, 2), | |
| 488 | + UpgradeConversionRate = Math.Round(upgradeConversionRate, 4), | |
| 489 | + UpgradeCustomerCount = upgradeCustomerCount, | |
| 490 | + OtherPerformanceAdd = Math.Round(otherPerformanceAdd, 2), | |
| 491 | + OtherPerformanceSubtract = Math.Round(otherPerformanceSubtract, 2) | |
| 492 | + }; | |
| 493 | + | |
| 494 | + entitiesToInsert.Add(entity); | |
| 495 | + successCount++; | |
| 496 | + } | |
| 497 | + catch | |
| 498 | + { | |
| 499 | + // 记录错误但继续处理其他健康师 | |
| 500 | + continue; | |
| 501 | + } | |
| 502 | + } | |
| 503 | + | |
| 504 | + // 5. 批量插入数据 | |
| 505 | + if (entitiesToInsert.Any()) | |
| 506 | + { | |
| 507 | + await _db.Insertable(entitiesToInsert).ExecuteCommandAsync(); | |
| 508 | + } | |
| 509 | + | |
| 510 | + return new | |
| 511 | + { | |
| 512 | + successCount, | |
| 513 | + totalCount = employeesToProcess.Count, | |
| 514 | + message = $"成功为新店健康师生成 {successCount} 条模拟数据" | |
| 515 | + }; | |
| 516 | + } | |
| 517 | + catch (Exception ex) | |
| 518 | + { | |
| 519 | + throw NCCException.Oh($"生成模拟数据失败: {ex.Message}"); | |
| 520 | + } | |
| 521 | + } | |
| 522 | + } | |
| 523 | +} | |
| 524 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
| ... | ... | @@ -16,6 +16,11 @@ using NCC.Extend.Entitys.lq_person_times_record; |
| 16 | 16 | using NCC.Extend.Entitys.lq_salary_statistics; |
| 17 | 17 | using NCC.Extend.Entitys.lq_xh_jksyj; |
| 18 | 18 | using NCC.Extend.Entitys.lq_ycsd_jsj; |
| 19 | +using NCC.Extend.Entitys.lq_mdxx; | |
| 20 | +using NCC.Extend.Entitys.lq_hytk_hytk; | |
| 21 | +using NCC.Extend.Entitys.lq_md_xdbhsj; | |
| 22 | +using NCC.Extend.Entitys.lq_salary_extra_calculation; | |
| 23 | +using NCC.System.Entitys.Permission; | |
| 19 | 24 | using SqlSugar; |
| 20 | 25 | using System; |
| 21 | 26 | using System.Collections.Generic; |
| ... | ... | @@ -25,9 +30,9 @@ using System.Threading.Tasks; |
| 25 | 30 | namespace NCC.Extend |
| 26 | 31 | { |
| 27 | 32 | /// <summary> |
| 28 | - /// 薪酬服务 | |
| 33 | + /// 健康师薪酬服务 | |
| 29 | 34 | /// </summary> |
| 30 | - [ApiDescriptionSettings(Tag = "薪酬服务", Name = "LqSalary", Order = 300)] | |
| 35 | + [ApiDescriptionSettings(Tag = "健康师薪酬服务", Name = "LqSalary", Order = 300)] | |
| 31 | 36 | [Route("api/Extend/[controller]")] |
| 32 | 37 | public class LqSalaryService : IDynamicApiController, ITransient |
| 33 | 38 | { |
| ... | ... | @@ -47,7 +52,7 @@ namespace NCC.Extend |
| 47 | 52 | /// <param name="input">查询参数</param> |
| 48 | 53 | /// <returns>健康师工资分页列表</returns> |
| 49 | 54 | [HttpGet("health-coach")] |
| 50 | - public async Task<PageResult<HealthCoachSalaryOutput>> GetHealthCoachSalaryList([FromQuery] HealthCoachSalaryInput input) | |
| 55 | + public async Task<dynamic> GetHealthCoachSalaryList([FromQuery] HealthCoachSalaryInput input) | |
| 51 | 56 | { |
| 52 | 57 | var monthStr = $"{input.Year}{input.Month:D2}"; |
| 53 | 58 | |
| ... | ... | @@ -78,26 +83,93 @@ namespace NCC.Extend |
| 78 | 83 | var list = await query.Select(x => new HealthCoachSalaryOutput |
| 79 | 84 | { |
| 80 | 85 | Id = x.Id, |
| 86 | + StoreId = x.StoreId, | |
| 81 | 87 | StoreName = x.StoreName, |
| 88 | + EmployeeId = x.EmployeeId, | |
| 82 | 89 | EmployeeName = x.EmployeeName, |
| 83 | 90 | Position = x.Position, |
| 91 | + GoldTriangleId = x.GoldTriangleId, | |
| 84 | 92 | GoldTriangleTeam = x.GoldTriangleTeam, |
| 85 | 93 | TotalPerformance = x.TotalPerformance, |
| 86 | 94 | BasePerformance = x.BasePerformance, |
| 87 | 95 | CooperationPerformance = x.CooperationPerformance, |
| 96 | + BaseRewardPerformance = x.BaseRewardPerformance, | |
| 97 | + CooperationRewardPerformance = x.CooperationRewardPerformance, | |
| 98 | + ActualBasePerformance = x.ActualBasePerformance, | |
| 99 | + ActualCooperationPerformance = x.ActualCooperationPerformance, | |
| 88 | 100 | RewardPerformance = x.RewardPerformance, |
| 101 | + StoreTotalPerformance = x.StoreTotalPerformance, | |
| 102 | + TeamPerformance = x.TeamPerformance, | |
| 103 | + Percentage = x.Percentage, | |
| 104 | + NewCustomerPerformance = x.NewCustomerPerformance, | |
| 105 | + NewCustomerConversionRate = x.NewCustomerConversionRate, | |
| 106 | + NewCustomerPoint = x.NewCustomerPoint, | |
| 107 | + UpgradeCustomerCount = x.UpgradeCustomerCount, | |
| 108 | + UpgradePerformance = x.UpgradePerformance, | |
| 109 | + UpgradePoint = x.UpgradePoint, | |
| 110 | + NewCustomerCommission = x.NewCustomerPerformanceCommission, | |
| 111 | + UpgradeCommission = x.UpgradePerformanceCommission, | |
| 112 | + OtherPerformanceAdd = x.OtherPerformanceAdd, | |
| 113 | + OtherPerformanceSubtract = x.OtherPerformanceSubtract, | |
| 89 | 114 | Consumption = x.Consumption, |
| 90 | 115 | ProjectCount = x.ProjectCount, |
| 91 | 116 | CustomerCount = x.CustomerCount, |
| 92 | 117 | WorkingDays = x.WorkingDays, |
| 93 | - HealthCoachBaseSalary = x.HealthCoachBaseSalary, | |
| 118 | + LeaveDays = x.LeaveDays, | |
| 119 | + CommissionPoint = x.CommissionPoint, | |
| 120 | + BasePerformanceCommission = x.BasePerformanceCommission, | |
| 121 | + CooperationPerformanceCommission = x.CooperationPerformanceCommission, | |
| 122 | + ConsultantCommission = x.ConsultantCommission, | |
| 123 | + StoreTZoneCommission = x.StoreTZoneCommission, | |
| 94 | 124 | TotalCommission = x.TotalCommission, |
| 125 | + HealthCoachBaseSalary = x.HealthCoachBaseSalary, | |
| 95 | 126 | HandworkFee = x.HandworkFee, |
| 127 | + OutherHandworkFee = x.OutherHandworkFee, | |
| 128 | + TransportationAllowance = x.TransportationAllowance, | |
| 129 | + LessRest = x.LessRest, | |
| 130 | + FullAttendance = x.FullAttendance, | |
| 131 | + CalculatedGrossSalary = x.CalculatedGrossSalary, | |
| 132 | + GuaranteedSalary = x.GuaranteedSalary, | |
| 133 | + GuaranteedLeaveDeduction = x.GuaranteedLeaveDeduction, | |
| 134 | + GuaranteedBaseSalary = x.GuaranteedBaseSalary, | |
| 135 | + GuaranteedSupplement = x.GuaranteedSupplement, | |
| 136 | + FinalGrossSalary = x.FinalGrossSalary, | |
| 137 | + MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy, | |
| 138 | + MonthlyTransportSubsidy = x.MonthlyTransportSubsidy, | |
| 139 | + LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy, | |
| 140 | + LastMonthTransportSubsidy = x.LastMonthTransportSubsidy, | |
| 96 | 141 | TotalSubsidy = x.TotalSubsidy, |
| 142 | + MissingCard = x.MissingCard, | |
| 143 | + LateArrival = x.LateArrival, | |
| 144 | + LeaveDeduction = x.LeaveDeduction, | |
| 145 | + SocialInsuranceDeduction = x.SocialInsuranceDeduction, | |
| 146 | + RewardDeduction = x.RewardDeduction, | |
| 147 | + AccommodationDeduction = x.AccommodationDeduction, | |
| 148 | + StudyPeriodDeduction = x.StudyPeriodDeduction, | |
| 149 | + WorkClothesDeduction = x.WorkClothesDeduction, | |
| 97 | 150 | TotalDeduction = x.TotalDeduction, |
| 151 | + Bonus = x.Bonus, | |
| 152 | + ReturnPhoneDeposit = x.ReturnPhoneDeposit, | |
| 153 | + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit, | |
| 98 | 154 | ActualSalary = x.ActualSalary, |
| 155 | + MonthlyPaymentStatus = x.MonthlyPaymentStatus, | |
| 156 | + PaidAmount = x.PaidAmount, | |
| 157 | + PendingAmount = x.PendingAmount, | |
| 158 | + LastMonthSupplement = x.LastMonthSupplement, | |
| 159 | + MonthlyTotalPayment = x.MonthlyTotalPayment, | |
| 160 | + StatisticsMonth = x.StatisticsMonth, | |
| 99 | 161 | IsLocked = x.IsLocked, |
| 100 | - UpdateTime = x.UpdateTime | |
| 162 | + CreateTime = x.CreateTime, | |
| 163 | + CreateUser = x.CreateUser, | |
| 164 | + UpdateTime = x.UpdateTime, | |
| 165 | + UpdateUser = x.UpdateUser, | |
| 166 | + IsNewStore = x.IsNewStore, | |
| 167 | + NewStoreProtectionStage = x.NewStoreProtectionStage, | |
| 168 | + StoreType = x.StoreType, | |
| 169 | + StoreCategory = x.StoreCategory, | |
| 170 | + DailyAverageConsumption = x.DailyAverageConsumption, | |
| 171 | + DailyAverageProjectCount = x.DailyAverageProjectCount, | |
| 172 | + TeamTotalConsumption = x.TeamTotalConsumption | |
| 101 | 173 | }) |
| 102 | 174 | .ToPagedListAsync(input.currentPage, input.pageSize); |
| 103 | 175 | |
| ... | ... | @@ -127,17 +199,18 @@ namespace NCC.Extend |
| 127 | 199 | // 1.1.1 获取关联的开单记录(用于获取 sfskdd) |
| 128 | 200 | var billingIds = performanceList.Select(x => x.Glkdbh).Distinct().ToList(); |
| 129 | 201 | var billingDict = await _db.Queryable<LqKdKdjlbEntity>() |
| 130 | - .Where(x => billingIds.Contains(x.Id)) | |
| 202 | + .Where(x => billingIds.Contains(x.Id) && x.Id != null) | |
| 131 | 203 | .ToDictionaryAsync(x => x.Id, x => x.Sfskdd); |
| 132 | 204 | |
| 133 | 205 | // 1.1.2 组合数据 |
| 134 | 206 | var performanceData = performanceList.Select(p => new |
| 135 | 207 | { |
| 136 | - p.Jks, | |
| 208 | + Jks = p.Jkszh, // 使用 Jkszh (账号/ID) 而不是 Jks (姓名) | |
| 137 | 209 | p.Jksxm, |
| 138 | 210 | p.StoreId, |
| 139 | 211 | p.Jksyj, |
| 140 | 212 | p.ItemCategory, |
| 213 | + p.PerformanceType, // 新增业绩类型字段 | |
| 141 | 214 | Sfskdd = billingDict.ContainsKey(p.Glkdbh) ? billingDict[p.Glkdbh] : null |
| 142 | 215 | }).ToList(); |
| 143 | 216 | |
| ... | ... | @@ -161,7 +234,7 @@ namespace NCC.Extend |
| 161 | 234 | var teamList = await _db.Queryable<LqYcsdJsjEntity>() |
| 162 | 235 | .Where(x => teamIds.Contains(x.Id)) |
| 163 | 236 | .ToListAsync(); |
| 164 | - var teamDict = teamList.ToDictionary(x => x.Id, x => x.Jsj); | |
| 237 | + var teamDict = teamList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x.Jsj); | |
| 165 | 238 | |
| 166 | 239 | // 1.4.2 组合数据 |
| 167 | 240 | var teamMembers = teamUserList.Select(user => new |
| ... | ... | @@ -185,6 +258,55 @@ namespace NCC.Extend |
| 185 | 258 | .Where(x => x.Month == monthStr) |
| 186 | 259 | .ToListAsync(); |
| 187 | 260 | |
| 261 | + // 1.6.1 门店总业绩计算 (开单实付 - 退款金额) | |
| 262 | + // 开单实付 | |
| 263 | + var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>() | |
| 264 | + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1) | |
| 265 | + .Select(x => new { x.Djmd, x.Sfyj }) | |
| 266 | + .ToListAsync(); | |
| 267 | + var storeBillingDict = storeBillingList | |
| 268 | + .Where(x => !string.IsNullOrEmpty(x.Djmd)) | |
| 269 | + .GroupBy(x => x.Djmd) | |
| 270 | + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj)); | |
| 271 | + | |
| 272 | + // 退款金额 | |
| 273 | + var storeRefundList = await _db.Queryable<LqHytkHytkEntity>() | |
| 274 | + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) | |
| 275 | + .Select(x => new { x.Mdbh, x.Tkje }) | |
| 276 | + .ToListAsync(); | |
| 277 | + var storeRefundDict = storeRefundList | |
| 278 | + .Where(x => !string.IsNullOrEmpty(x.Mdbh)) | |
| 279 | + .GroupBy(x => x.Mdbh) | |
| 280 | + .ToDictionary(g => g.Key, g => g.Sum(x => x.Tkje ?? 0)); | |
| 281 | + | |
| 282 | + // 1.7 门店信息 (lq_mdxx) | |
| 283 | + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync(); | |
| 284 | + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); | |
| 285 | + | |
| 286 | + // 1.7.1 门店新店保护信息 (lq_md_xdbhsj) | |
| 287 | + var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>() | |
| 288 | + .Where(x => x.Sfqy == 1) | |
| 289 | + .ToListAsync(); | |
| 290 | + | |
| 291 | + // 构造新店保护查找字典: StoreId -> List<ProtectionInfo> | |
| 292 | + // 因为一个门店可能有多个阶段配置,虽然通常是一个时间段,但为了严谨取符合当前月份的配置 | |
| 293 | + // 逻辑: 统计月份 (startDate ~ endDate) 是否在 bhkssj ~ bhjssj 范围内 | |
| 294 | + // 只要统计月份与保护期有交集,就算保护期。或者严格一点,统计月份的第一天在保护期内。 | |
| 295 | + // 这里取: 统计月份的第一天 (startDate) 在保护期内 | |
| 296 | + var newStoreProtectionDict = newStoreProtectionList | |
| 297 | + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate) | |
| 298 | + .GroupBy(x => x.Mdid) | |
| 299 | + .ToDictionary(g => g.Key, g => g.First()); // 取第一个匹配的配置 | |
| 300 | + | |
| 301 | + // 1.8 健康师工资额外计算数据 (lq_salary_extra_calculation) | |
| 302 | + var extraCalculationList = await _db.Queryable<LqSalaryExtraCalculationEntity>() | |
| 303 | + .Where(x => x.Year == year && x.Month == month) | |
| 304 | + .ToListAsync(); | |
| 305 | + | |
| 306 | + var extraCalculationDict = extraCalculationList | |
| 307 | + .Where(x => !string.IsNullOrEmpty(x.EmployeeId)) | |
| 308 | + .ToDictionary(x => x.EmployeeId, x => x); | |
| 309 | + | |
| 188 | 310 | // 2. 聚合每个健康师的数据对象 |
| 189 | 311 | var employeeStats = new Dictionary<string, LqSalaryStatisticsEntity>(); |
| 190 | 312 | |
| ... | ... | @@ -197,6 +319,19 @@ namespace NCC.Extend |
| 197 | 319 | .Distinct() |
| 198 | 320 | .ToList(); |
| 199 | 321 | |
| 322 | + // 1.8 批量获取员工信息 (BASE_USER + BASE_POSITION) | |
| 323 | + // 使用 allEmployeeIds 作为驱动,查询 BASE_USER | |
| 324 | + var userList = await _db.Queryable<UserEntity>() | |
| 325 | + .Where(x => allEmployeeIds.Contains(x.Id)) | |
| 326 | + .Select(x => new { x.Id, x.RealName, x.PositionId, x.Mdid }) | |
| 327 | + .ToListAsync(); | |
| 328 | + | |
| 329 | + var userDict = userList.ToDictionary(x => x.Id, x => x); | |
| 330 | + | |
| 331 | + var positionIds = userList.Select(x => x.PositionId).Distinct().ToList(); | |
| 332 | + var positionList = await _db.Queryable<PositionEntity>().Where(x => positionIds.Contains(x.Id)).ToListAsync(); | |
| 333 | + var positionLookup = positionList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x.FullName); | |
| 334 | + | |
| 200 | 335 | foreach (var empId in allEmployeeIds) |
| 201 | 336 | { |
| 202 | 337 | var salary = new LqSalaryStatisticsEntity |
| ... | ... | @@ -209,37 +344,122 @@ namespace NCC.Extend |
| 209 | 344 | IsLocked = 0 |
| 210 | 345 | }; |
| 211 | 346 | |
| 212 | - // 填充基础信息 (姓名、门店) | |
| 213 | - var perfRecord = performanceData.FirstOrDefault(x => x.Jks == empId); | |
| 214 | - var consRecord = consumptionList.FirstOrDefault(x => x.Jks == empId); | |
| 347 | + // 填充基础信息 (优先从 BASE_USER 获取) | |
| 348 | + string userMdid = null; | |
| 349 | + if (userDict.ContainsKey(empId)) | |
| 350 | + { | |
| 351 | + var user = userDict[empId]; | |
| 352 | + salary.EmployeeName = user.RealName; | |
| 353 | + userMdid = user.Mdid; | |
| 215 | 354 | |
| 216 | - if (perfRecord != null) | |
| 355 | + // 岗位 | |
| 356 | + if (user.PositionId != null && positionLookup.ContainsKey(user.PositionId)) | |
| 357 | + { | |
| 358 | + salary.Position = positionLookup[user.PositionId]; | |
| 359 | + } | |
| 360 | + } | |
| 361 | + | |
| 362 | + // 如果 BASE_USER 没名字,尝试从业务表获取 | |
| 363 | + if (string.IsNullOrEmpty(salary.EmployeeName)) | |
| 217 | 364 | { |
| 218 | - salary.EmployeeName = perfRecord.Jksxm; | |
| 219 | - salary.StoreId = perfRecord.StoreId; | |
| 365 | + var perfRecord = performanceData.FirstOrDefault(x => x.Jks == empId); | |
| 366 | + var consRecord = consumptionList.FirstOrDefault(x => x.Jks == empId); | |
| 367 | + if (perfRecord != null) salary.EmployeeName = perfRecord.Jksxm; | |
| 368 | + else if (consRecord != null) salary.EmployeeName = consRecord.Jksxm; | |
| 220 | 369 | } |
| 221 | - else if (consRecord != null) | |
| 370 | + | |
| 371 | + // 填充门店ID (从业务数据获取,因为 User 表的 OrganizeId 未必是门店) | |
| 372 | + var perfStore = performanceData.FirstOrDefault(x => x.Jks == empId && !string.IsNullOrEmpty(x.StoreId)); | |
| 373 | + var consStore = consumptionList.FirstOrDefault(x => x.Jks == empId && !string.IsNullOrEmpty(x.StoreId)); | |
| 374 | + | |
| 375 | + if (perfStore != null) salary.StoreId = perfStore.StoreId; | |
| 376 | + else if (consStore != null) salary.StoreId = consStore.StoreId; | |
| 377 | + | |
| 378 | + // 如果业务数据没门店,尝试使用 User.Mdid | |
| 379 | + if (string.IsNullOrEmpty(salary.StoreId) && !string.IsNullOrEmpty(userMdid)) | |
| 222 | 380 | { |
| 223 | - salary.EmployeeName = consRecord.Jksxm; | |
| 224 | - salary.StoreId = consRecord.StoreId; | |
| 381 | + if (storeDict.ContainsKey(userMdid)) | |
| 382 | + { | |
| 383 | + salary.StoreId = userMdid; | |
| 384 | + } | |
| 225 | 385 | } |
| 226 | 386 | |
| 227 | - // 填充门店名称 | |
| 228 | - if (!string.IsNullOrEmpty(salary.StoreId)) | |
| 387 | + // 填充门店名称及分类信息 | |
| 388 | + if (!string.IsNullOrEmpty(salary.StoreId) && storeDict.ContainsKey(salary.StoreId)) | |
| 229 | 389 | { |
| 230 | - // 这里简单处理,实际可能需要缓存门店列表 | |
| 231 | - // salary.StoreName = ... | |
| 390 | + var store = storeDict[salary.StoreId]; | |
| 391 | + salary.StoreName = store.Dm; | |
| 392 | + salary.StoreType = store.StoreType; | |
| 393 | + salary.StoreCategory = store.StoreCategory; | |
| 394 | + } | |
| 395 | + | |
| 396 | + // 填充新店保护信息 | |
| 397 | + if (!string.IsNullOrEmpty(salary.StoreId) && newStoreProtectionDict.ContainsKey(salary.StoreId)) | |
| 398 | + { | |
| 399 | + var protection = newStoreProtectionDict[salary.StoreId]; | |
| 400 | + salary.IsNewStore = "是"; | |
| 401 | + salary.NewStoreProtectionStage = protection.Stage; | |
| 402 | + } | |
| 403 | + else | |
| 404 | + { | |
| 405 | + salary.IsNewStore = "否"; | |
| 406 | + salary.NewStoreProtectionStage = 0; | |
| 232 | 407 | } |
| 233 | 408 | |
| 234 | 409 | // 2.1 计算个人业绩 |
| 235 | 410 | var myPerf = performanceData.Where(x => x.Jks == empId).ToList(); |
| 236 | - salary.BasePerformance = myPerf.Where(x => x.ItemCategory == "基础业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); | |
| 237 | - salary.CooperationPerformance = myPerf.Where(x => x.ItemCategory == "合作业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); | |
| 411 | + salary.BasePerformance = myPerf.Where(x => (x.PerformanceType ?? "").Trim() == "基础业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); | |
| 412 | + salary.CooperationPerformance = myPerf.Where(x => (x.PerformanceType ?? "").Trim() == "合作业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); | |
| 238 | 413 | salary.TotalPerformance = myPerf.Sum(x => decimal.Parse(x.Jksyj ?? "0")); |
| 239 | 414 | |
| 240 | 415 | // 新客与升单业绩 |
| 241 | - salary.NewCustomerPerformance = myPerf.Where(x => x.Sfskdd == "是").Sum(x => decimal.Parse(x.Jksyj ?? "0")); | |
| 242 | - salary.UpgradePerformance = myPerf.Where(x => x.Sfskdd == "否").Sum(x => decimal.Parse(x.Jksyj ?? "0")); | |
| 416 | + salary.NewCustomerPerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "是")).Sum(x => decimal.Parse(x.Jksyj ?? "0")); | |
| 417 | + salary.UpgradePerformance = myPerf.Where(x => string.Equals(x.Sfskdd, "否")).Sum(x => decimal.Parse(x.Jksyj ?? "0")); | |
| 418 | + | |
| 419 | + // 2.1.1 填充额外计算数据 | |
| 420 | + if (extraCalculationDict.ContainsKey(empId)) | |
| 421 | + { | |
| 422 | + var extraData = extraCalculationDict[empId]; | |
| 423 | + salary.BaseRewardPerformance = extraData.BaseRewardPerformance; | |
| 424 | + salary.CooperationRewardPerformance = extraData.CooperationRewardPerformance; | |
| 425 | + salary.OtherPerformanceAdd = extraData.OtherPerformanceAdd; | |
| 426 | + salary.OtherPerformanceSubtract = extraData.OtherPerformanceSubtract; | |
| 427 | + salary.UpgradeCustomerCount = extraData.UpgradeCustomerCount; | |
| 428 | + salary.NewCustomerConversionRate = extraData.NewCustomerConversionRate; | |
| 429 | + salary.NewCustomerPerformance = extraData.NewCustomerPerformance; | |
| 430 | + salary.UpgradePerformance = extraData.UpgradePerformance; | |
| 431 | + } | |
| 432 | + | |
| 433 | + // 2.1.2 计算实际基础业绩和实际合作业绩 | |
| 434 | + // 定义新店相关变量,供后续多处使用 | |
| 435 | + bool isNewStore = salary.IsNewStore == "是"; | |
| 436 | + int newStoreStage = salary.NewStoreProtectionStage; | |
| 437 | + | |
| 438 | + // 实际基础业绩 = 基础业绩 - 基础奖励业绩 + 其他业绩加 - 其他业绩减 | |
| 439 | + decimal actualBasePerformance = salary.BasePerformance | |
| 440 | + - salary.BaseRewardPerformance | |
| 441 | + + salary.OtherPerformanceAdd | |
| 442 | + - salary.OtherPerformanceSubtract; | |
| 443 | + | |
| 444 | + // 新店额外调整:根据阶段扣除新客业绩或升单业绩 | |
| 445 | + if (isNewStore) | |
| 446 | + { | |
| 447 | + if (newStoreStage == 1) | |
| 448 | + { | |
| 449 | + // 第一阶段:扣除新客业绩 | |
| 450 | + actualBasePerformance -= salary.NewCustomerPerformance; | |
| 451 | + } | |
| 452 | + else if (newStoreStage == 2) | |
| 453 | + { | |
| 454 | + // 第二阶段:扣除升单业绩 | |
| 455 | + actualBasePerformance -= salary.UpgradePerformance; | |
| 456 | + } | |
| 457 | + } | |
| 458 | + | |
| 459 | + salary.ActualBasePerformance = actualBasePerformance; | |
| 460 | + | |
| 461 | + // 实际合作业绩 = 合作业绩 - 合作奖励业绩 | |
| 462 | + salary.ActualCooperationPerformance = salary.CooperationPerformance - salary.CooperationRewardPerformance; | |
| 243 | 463 | |
| 244 | 464 | // 2.2 计算消耗和项目数 |
| 245 | 465 | var myCons = consumptionList.Where(x => x.Jks == empId).ToList(); |
| ... | ... | @@ -252,23 +472,59 @@ namespace NCC.Extend |
| 252 | 472 | salary.WorkingDays = myAtt?.WorkDays ?? 0; |
| 253 | 473 | salary.LeaveDays = myAtt?.LeaveDays ?? 0; |
| 254 | 474 | |
| 475 | + // 计算日均消耗和日均项目数 (用于底薪计算) | |
| 476 | + // 逻辑: 总消耗/在店天数, 总项目数/在店天数 | |
| 477 | + if (salary.WorkingDays > 0) | |
| 478 | + { | |
| 479 | + salary.DailyAverageConsumption = salary.Consumption / salary.WorkingDays; | |
| 480 | + salary.DailyAverageProjectCount = salary.ProjectCount / salary.WorkingDays; | |
| 481 | + } | |
| 482 | + else | |
| 483 | + { | |
| 484 | + salary.DailyAverageConsumption = 0; | |
| 485 | + salary.DailyAverageProjectCount = 0; | |
| 486 | + } | |
| 487 | + | |
| 255 | 488 | // 2.4 到店人头 |
| 256 | 489 | var myHeadcount = headcountList.FirstOrDefault(x => x.PersonId == empId); |
| 257 | 490 | salary.CustomerCount = myHeadcount?.Count ?? 0; |
| 258 | 491 | |
| 259 | 492 | // 2.5 战队信息 (初始) |
| 260 | 493 | var myTeam = teamMembers.FirstOrDefault(x => x.UserId == empId); |
| 494 | + // 初始判断岗位:如果是战队队长(IsLeader=1)则是顾问,否则是健康师 | |
| 495 | + // 注意:这里先根据战队设置判断,后续考勤不足21天会降级 | |
| 496 | + if (myTeam != null && myTeam.IsLeader == 1) | |
| 497 | + { | |
| 498 | + salary.Position = "顾问"; | |
| 499 | + } | |
| 500 | + else if (string.IsNullOrEmpty(salary.Position)) // 如果BASE_USER没岗位,且不是队长,默认为健康师 | |
| 501 | + { | |
| 502 | + salary.Position = "健康师"; | |
| 503 | + } | |
| 504 | + | |
| 261 | 505 | if (myTeam != null) |
| 262 | 506 | { |
| 263 | 507 | salary.GoldTriangleId = myTeam.TeamId; |
| 264 | - salary.GoldTriangleTeam = myTeam.TeamName ?? ""; | |
| 508 | + salary.GoldTriangleTeam = myTeam.TeamName ?? "个人"; | |
| 509 | + } | |
| 510 | + else | |
| 511 | + { | |
| 512 | + salary.GoldTriangleTeam = "个人"; | |
| 513 | + } | |
| 514 | + | |
| 515 | + // 2.6 门店总业绩 | |
| 516 | + if (!string.IsNullOrEmpty(salary.StoreId)) | |
| 517 | + { | |
| 518 | + decimal billing = storeBillingDict.ContainsKey(salary.StoreId) ? storeBillingDict[salary.StoreId] : 0; | |
| 519 | + decimal refund = storeRefundDict.ContainsKey(salary.StoreId) ? storeRefundDict[salary.StoreId] : 0; | |
| 520 | + salary.StoreTotalPerformance = billing - refund; | |
| 265 | 521 | } |
| 266 | 522 | |
| 267 | 523 | employeeStats[empId] = salary; |
| 268 | 524 | } |
| 269 | 525 | |
| 270 | 526 | // 3. 处理战队逻辑 (考勤规则) |
| 271 | - // 规则:若出勤天数 < 21天,则该健康师不计入战队,按单人计算。 | |
| 527 | + // 规则:若出勤天数 < 20天,则该健康师不计入战队,按单人计算。 | |
| 272 | 528 | |
| 273 | 529 | // 按战队分组 |
| 274 | 530 | var teamGroups = employeeStats.Values |
| ... | ... | @@ -283,7 +539,7 @@ namespace NCC.Extend |
| 283 | 539 | |
| 284 | 540 | foreach (var member in group) |
| 285 | 541 | { |
| 286 | - if (member.WorkingDays >= 21) | |
| 542 | + if (member.WorkingDays >= 20) | |
| 287 | 543 | { |
| 288 | 544 | validMembers.Add(member); |
| 289 | 545 | } |
| ... | ... | @@ -293,37 +549,62 @@ namespace NCC.Extend |
| 293 | 549 | } |
| 294 | 550 | } |
| 295 | 551 | |
| 296 | - // 对于无效成员,移除战队标识,视为单人 | |
| 552 | + // 对于无效成员,移除战队标识,视为单人,并重置岗位为健康师 | |
| 297 | 553 | foreach (var member in invalidMembers) |
| 298 | 554 | { |
| 299 | 555 | member.GoldTriangleId = null; |
| 300 | - member.GoldTriangleTeam = null; | |
| 556 | + member.GoldTriangleTeam = "个人"; | |
| 557 | + member.Position = "健康师"; // 降级为健康师 | |
| 301 | 558 | } |
| 302 | 559 | |
| 303 | - // 计算有效战队的总业绩 | |
| 560 | + // 计算有效战队的总业绩和总消耗 | |
| 304 | 561 | var teamTotalPerformance = validMembers.Sum(x => x.TotalPerformance); |
| 562 | + var teamTotalConsumption = validMembers.Sum(x => x.Consumption); | |
| 305 | 563 | |
| 306 | - // 更新有效成员的战队业绩 | |
| 564 | + // 更新有效成员的战队业绩和战队总消耗 | |
| 307 | 565 | foreach (var member in validMembers) |
| 308 | 566 | { |
| 309 | 567 | member.TeamPerformance = teamTotalPerformance; |
| 568 | + member.TeamTotalConsumption = teamTotalConsumption; | |
| 569 | + } | |
| 570 | + } | |
| 571 | + | |
| 572 | + // 补充处理:对于没有战队ID的(个人,或被剔除的),战队业绩等于个人总业绩 | |
| 573 | + foreach (var salary in employeeStats.Values) | |
| 574 | + { | |
| 575 | + if (string.IsNullOrEmpty(salary.GoldTriangleId)) | |
| 576 | + { | |
| 577 | + salary.TeamPerformance = salary.TotalPerformance; | |
| 310 | 578 | } |
| 311 | 579 | } |
| 312 | 580 | |
| 313 | 581 | // 4. 计算薪资 (底薪 & 提成) |
| 314 | 582 | foreach (var salary in employeeStats.Values) |
| 315 | 583 | { |
| 584 | + // 定义新店相关变量,供底薪和提成计算使用 | |
| 585 | + bool isNewStore = salary.IsNewStore == "是"; | |
| 586 | + int newStoreStage = salary.NewStoreProtectionStage; | |
| 587 | + | |
| 588 | + // 4.1 底薪计算 | |
| 316 | 589 | // 4.1 底薪计算 |
| 317 | - salary.HealthCoachBaseSalary = CalculateBaseSalary(salary.Consumption, salary.ProjectCount); | |
| 590 | + // 传入当月天数用于计算标准日均 | |
| 591 | + int daysInMonth = DateTime.DaysInMonth(year, month); | |
| 592 | + salary.HealthCoachBaseSalary = CalculateBaseSalary( | |
| 593 | + salary.DailyAverageConsumption, | |
| 594 | + salary.DailyAverageProjectCount, | |
| 595 | + daysInMonth, | |
| 596 | + isNewStore); | |
| 318 | 597 | |
| 319 | 598 | // 4.2 提成计算 |
| 320 | - // 单人业绩 <= 6000 无提成 | |
| 321 | - if (salary.TotalPerformance <= 6000) | |
| 599 | + // 业绩门槛: 战队成员个人总业绩 <= 6000 无提成 | |
| 600 | + if (!string.IsNullOrEmpty(salary.GoldTriangleId) && salary.TotalPerformance <= 6000) | |
| 322 | 601 | { |
| 323 | 602 | salary.TotalCommission = 0; |
| 324 | 603 | salary.BasePerformanceCommission = 0; |
| 325 | 604 | salary.CooperationPerformanceCommission = 0; |
| 326 | 605 | salary.ConsultantCommission = 0; |
| 606 | + salary.NewCustomerPerformanceCommission = 0; | |
| 607 | + salary.UpgradePerformanceCommission = 0; | |
| 327 | 608 | } |
| 328 | 609 | else |
| 329 | 610 | { |
| ... | ... | @@ -335,29 +616,68 @@ namespace NCC.Extend |
| 335 | 616 | // 是战队成员 |
| 336 | 617 | // 获取战队人数 (注意:这里应该是有效战队人数) |
| 337 | 618 | var teamMemberCount = employeeStats.Values.Count(x => x.GoldTriangleId == salary.GoldTriangleId); |
| 619 | + // 注意:提成点按原始基础业绩计算,不是实际基础业绩 | |
| 338 | 620 | commissionPoint = GetTeamCommissionPoint(teamMemberCount, salary.TeamPerformance); |
| 339 | 621 | } |
| 340 | 622 | else |
| 341 | 623 | { |
| 342 | 624 | // 单人 (或被剔除出战队) |
| 625 | + // 注意:提成点按原始总业绩计算 | |
| 343 | 626 | commissionPoint = GetTeamCommissionPoint(1, salary.TotalPerformance); |
| 344 | 627 | } |
| 345 | 628 | |
| 346 | 629 | salary.CommissionPoint = commissionPoint; |
| 347 | 630 | |
| 348 | - // 计算基础/合作提成 | |
| 349 | - salary.BasePerformanceCommission = salary.BasePerformance * 0.95m * commissionPoint; | |
| 350 | - salary.CooperationPerformanceCommission = salary.CooperationPerformance * 0.95m * 0.65m * commissionPoint; | |
| 631 | + // 计算基础/合作提成(使用实际业绩) | |
| 632 | + salary.BasePerformanceCommission = salary.ActualBasePerformance * 0.95m * commissionPoint; | |
| 633 | + salary.CooperationPerformanceCommission = salary.ActualCooperationPerformance * 0.95m * 0.65m * commissionPoint; | |
| 634 | + | |
| 635 | + // 计算新客转化率提成和升单人头提成(根据新店阶段) | |
| 636 | + // isNewStore 和 newStoreStage 已在上面定义 | |
| 637 | + | |
| 638 | + if (isNewStore) | |
| 639 | + { | |
| 640 | + if (newStoreStage == 1) | |
| 641 | + { | |
| 642 | + // 第一阶段:计算新客转化率提成 (需乘以0.95) | |
| 643 | + salary.NewCustomerPerformanceCommission = CalculateNewCustomerConversionCommission( | |
| 644 | + salary.NewCustomerPerformance, | |
| 645 | + salary.NewCustomerConversionRate) * 0.95m; | |
| 646 | + } | |
| 647 | + else if (newStoreStage == 2) | |
| 648 | + { | |
| 649 | + // 第二阶段:计算升单人头提成 (需乘以0.95) | |
| 650 | + salary.UpgradePerformanceCommission = CalculateUpgradeCustomerCommission( | |
| 651 | + salary.UpgradePerformance, | |
| 652 | + salary.UpgradeCustomerCount) * 0.95m; | |
| 653 | + } | |
| 654 | + // 第三阶段:不计算新客/升单提成 | |
| 655 | + } | |
| 351 | 656 | |
| 352 | 657 | // 计算顾问提成 |
| 353 | 658 | // 检查是否是顾问 |
| 354 | - var isConsultant = teamMembers.Any(x => x.UserId == salary.EmployeeId && x.IsLeader == 1); | |
| 355 | - if (isConsultant && !string.IsNullOrEmpty(salary.GoldTriangleId)) | |
| 659 | + // 注意:这里需要重新判断是否是顾问,因为可能被降级了 | |
| 660 | + if (salary.Position == "顾问" && !string.IsNullOrEmpty(salary.GoldTriangleId)) | |
| 661 | + { | |
| 662 | + salary.ConsultantCommission = CalculateConsultantCommission( | |
| 663 | + salary.TeamPerformance, | |
| 664 | + employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList(), | |
| 665 | + isNewStore); | |
| 666 | + } | |
| 667 | + | |
| 668 | + // 计算门店T区提成 | |
| 669 | + // 规则:姓名包含"T区" -> 门店总业绩 * 0.05 * 0.05 | |
| 670 | + if (!string.IsNullOrEmpty(salary.EmployeeName) && salary.EmployeeName.Contains("T区")) | |
| 356 | 671 | { |
| 357 | - salary.ConsultantCommission = CalculateConsultantCommission(salary.TeamPerformance, employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList()); | |
| 672 | + salary.StoreTZoneCommission = salary.StoreTotalPerformance * 0.05m * 0.05m; | |
| 358 | 673 | } |
| 359 | 674 | |
| 360 | - salary.TotalCommission = salary.BasePerformanceCommission + salary.CooperationPerformanceCommission + salary.ConsultantCommission; | |
| 675 | + salary.TotalCommission = salary.BasePerformanceCommission | |
| 676 | + + salary.CooperationPerformanceCommission | |
| 677 | + + salary.ConsultantCommission | |
| 678 | + + salary.NewCustomerPerformanceCommission | |
| 679 | + + salary.UpgradePerformanceCommission | |
| 680 | + + salary.StoreTZoneCommission; | |
| 361 | 681 | } |
| 362 | 682 | |
| 363 | 683 | // 计算占比 |
| ... | ... | @@ -386,24 +706,39 @@ namespace NCC.Extend |
| 386 | 706 | /// <summary> |
| 387 | 707 | /// 计算底薪 |
| 388 | 708 | /// </summary> |
| 389 | - private decimal CalculateBaseSalary(decimal consumption, decimal projectCount) | |
| 709 | + private decimal CalculateBaseSalary(decimal dailyAvgConsumption, decimal dailyAvgProjectCount, int daysInMonth, bool isNewStore) | |
| 390 | 710 | { |
| 391 | - // 0星:<1w 或 <96个 -> 1800 | |
| 392 | - // 1星:>=1w 且 >=96个 -> 2000 | |
| 393 | - // 2星:>=2w 且 >=126个 -> 2200 | |
| 394 | - // 3星:>=4w 且 >=156个 -> 2400 | |
| 711 | + // 规则调整:按日均计算 | |
| 712 | + // 一星:月消耗 10000 / 当月天数,项目数 96 / 当月天数 | |
| 713 | + // 二星:月消耗 20000 / 当月天数,项目数 126 / 当月天数 | |
| 714 | + // 三星:月消耗 40000 / 当月天数,项目数 156 / 当月天数 | |
| 715 | + | |
| 716 | + // 计算各星级日均标准 | |
| 717 | + decimal consStar1 = 10000m / daysInMonth; | |
| 718 | + decimal consStar2 = 20000m / daysInMonth; | |
| 719 | + decimal consStar3 = 40000m / daysInMonth; | |
| 720 | + | |
| 721 | + decimal projStar1 = 96m / daysInMonth; | |
| 722 | + decimal projStar2 = 126m / daysInMonth; | |
| 723 | + decimal projStar3 = 156m / daysInMonth; | |
| 724 | + | |
| 725 | + // 0星:未达标 -> 1800 | |
| 726 | + // 1星:>=1w标准 且 >=96个标准 -> 2000 | |
| 727 | + // 2星:>=2w标准 且 >=126个标准 -> 2200 | |
| 728 | + // 3星:>=4w标准 且 >=156个标准 -> 2400 | |
| 395 | 729 | |
| 396 | 730 | // 特殊规则:若消耗或项目数中仅一项未达标(0星),底薪按1星(2000元)计算 |
| 731 | + // 新店规则:新店底薪最低为1星(2000元),不满足1星按1星算 | |
| 397 | 732 | |
| 398 | 733 | int starCons = 0; |
| 399 | - if (consumption >= 40000) starCons = 3; | |
| 400 | - else if (consumption >= 20000) starCons = 2; | |
| 401 | - else if (consumption >= 10000) starCons = 1; | |
| 734 | + if (dailyAvgConsumption >= consStar3) starCons = 3; | |
| 735 | + else if (dailyAvgConsumption >= consStar2) starCons = 2; | |
| 736 | + else if (dailyAvgConsumption >= consStar1) starCons = 1; | |
| 402 | 737 | |
| 403 | 738 | int starProj = 0; |
| 404 | - if (projectCount >= 156) starProj = 3; | |
| 405 | - else if (projectCount >= 126) starProj = 2; | |
| 406 | - else if (projectCount >= 96) starProj = 1; | |
| 739 | + if (dailyAvgProjectCount >= projStar3) starProj = 3; | |
| 740 | + else if (dailyAvgProjectCount >= projStar2) starProj = 2; | |
| 741 | + else if (dailyAvgProjectCount >= projStar1) starProj = 1; | |
| 407 | 742 | |
| 408 | 743 | int finalStar = Math.Min(starCons, starProj); |
| 409 | 744 | |
| ... | ... | @@ -413,13 +748,21 @@ namespace NCC.Extend |
| 413 | 748 | finalStar = 1; |
| 414 | 749 | } |
| 415 | 750 | |
| 416 | - switch (finalStar) | |
| 751 | + decimal baseSalary = finalStar switch | |
| 752 | + { | |
| 753 | + 3 => 2400, | |
| 754 | + 2 => 2200, | |
| 755 | + 1 => 2000, | |
| 756 | + _ => 1800 | |
| 757 | + }; | |
| 758 | + | |
| 759 | + // 新店保底1星(2000元) | |
| 760 | + if (isNewStore && baseSalary < 2000) | |
| 417 | 761 | { |
| 418 | - case 3: return 2400; | |
| 419 | - case 2: return 2200; | |
| 420 | - case 1: return 2000; | |
| 421 | - default: return 1800; | |
| 762 | + baseSalary = 2000; | |
| 422 | 763 | } |
| 764 | + | |
| 765 | + return baseSalary; | |
| 423 | 766 | } |
| 424 | 767 | |
| 425 | 768 | /// <summary> |
| ... | ... | @@ -455,32 +798,76 @@ namespace NCC.Extend |
| 455 | 798 | /// <summary> |
| 456 | 799 | /// 计算顾问提成 |
| 457 | 800 | /// </summary> |
| 458 | - private decimal CalculateConsultantCommission(decimal teamPerformance, List<LqSalaryStatisticsEntity> teamMembers) | |
| 801 | + private decimal CalculateConsultantCommission(decimal teamPerformance, List<LqSalaryStatisticsEntity> teamMembers, bool isNewStore) | |
| 459 | 802 | { |
| 460 | 803 | // 顾问提成规则: |
| 461 | 804 | // 高级顾问:战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8% |
| 462 | 805 | // 普通顾问:战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3% |
| 463 | 806 | |
| 464 | - // 这里的“组员业绩达到X%以上”理解为:除顾问外的成员业绩占比?或者每个成员都达标? | |
| 465 | - // 通常理解为:团队中是否有成员业绩贡献较高,或者团队整体结构健康。 | |
| 466 | - // 假设“组员业绩达到X%”是指:团队中至少有一名成员(非顾问本人?)或者所有成员平均? | |
| 467 | - // 鉴于规则模糊,这里先简化实现:暂只考核总业绩和消耗。 | |
| 468 | - // 消耗是团队总消耗吗?假设是。 | |
| 807 | + // 注意: | |
| 808 | + // 1. "组员业绩"指除顾问外的其他成员业绩总和 | |
| 809 | + // 2. 只统计有效战队成员(考勤≥21天,未被剔除的成员) | |
| 810 | + // 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X% | |
| 811 | + // 4. 新店顾问不考核消耗 | |
| 469 | 812 | |
| 813 | + // 使用传入的 teamMembers 计算总消耗,或者直接使用 TeamTotalConsumption (如果已计算) | |
| 814 | + // 但为了保险起见,这里重新计算或使用已有的逻辑 | |
| 815 | + // 注意:CalculateConsultantCommission 方法签名未变,但逻辑需确认 teamConsumption 来源 | |
| 816 | + // 在调用此方法前,teamMembers 已经有了 Consumption 数据 | |
| 470 | 817 | var teamConsumption = teamMembers.Sum(x => x.Consumption); |
| 471 | 818 | |
| 472 | - // 高级顾问 | |
| 473 | - if (teamPerformance >= 60000 && teamConsumption >= 60000) | |
| 819 | + // 计算组员(非顾问)业绩总和 | |
| 820 | + // teamMembers 已经是过滤后的有效成员列表(GoldTriangleId 相同且未被剔除) | |
| 821 | + var memberPerformance = teamMembers.Where(x => x.Position != "顾问").Sum(x => x.TotalPerformance); | |
| 822 | + | |
| 823 | + // 高级顾问:业绩≥6万 且 组员业绩≥40% 且 (新店 或 消耗≥6万) | |
| 824 | + if (teamPerformance >= 60000 && memberPerformance >= teamPerformance * 0.4m) | |
| 474 | 825 | { |
| 475 | - return teamPerformance * 0.008m; | |
| 826 | + if (isNewStore || teamConsumption >= 60000) | |
| 827 | + { | |
| 828 | + return teamPerformance * 0.008m; | |
| 829 | + } | |
| 476 | 830 | } |
| 477 | - // 普通顾问 | |
| 478 | - if (teamPerformance >= 40000 && teamConsumption >= 40000) | |
| 831 | + | |
| 832 | + // 普通顾问:业绩≥4万 且 组员业绩≥30% 且 (新店 或 消耗≥4万) | |
| 833 | + if (teamPerformance >= 40000 && memberPerformance >= teamPerformance * 0.3m) | |
| 479 | 834 | { |
| 480 | - return teamPerformance * 0.003m; | |
| 835 | + if (isNewStore || teamConsumption >= 40000) | |
| 836 | + { | |
| 837 | + return teamPerformance * 0.003m; | |
| 838 | + } | |
| 481 | 839 | } |
| 482 | 840 | |
| 483 | 841 | return 0; |
| 484 | 842 | } |
| 843 | + | |
| 844 | + /// <summary> | |
| 845 | + /// 计算新客转化率提成 | |
| 846 | + /// </summary> | |
| 847 | + private decimal CalculateNewCustomerConversionCommission(decimal newCustomerPerformance, decimal conversionRate) | |
| 848 | + { | |
| 849 | + decimal commissionRate = 0; | |
| 850 | + | |
| 851 | + if (conversionRate >= 0.5m) commissionRate = 0.20m; | |
| 852 | + else if (conversionRate >= 0.45m) commissionRate = 0.15m; | |
| 853 | + else if (conversionRate >= 0.35m) commissionRate = 0.10m; | |
| 854 | + else if (conversionRate >= 0) commissionRate = 0.06m; | |
| 855 | + | |
| 856 | + return newCustomerPerformance * commissionRate; | |
| 857 | + } | |
| 858 | + | |
| 859 | + /// <summary> | |
| 860 | + /// 计算升单人头提成 | |
| 861 | + /// </summary> | |
| 862 | + private decimal CalculateUpgradeCustomerCommission(decimal upgradePerformance, decimal upgradeCustomerCount) | |
| 863 | + { | |
| 864 | + decimal commissionRate = 0; | |
| 865 | + | |
| 866 | + if (upgradeCustomerCount >= 10) commissionRate = 0.20m; | |
| 867 | + else if (upgradeCustomerCount >= 4) commissionRate = 0.10m; | |
| 868 | + // 0-4个: 0% | |
| 869 | + | |
| 870 | + return upgradePerformance * commissionRate; | |
| 871 | + } | |
| 485 | 872 | } |
| 486 | 873 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
netcore/src/Modularity/System/NCC.System.Entitys/Entity/Permission/UserEntity.cs
| ... | ... | @@ -358,5 +358,11 @@ namespace NCC.System.Entitys.Permission |
| 358 | 358 | [SugarColumn(ColumnName = "F_GW")] |
| 359 | 359 | public string Gw { get; set; } |
| 360 | 360 | |
| 361 | + /// <summary> | |
| 362 | + /// 是否在职(1-在职,0-离职) | |
| 363 | + /// </summary> | |
| 364 | + [SugarColumn(ColumnName = "F_IsOnJob")] | |
| 365 | + public int? IsOnJob { get; set; } | |
| 366 | + | |
| 361 | 367 | } |
| 362 | 368 | } | ... | ... |
sql/创建主任工资统计表.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 创建主任工资统计表 | |
| 3 | +-- 功能:存储主任每月的工资计算数据,包括底薪、提成、考核扣款、扣款、补贴、奖金、支付等信息 | |
| 4 | +-- 创建时间:2025年 | |
| 5 | +-- ============================================ | |
| 6 | + | |
| 7 | +-- 删除表(如果存在) | |
| 8 | +DROP TABLE IF EXISTS lq_director_salary_statistics; | |
| 9 | + | |
| 10 | +-- ============================================ | |
| 11 | +-- 创建主任工资统计表 | |
| 12 | +-- ============================================ | |
| 13 | +CREATE TABLE lq_director_salary_statistics ( | |
| 14 | + -- 主键 | |
| 15 | + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID', | |
| 16 | + | |
| 17 | + -- 一、基础信息字段 | |
| 18 | + F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID', | |
| 19 | + F_StoreName VARCHAR(200) NOT NULL COMMENT '门店名称', | |
| 20 | + F_Position VARCHAR(50) NOT NULL DEFAULT '主任' COMMENT '核算岗位(固定为"主任")', | |
| 21 | + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名', | |
| 22 | + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID', | |
| 23 | + F_StatisticsMonth VARCHAR(20) NOT NULL COMMENT '统计月份(YYYYMM格式)', | |
| 24 | + F_StoreType INT NULL COMMENT '门店类型(200平/旗舰店)', | |
| 25 | + F_StoreCategory INT NOT NULL COMMENT '门店分类(1=A类,2=B类,3=C类)', | |
| 26 | + F_IsNewStore VARCHAR(10) NULL COMMENT '是否新店(是/否)', | |
| 27 | + F_NewStoreProtectionStage INT NULL DEFAULT 0 COMMENT '新店保护阶段(0/1/2)', | |
| 28 | + | |
| 29 | + -- 二、业绩相关字段 | |
| 30 | + F_StoreTotalPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店总业绩(门店开单业绩-门店退卡业绩)', | |
| 31 | + F_StoreBillingPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店开单业绩', | |
| 32 | + F_StoreRefundPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店退卡业绩', | |
| 33 | + F_StoreLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店生命线', | |
| 34 | + F_PerformanceCompletionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '业绩完成率(门店业绩/门店生命线)', | |
| 35 | + | |
| 36 | + -- 三、考核相关字段 | |
| 37 | + F_PerformanceReached VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '业绩是否达标(是/否,业绩≥生命线为是)', | |
| 38 | + F_HeadCountReached VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '人头是否达标(是/否,实际人头≥目标人头为是)', | |
| 39 | + F_ConsumeReached VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '消耗是否达标(是/否,实际消耗≥目标消耗为是,仅老店考核)', | |
| 40 | + F_AssessmentDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '考核扣款金额(未达标指标数×500)', | |
| 41 | + F_UnreachedIndicatorCount INT NOT NULL DEFAULT 0 COMMENT '未达标指标数量(老店最多3个,新店最多2个)', | |
| 42 | + | |
| 43 | + -- 四、人头相关字段 | |
| 44 | + F_HeadCount INT NOT NULL DEFAULT 0 COMMENT '进店消耗人数(有消费金额的,按门店按月去重客户数)', | |
| 45 | + F_TargetHeadCount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '目标人头数(从lq_md_target获取)', | |
| 46 | + | |
| 47 | + -- 五、消耗相关字段 | |
| 48 | + F_StoreConsume DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店消耗金额(门店当月总消耗)', | |
| 49 | + F_TargetConsume DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '目标消耗金额(从lq_md_target获取)', | |
| 50 | + | |
| 51 | + -- 六、提成相关字段(阶梯提成) | |
| 52 | + F_CommissionRateBelowLifeline DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '≤生命线部分提成比例(老店:A类2%,B类2.5%,C类3%;新店:统一2%)', | |
| 53 | + F_CommissionRateAboveLifeline DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '>生命线部分提成比例(老店:A类2.5%,B类3%,C类3.5%;新店:统一2.5%)', | |
| 54 | + F_CommissionAmountBelowLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '≤生命线部分提成金额', | |
| 55 | + F_CommissionAmountAboveLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '>生命线部分提成金额', | |
| 56 | + F_TotalCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成总金额(≤生命线部分+>生命线部分)', | |
| 57 | + | |
| 58 | + -- 七、底薪相关字段 | |
| 59 | + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 3500.00 COMMENT '底薪金额(固定3500元)', | |
| 60 | + F_ActualBaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实际底薪(底薪-考核扣款)', | |
| 61 | + | |
| 62 | + -- 八、考勤相关字段 | |
| 63 | + F_WorkingDays INT NOT NULL DEFAULT 0 COMMENT '在店天数', | |
| 64 | + F_LeaveDays INT NOT NULL DEFAULT 0 COMMENT '请假天数', | |
| 65 | + | |
| 66 | + -- 九、工资计算字段 | |
| 67 | + F_GrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '应发工资(实际底薪+提成总金额)', | |
| 68 | + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(应发工资-扣款合计+补贴合计+奖金)', | |
| 69 | + | |
| 70 | + -- 十、扣款相关字段 | |
| 71 | + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款', | |
| 72 | + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款', | |
| 73 | + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款', | |
| 74 | + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保', | |
| 75 | + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励', | |
| 76 | + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费', | |
| 77 | + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用', | |
| 78 | + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用', | |
| 79 | + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计', | |
| 80 | + | |
| 81 | + -- 十一、补贴相关字段 | |
| 82 | + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴', | |
| 83 | + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴', | |
| 84 | + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴', | |
| 85 | + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴', | |
| 86 | + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计', | |
| 87 | + | |
| 88 | + -- 十二、奖金相关字段 | |
| 89 | + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金', | |
| 90 | + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金', | |
| 91 | + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金', | |
| 92 | + | |
| 93 | + -- 十三、支付相关字段 | |
| 94 | + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)', | |
| 95 | + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额', | |
| 96 | + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额', | |
| 97 | + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月', | |
| 98 | + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额', | |
| 99 | + | |
| 100 | + -- 十四、系统字段 | |
| 101 | + F_IsLocked 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_store_category` (F_StoreCategory), | |
| 118 | + KEY `idx_create_time` (F_CreateTime) | |
| 119 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主任工资统计表'; | |
| 120 | + | |
| 121 | +-- ============================================ | |
| 122 | +-- 表结构说明 | |
| 123 | +-- ============================================ | |
| 124 | +/* | |
| 125 | +表名:lq_director_salary_statistics(主任工资统计表) | |
| 126 | + | |
| 127 | +功能说明: | |
| 128 | +1. 存储主任每月的工资计算数据 | |
| 129 | +2. 包括底薪、提成、考核扣款、扣款、补贴、奖金、支付等信息 | |
| 130 | +3. 支持按门店、员工、月份查询 | |
| 131 | + | |
| 132 | +主要字段说明: | |
| 133 | +- F_StoreCategory:门店分类(1=A类,2=B类,3=C类),用于确定提成比例 | |
| 134 | +- F_StoreTotalPerformance:门店总业绩,用于计算提成 | |
| 135 | +- F_StoreLifeline:门店生命线,用于判断提成比例和考核 | |
| 136 | +- F_HeadCount:进店消耗人数,用于考核 | |
| 137 | +- F_StoreConsume:门店消耗金额,用于考核(仅老店) | |
| 138 | +- F_BaseSalary:底薪(固定3500元) | |
| 139 | +- F_AssessmentDeduction:考核扣款(未达标指标数×500) | |
| 140 | +- F_ActualBaseSalary:实际底薪(底薪-考核扣款) | |
| 141 | +- F_CommissionAmountBelowLifeline:≤生命线部分提成金额 | |
| 142 | +- F_CommissionAmountAboveLifeline:>生命线部分提成金额 | |
| 143 | +- F_TotalCommissionAmount:提成总金额(阶梯提成) | |
| 144 | + | |
| 145 | +索引说明: | |
| 146 | +- 主键索引:F_Id | |
| 147 | +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录) | |
| 148 | +- 普通索引: | |
| 149 | + - F_StoreId:按门店查询 | |
| 150 | + - F_StatisticsMonth:按月份查询 | |
| 151 | + - F_EmployeeId:按员工查询 | |
| 152 | + - F_StoreCategory:按门店分类查询 | |
| 153 | + - F_CreateTime:按创建时间查询 | |
| 154 | + | |
| 155 | +数据校验要求: | |
| 156 | +1. 门店分类(F_StoreCategory)必须设置,不允许为NULL | |
| 157 | +2. 门店生命线(F_StoreLifeline)必须设置,未设置应报错 | |
| 158 | +3. 目标人头数(F_TargetHeadCount)必须设置(用于考核) | |
| 159 | +4. 目标消耗(F_TargetConsume)必须设置(老店考核用) | |
| 160 | + | |
| 161 | +计算公式: | |
| 162 | +- 应发工资 = 实际底薪 + 提成总金额 | |
| 163 | +- 实发工资 = 应发工资 - 扣款合计 + 补贴合计 + 奖金 | |
| 164 | +- 业绩完成率 = 门店业绩 / 门店生命线 × 100% | |
| 165 | +- 实际底薪 = 底薪(3500)- 考核扣款 | |
| 166 | +- 考核扣款 = 未达标指标数 × 500 | |
| 167 | +- 提成总金额 = ≤生命线部分提成金额 + >生命线部分提成金额 | |
| 168 | + | |
| 169 | +提成计算规则(阶梯提成): | |
| 170 | +老店: | |
| 171 | +- A类门店:≤生命线部分2%,>生命线部分2.5% | |
| 172 | +- B类门店:≤生命线部分2.5%,>生命线部分3% | |
| 173 | +- C类门店:≤生命线部分3%,>生命线部分3.5% | |
| 174 | + | |
| 175 | +新店(统一标准): | |
| 176 | +- ≤生命线部分2%,>生命线部分2.5% | |
| 177 | + | |
| 178 | +考核规则: | |
| 179 | +老店(3个指标): | |
| 180 | +- 业绩考核:门店业绩是否达到门店生命线 | |
| 181 | +- 人头考核:进店消耗人数是否达到目标人头数 | |
| 182 | +- 消耗考核:门店消耗是否达到目标消耗 | |
| 183 | +- 每个未达标指标扣500元 | |
| 184 | + | |
| 185 | +新店(2个指标): | |
| 186 | +- 业绩考核:门店业绩是否达到门店生命线 | |
| 187 | +- 人头考核:进店消耗人数是否达到目标人头数 | |
| 188 | +- 每个未达标指标扣500元 | |
| 189 | +- 新店不考核消耗 | |
| 190 | +*/ | |
| 191 | + | ... | ... |
sql/创建健康师工资额外计算表.sql
0 → 100644
| 1 | +-- 创建健康师工资额外计算表 | |
| 2 | +CREATE TABLE IF NOT EXISTS `lq_salary_extra_calculation` ( | |
| 3 | + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID', | |
| 4 | + `F_EmployeeId` VARCHAR(50) NULL COMMENT '健康师ID', | |
| 5 | + `F_Year` INT NULL COMMENT '年份', | |
| 6 | + `F_Month` INT NULL COMMENT '月份', | |
| 7 | + `F_BaseRewardPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '基础奖励业绩', | |
| 8 | + `F_CooperationRewardPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合作奖励业绩', | |
| 9 | + `F_NewCustomerPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '新客业绩', | |
| 10 | + `F_NewCustomerConversionRate` DECIMAL(18,4) DEFAULT 0.0000 COMMENT '新客成交率', | |
| 11 | + `F_UpgradePerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '升单业绩', | |
| 12 | + `F_UpgradeConversionRate` DECIMAL(18,4) DEFAULT 0.0000 COMMENT '升单成交率', | |
| 13 | + PRIMARY KEY (`F_Id`), | |
| 14 | + KEY `idx_employee_year_month` (`F_EmployeeId`, `F_Year`, `F_Month`), | |
| 15 | + KEY `idx_year_month` (`F_Year`, `F_Month`) | |
| 16 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康师工资额外计算表'; | |
| 17 | + | ... | ... |
sql/创建店助工资统计表.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 创建店助工资统计表 | |
| 3 | +-- 功能:存储店助每月的工资计算数据,包括底薪、提成、阶段奖励、扣款、补贴、奖金、支付等信息 | |
| 4 | +-- 创建时间:2025年 | |
| 5 | +-- ============================================ | |
| 6 | + | |
| 7 | +-- 删除表(如果存在) | |
| 8 | +DROP TABLE IF EXISTS lq_assistant_salary_statistics; | |
| 9 | + | |
| 10 | +-- ============================================ | |
| 11 | +-- 创建店助工资统计表 | |
| 12 | +-- ============================================ | |
| 13 | +CREATE TABLE lq_assistant_salary_statistics ( | |
| 14 | + -- 主键 | |
| 15 | + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID', | |
| 16 | + | |
| 17 | + -- 一、基础信息字段 | |
| 18 | + F_StoreId VARCHAR(50) NOT NULL COMMENT '门店ID', | |
| 19 | + F_StoreName VARCHAR(200) NOT NULL COMMENT '门店名称', | |
| 20 | + F_Position VARCHAR(50) NOT NULL COMMENT '核算岗位(店助/店助主任)', | |
| 21 | + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名', | |
| 22 | + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID', | |
| 23 | + F_StatisticsMonth VARCHAR(20) NOT NULL COMMENT '统计月份(YYYYMM格式)', | |
| 24 | + F_StoreType INT NULL COMMENT '门店类型(200平/旗舰店)', | |
| 25 | + F_StoreCategory INT NOT NULL COMMENT '门店分类(1=A类,2=B类,3=C类)', | |
| 26 | + F_IsNewStore VARCHAR(10) NULL COMMENT '是否新店(是/否)', | |
| 27 | + F_NewStoreProtectionStage INT NULL DEFAULT 0 COMMENT '新店保护阶段(0/1/2)', | |
| 28 | + | |
| 29 | + -- 二、业绩相关字段 | |
| 30 | + F_StoreTotalPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店总业绩(门店开单业绩-门店退卡业绩)', | |
| 31 | + F_StoreBillingPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店开单业绩', | |
| 32 | + F_StoreRefundPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店退卡业绩', | |
| 33 | + F_StoreLifeline DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '门店生命线', | |
| 34 | + F_PerformanceCompletionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '业绩完成率(门店业绩/门店生命线)', | |
| 35 | + | |
| 36 | + -- 三、提成相关字段 | |
| 37 | + F_CommissionRate DECIMAL(18,4) NOT NULL DEFAULT 0.0000 COMMENT '提成比例(0%/0.4%/0.6%)', | |
| 38 | + F_CommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成金额(门店业绩×提成比例)', | |
| 39 | + | |
| 40 | + -- 四、阶段奖励相关字段 | |
| 41 | + F_HeadCount INT NOT NULL DEFAULT 0 COMMENT '进店消耗人数(有消费金额的,按门店按月去重客户数)', | |
| 42 | + F_Stage1TargetHeadCount INT NOT NULL DEFAULT 0 COMMENT '第一阶段目标人数', | |
| 43 | + F_Stage2TargetHeadCount INT NOT NULL DEFAULT 0 COMMENT '第二阶段目标人数', | |
| 44 | + F_ReachedStage1 VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '是否达到第一阶段(是/否)', | |
| 45 | + F_ReachedStage2 VARCHAR(10) NOT NULL DEFAULT '否' COMMENT '是否达到第二阶段(是/否)', | |
| 46 | + F_StageRewardAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '阶段奖励金额(0/200/400元)', | |
| 47 | + F_Stage1Reward DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '第一阶段奖励金额(0或200元)', | |
| 48 | + F_Stage2Reward DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '第二阶段奖励金额(0或200元)', | |
| 49 | + | |
| 50 | + -- 五、底薪相关字段 | |
| 51 | + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '底薪金额(A类3000,B类3100,C类3200)', | |
| 52 | + | |
| 53 | + -- 六、固定奖励字段 | |
| 54 | + F_PhoneManagementFee DECIMAL(18,2) NOT NULL DEFAULT 150.00 COMMENT '手机管理费(固定150元/月)', | |
| 55 | + | |
| 56 | + -- 七、考勤相关字段 | |
| 57 | + F_WorkingDays INT NOT NULL DEFAULT 0 COMMENT '在店天数', | |
| 58 | + F_LeaveDays INT NOT NULL DEFAULT 0 COMMENT '请假天数', | |
| 59 | + | |
| 60 | + -- 八、工资计算字段 | |
| 61 | + F_GrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '应发工资(底薪+提成+阶段奖励+固定奖励)', | |
| 62 | + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(应发工资-扣款合计+补贴合计+奖金)', | |
| 63 | + | |
| 64 | + -- 九、扣款相关字段 | |
| 65 | + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款', | |
| 66 | + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款', | |
| 67 | + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款', | |
| 68 | + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保', | |
| 69 | + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励', | |
| 70 | + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费', | |
| 71 | + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用', | |
| 72 | + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用', | |
| 73 | + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计', | |
| 74 | + | |
| 75 | + -- 十、补贴相关字段 | |
| 76 | + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴', | |
| 77 | + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴', | |
| 78 | + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴', | |
| 79 | + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴', | |
| 80 | + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计', | |
| 81 | + | |
| 82 | + -- 十一、奖金相关字段 | |
| 83 | + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金', | |
| 84 | + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金', | |
| 85 | + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金', | |
| 86 | + | |
| 87 | + -- 十二、支付相关字段 | |
| 88 | + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)', | |
| 89 | + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额', | |
| 90 | + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额', | |
| 91 | + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月', | |
| 92 | + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额', | |
| 93 | + | |
| 94 | + -- 十三、系统字段 | |
| 95 | + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)', | |
| 96 | + F_CreateTime DATETIME NOT NULL COMMENT '创建时间', | |
| 97 | + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间', | |
| 98 | + F_CreateUser VARCHAR(50) NULL COMMENT '创建人', | |
| 99 | + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人', | |
| 100 | + | |
| 101 | + -- 主键约束 | |
| 102 | + PRIMARY KEY (F_Id), | |
| 103 | + | |
| 104 | + -- 唯一索引:确保同一员工同一月份只有一条记录 | |
| 105 | + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth), | |
| 106 | + | |
| 107 | + -- 普通索引 | |
| 108 | + KEY `idx_store_id` (F_StoreId), | |
| 109 | + KEY `idx_statistics_month` (F_StatisticsMonth), | |
| 110 | + KEY `idx_employee_id` (F_EmployeeId), | |
| 111 | + KEY `idx_store_category` (F_StoreCategory), | |
| 112 | + KEY `idx_create_time` (F_CreateTime) | |
| 113 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店助工资统计表'; | |
| 114 | + | |
| 115 | +-- ============================================ | |
| 116 | +-- 表结构说明 | |
| 117 | +-- ============================================ | |
| 118 | +/* | |
| 119 | +表名:lq_assistant_salary_statistics(店助工资统计表) | |
| 120 | + | |
| 121 | +功能说明: | |
| 122 | +1. 存储店助每月的工资计算数据 | |
| 123 | +2. 包括底薪、提成、阶段奖励、扣款、补贴、奖金、支付等信息 | |
| 124 | +3. 支持按门店、员工、月份查询 | |
| 125 | + | |
| 126 | +主要字段说明: | |
| 127 | +- F_StoreCategory:门店分类(1=A类,2=B类,3=C类),用于确定底薪 | |
| 128 | +- F_StoreTotalPerformance:门店总业绩,用于计算提成 | |
| 129 | +- F_StoreLifeline:门店生命线,用于判断提成比例 | |
| 130 | +- F_HeadCount:进店消耗人数,用于判断阶段奖励 | |
| 131 | +- F_Stage1TargetHeadCount/F_Stage2TargetHeadCount:阶段目标人数 | |
| 132 | +- F_BaseSalary:底薪(A类3000,B类3100,C类3200) | |
| 133 | +- F_CommissionAmount:提成金额(门店业绩×提成比例) | |
| 134 | +- F_StageRewardAmount:阶段奖励金额(0/200/400元) | |
| 135 | +- F_PhoneManagementFee:手机管理费(固定150元/月) | |
| 136 | + | |
| 137 | +索引说明: | |
| 138 | +- 主键索引:F_Id | |
| 139 | +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录) | |
| 140 | +- 普通索引: | |
| 141 | + - F_StoreId:按门店查询 | |
| 142 | + - F_StatisticsMonth:按月份查询 | |
| 143 | + - F_EmployeeId:按员工查询 | |
| 144 | + - F_StoreCategory:按门店分类查询 | |
| 145 | + - F_CreateTime:按创建时间查询 | |
| 146 | + | |
| 147 | +数据校验要求: | |
| 148 | +1. 门店分类(F_StoreCategory)必须设置,不允许为NULL | |
| 149 | +2. 门店生命线(F_StoreLifeline)必须设置,未设置应报错 | |
| 150 | +3. 阶段目标(F_Stage1TargetHeadCount、F_Stage2TargetHeadCount)必须设置,未设置应报错 | |
| 151 | + | |
| 152 | +计算公式: | |
| 153 | +- 应发工资 = 底薪 + 提成金额 + 阶段奖励金额 + 手机管理费 | |
| 154 | +- 实发工资 = 应发工资 - 扣款合计 + 补贴合计 + 奖金 | |
| 155 | +- 业绩完成率 = 门店业绩 / 门店生命线 × 100% | |
| 156 | +- 提成比例:根据门店业绩与门店生命线的比例确定(0%/0.4%/0.6%) | |
| 157 | +- 阶段奖励:根据进店消耗人数是否达到阶段目标(0/200/400元) | |
| 158 | +*/ | |
| 159 | + | ... | ... |
sql/创建报销多级审批流程表.sql
0 → 100644
| 1 | +-- 报销多级审批流程改造 - 数据库表结构 | |
| 2 | +-- 执行时间:2024年 | |
| 3 | +-- 说明:支持3-5个动态审批节点,每个报销申请在创建时设置节点和审批人,支持通过/不通过/退回操作 | |
| 4 | + | |
| 5 | +-- 1. 报销申请节点表(每个报销申请的节点配置) | |
| 6 | +CREATE TABLE IF NOT EXISTS `lq_reimbursement_application_node` ( | |
| 7 | + `F_Id` varchar(50) NOT NULL COMMENT '节点编号', | |
| 8 | + `F_ApplicationId` varchar(50) NOT NULL COMMENT '报销申请ID', | |
| 9 | + `F_NodeOrder` int NOT NULL COMMENT '节点顺序(1,2,3,4,5)', | |
| 10 | + `F_NodeName` varchar(100) DEFAULT NULL COMMENT '节点名称', | |
| 11 | + `F_ApprovalType` varchar(20) DEFAULT '会签' COMMENT '审批类型(会签/或签)', | |
| 12 | + `F_IsRequired` int DEFAULT 1 COMMENT '是否必审(1-必审,0-可选)', | |
| 13 | + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |
| 14 | + PRIMARY KEY (`F_Id`), | |
| 15 | + KEY `idx_application_id` (`F_ApplicationId`), | |
| 16 | + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`) | |
| 17 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='报销申请节点表'; | |
| 18 | + | |
| 19 | +-- 2. 报销申请节点审批人表(每个节点的审批人,在创建报销申请时指定) | |
| 20 | +CREATE TABLE IF NOT EXISTS `lq_reimbursement_application_node_user` ( | |
| 21 | + `F_Id` varchar(50) NOT NULL COMMENT '记录编号', | |
| 22 | + `F_ApplicationId` varchar(50) NOT NULL COMMENT '报销申请ID', | |
| 23 | + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号', | |
| 24 | + `F_NodeOrder` int NOT NULL COMMENT '节点顺序(1,2,3,4,5)', | |
| 25 | + `F_UserId` varchar(50) NOT NULL COMMENT '审批人ID', | |
| 26 | + `F_UserName` varchar(100) DEFAULT NULL COMMENT '审批人姓名', | |
| 27 | + `F_SortOrder` int DEFAULT 0 COMMENT '排序', | |
| 28 | + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |
| 29 | + PRIMARY KEY (`F_Id`), | |
| 30 | + KEY `idx_application_id` (`F_ApplicationId`), | |
| 31 | + KEY `idx_node_id` (`F_NodeId`), | |
| 32 | + KEY `idx_user_id` (`F_UserId`), | |
| 33 | + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`), | |
| 34 | + UNIQUE KEY `uk_application_node_user` (`F_ApplicationId`, `F_NodeId`, `F_UserId`) | |
| 35 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='报销申请节点审批人表'; | |
| 36 | + | |
| 37 | +-- 3. 审批记录表 | |
| 38 | +CREATE TABLE IF NOT EXISTS `lq_reimbursement_approval_record` ( | |
| 39 | + `F_Id` varchar(50) NOT NULL COMMENT '记录编号', | |
| 40 | + `F_ApplicationId` varchar(50) NOT NULL COMMENT '报销申请ID', | |
| 41 | + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号', | |
| 42 | + `F_NodeOrder` int NOT NULL COMMENT '节点顺序(1,2,3,4,5)', | |
| 43 | + `F_ApproverId` varchar(50) NOT NULL COMMENT '审批人ID', | |
| 44 | + `F_ApproverName` varchar(100) DEFAULT NULL COMMENT '审批人姓名', | |
| 45 | + `F_ApprovalResult` varchar(20) NOT NULL COMMENT '审批结果(通过/不通过/退回)', | |
| 46 | + `F_ApprovalOpinion` varchar(500) DEFAULT NULL COMMENT '审批意见', | |
| 47 | + `F_ApprovalTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '审批时间', | |
| 48 | + `F_IsCurrentNode` int DEFAULT 0 COMMENT '是否当前节点(1-是,0-否)', | |
| 49 | + PRIMARY KEY (`F_Id`), | |
| 50 | + KEY `idx_application_id` (`F_ApplicationId`), | |
| 51 | + KEY `idx_node_id` (`F_NodeId`), | |
| 52 | + KEY `idx_approver_id` (`F_ApproverId`), | |
| 53 | + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`) | |
| 54 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='审批记录表'; | |
| 55 | + | |
| 56 | +-- 4. 修改报销申请表,添加新字段 | |
| 57 | +ALTER TABLE `lq_reimbursement_application` | |
| 58 | + ADD COLUMN `F_NodeCount` int DEFAULT 0 COMMENT '节点数量(3,4,5)' AFTER `F_PurchaseRecordsId`, | |
| 59 | + ADD COLUMN `F_CurrentNodeOrder` int DEFAULT 0 COMMENT '当前审批节点(0-待审批,1-N节点,N+1-已完成)' AFTER `F_NodeCount`, | |
| 60 | + ADD COLUMN `F_CurrentNodeId` varchar(50) DEFAULT NULL COMMENT '当前节点ID' AFTER `F_CurrentNodeOrder`, | |
| 61 | + ADD COLUMN `F_ApprovalStatus` varchar(20) DEFAULT '待审批' COMMENT '审批状态(待审批/审批中/已通过/未通过/已退回)' AFTER `F_CurrentNodeId`, | |
| 62 | + ADD COLUMN `F_ReturnedNodeOrder` int DEFAULT NULL COMMENT '退回节点' AFTER `F_ApprovalStatus`, | |
| 63 | + ADD COLUMN `F_ReturnedReason` varchar(500) DEFAULT NULL COMMENT '退回原因' AFTER `F_ReturnedNodeOrder`, | |
| 64 | + ADD KEY `idx_current_node` (`F_CurrentNodeId`), | |
| 65 | + ADD KEY `idx_approval_status` (`F_ApprovalStatus`), | |
| 66 | + ADD KEY `idx_node_count` (`F_NodeCount`); | |
| 67 | + | |
| 68 | +-- 5. 数据迁移:更新已有数据的审批状态 | |
| 69 | +-- 已审批的数据(假设为4个节点) | |
| 70 | +UPDATE `lq_reimbursement_application` | |
| 71 | +SET `F_NodeCount` = 4, | |
| 72 | + `F_CurrentNodeOrder` = 5, | |
| 73 | + `F_ApprovalStatus` = '已通过' | |
| 74 | +WHERE `F_ApproveStatus` = '已审批'; | |
| 75 | + | |
| 76 | +-- 未通过的数据(假设为4个节点) | |
| 77 | +UPDATE `lq_reimbursement_application` | |
| 78 | +SET `F_NodeCount` = 4, | |
| 79 | + `F_CurrentNodeOrder` = 1, | |
| 80 | + `F_ApprovalStatus` = '未通过' | |
| 81 | +WHERE `F_ApproveStatus` = '未通过'; | |
| 82 | + | |
| 83 | +-- 待审批的数据 | |
| 84 | +UPDATE `lq_reimbursement_application` | |
| 85 | +SET `F_NodeCount` = 4, -- 默认4个节点,实际使用时需要根据具体情况设置 | |
| 86 | + `F_CurrentNodeOrder` = 0, | |
| 87 | + `F_ApprovalStatus` = '待审批' | |
| 88 | +WHERE `F_ApproveStatus` = '待审批' OR `F_ApproveStatus` IS NULL; | |
| 89 | + | ... | ... |
sql/添加BASE_USER表是否在职字段.sql
0 → 100644
| 1 | +-- 在 BASE_USER 表中添加是否在职字段 | |
| 2 | +-- 执行时间:2024年 | |
| 3 | +-- 说明:添加 F_IsOnJob 字段,用于标识员工是否在职(1-在职,0-离职) | |
| 4 | + | |
| 5 | +ALTER TABLE BASE_USER | |
| 6 | +ADD COLUMN F_IsOnJob INT DEFAULT 1 COMMENT '是否在职(1-在职,0-离职)' AFTER F_GW; | |
| 7 | + | |
| 8 | +-- 更新现有数据:默认所有用户为在职状态(如果字段已存在,此语句会报错,可忽略) | |
| 9 | +-- UPDATE BASE_USER SET F_IsOnJob = 1 WHERE F_IsOnJob IS NULL; | |
| 10 | + | ... | ... |
sql/添加健康师工资表其他业绩字段.sql
0 → 100644
| 1 | +-- 在健康师工资表中添加其他业绩加和其他业绩减字段 | |
| 2 | + | |
| 3 | +-- 添加其他业绩加字段 | |
| 4 | +ALTER TABLE `lq_salary_statistics` | |
| 5 | +ADD COLUMN `F_OtherPerformanceAdd` DECIMAL(18,2) DEFAULT 0.00 COMMENT '其他业绩加' AFTER `F_UpgradePerformanceCommission`; | |
| 6 | + | |
| 7 | +-- 添加其他业绩减字段 | |
| 8 | +ALTER TABLE `lq_salary_statistics` | |
| 9 | +ADD COLUMN `F_OtherPerformanceSubtract` DECIMAL(18,2) DEFAULT 0.00 COMMENT '其他业绩减' AFTER `F_OtherPerformanceAdd`; | |
| 10 | + | ... | ... |
sql/添加健康师工资表升单人头数和提成金额字段.sql
0 → 100644
| 1 | +-- 在健康师工资表中添加升单人头数、新客业绩提成金额、升单业绩提成金额字段 | |
| 2 | + | |
| 3 | +-- 添加升单人头数字段(放在F_NewCustomerPoint字段后面) | |
| 4 | +ALTER TABLE `lq_salary_statistics` | |
| 5 | +ADD COLUMN `F_UpgradeCustomerCount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '升单人头数' AFTER `F_NewCustomerPoint`; | |
| 6 | + | |
| 7 | +-- 添加新客业绩提成金额字段 | |
| 8 | +ALTER TABLE `lq_salary_statistics` | |
| 9 | +ADD COLUMN `F_NewCustomerPerformanceCommission` DECIMAL(18,2) DEFAULT 0.00 COMMENT '新客业绩提成金额' AFTER `F_UpgradePoint`; | |
| 10 | + | |
| 11 | +-- 添加升单业绩提成金额字段 | |
| 12 | +ALTER TABLE `lq_salary_statistics` | |
| 13 | +ADD COLUMN `F_UpgradePerformanceCommission` DECIMAL(18,2) DEFAULT 0.00 COMMENT '升单业绩提成金额' AFTER `F_NewCustomerPerformanceCommission`; | |
| 14 | + | ... | ... |
sql/添加健康师工资表奖励业绩字段.sql
0 → 100644
| 1 | +-- 在健康师工资表中添加基础奖励业绩和合作奖励业绩字段 | |
| 2 | + | |
| 3 | +-- 添加基础奖励业绩字段 | |
| 4 | +ALTER TABLE `lq_salary_statistics` | |
| 5 | +ADD COLUMN `F_BaseRewardPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '基础奖励业绩' AFTER `F_CooperationPerformance`; | |
| 6 | + | |
| 7 | +-- 添加合作奖励业绩字段 | |
| 8 | +ALTER TABLE `lq_salary_statistics` | |
| 9 | +ADD COLUMN `F_CooperationRewardPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合作奖励业绩' AFTER `F_BaseRewardPerformance`; | |
| 10 | + | ... | ... |
sql/添加健康师工资表实际业绩字段.sql
0 → 100644
| 1 | +-- 在健康师工资表中添加实际基础业绩和实际合作业绩字段 | |
| 2 | + | |
| 3 | +-- 添加实际基础业绩字段 | |
| 4 | +ALTER TABLE `lq_salary_statistics` | |
| 5 | +ADD COLUMN `F_ActualBasePerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '实际基础业绩' AFTER `F_CooperationRewardPerformance`; | |
| 6 | + | |
| 7 | +-- 添加实际合作业绩字段 | |
| 8 | +ALTER TABLE `lq_salary_statistics` | |
| 9 | +ADD COLUMN `F_ActualCooperationPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '实际合作业绩' AFTER `F_ActualBasePerformance`; | |
| 10 | + | ... | ... |
sql/添加健康师工资额外计算表其他业绩字段.sql
0 → 100644
| 1 | +-- 在健康师工资额外计算表中添加其他业绩加和其他业绩减字段 | |
| 2 | + | |
| 3 | +-- 添加其他业绩加字段 | |
| 4 | +ALTER TABLE `lq_salary_extra_calculation` | |
| 5 | +ADD COLUMN `F_OtherPerformanceAdd` DECIMAL(18,2) DEFAULT 0.00 COMMENT '其他业绩加' AFTER `F_UpgradeCustomerCount`; | |
| 6 | + | |
| 7 | +-- 添加其他业绩减字段 | |
| 8 | +ALTER TABLE `lq_salary_extra_calculation` | |
| 9 | +ADD COLUMN `F_OtherPerformanceSubtract` DECIMAL(18,2) DEFAULT 0.00 COMMENT '其他业绩减' AFTER `F_OtherPerformanceAdd`; | |
| 10 | + | ... | ... |
sql/添加健康师工资额外计算表升单人头数字段.sql
0 → 100644
主任工资计算规则梳理.md
0 → 100644
| 1 | +# 主任工资计算规则梳理 | |
| 2 | + | |
| 3 | +## 📋 概述 | |
| 4 | + | |
| 5 | +主任工资由以下几个部分组成: | |
| 6 | +1. **底薪**:固定3500元,根据考核指标扣款 | |
| 7 | +2. **提成**:根据门店业绩与门店生命线的关系,使用阶梯提成模式计算 | |
| 8 | + | |
| 9 | +--- | |
| 10 | + | |
| 11 | +## 💰 工资组成规则 | |
| 12 | + | |
| 13 | +### 1. 底薪规则 | |
| 14 | + | |
| 15 | +**固定底薪**:3500元 | |
| 16 | + | |
| 17 | +#### 老店主任底薪考核 | |
| 18 | + | |
| 19 | +**考核指标**(3个): | |
| 20 | +1. **业绩考核**:门店业绩是否达到门店生命线 | |
| 21 | +2. **人头考核**:进店消耗人数是否达到目标人头数 | |
| 22 | +3. **消耗考核**:门店消耗是否达到目标消耗 | |
| 23 | + | |
| 24 | +**扣款规则**: | |
| 25 | +- 每个指标未达到:扣除500元 | |
| 26 | +- 如果3个指标都未达到:扣除1500元(500 × 3) | |
| 27 | + | |
| 28 | +**计算公式**: | |
| 29 | +``` | |
| 30 | +底薪 = 3500 - (未达标指标数 × 500) | |
| 31 | +``` | |
| 32 | + | |
| 33 | +#### 新店主任底薪考核 | |
| 34 | + | |
| 35 | +**考核指标**(2个): | |
| 36 | +1. **业绩考核**:门店业绩是否达到门店生命线 | |
| 37 | +2. **人头考核**:进店消耗人数是否达到目标人头数 | |
| 38 | + | |
| 39 | +**扣款规则**: | |
| 40 | +- 每个指标未达到:扣除500元 | |
| 41 | +- 如果2个指标都未达到:扣除1000元(500 × 2) | |
| 42 | + | |
| 43 | +**计算公式**: | |
| 44 | +``` | |
| 45 | +底薪 = 3500 - (未达标指标数 × 500) | |
| 46 | +``` | |
| 47 | + | |
| 48 | +**注意**:新店不考核消耗指标 | |
| 49 | + | |
| 50 | +--- | |
| 51 | + | |
| 52 | +### 2. 提成规则 | |
| 53 | + | |
| 54 | +**提成计算方式**:阶梯提成模式 | |
| 55 | + | |
| 56 | +#### 老店主任提成规则 | |
| 57 | + | |
| 58 | +根据门店分类(A、B、C类)和业绩是否超过生命线,使用不同的阶梯提成比例: | |
| 59 | + | |
| 60 | +| 门店分类 | 业绩 ≤ 生命线部分 | 业绩 > 生命线部分 | | |
| 61 | +|---------|----------------|-----------------| | |
| 62 | +| A类门店 | 2% | 2.5% | | |
| 63 | +| B类门店 | 2.5% | 3% | | |
| 64 | +| C类门店 | 3% | 3.5% | | |
| 65 | + | |
| 66 | +**计算公式**: | |
| 67 | +``` | |
| 68 | +如果 业绩 ≤ 生命线: | |
| 69 | + 提成 = 业绩 × 对应提成比例(≤生命线部分) | |
| 70 | + | |
| 71 | +如果 业绩 > 生命线: | |
| 72 | + 提成 = 生命线 × 对应提成比例(≤生命线部分) + (业绩 - 生命线) × 对应提成比例(>生命线部分) | |
| 73 | +``` | |
| 74 | + | |
| 75 | +**计算示例**: | |
| 76 | +- 生命线:100,000元 | |
| 77 | +- 业绩:150,000元 | |
| 78 | +- 门店分类:A类 | |
| 79 | + | |
| 80 | +计算过程: | |
| 81 | +1. 业绩(150,000)> 生命线(100,000) | |
| 82 | +2. ≤生命线部分:100,000 × 2% = 2,000元 | |
| 83 | +3. >生命线部分:(150,000 - 100,000) × 2.5% = 50,000 × 2.5% = 1,250元 | |
| 84 | +4. 总提成:2,000 + 1,250 = 3,250元 | |
| 85 | + | |
| 86 | +#### 新店主任提成规则 | |
| 87 | + | |
| 88 | +**统一标准**(不区分A、B、C类门店): | |
| 89 | + | |
| 90 | +| 业绩范围 | 提成比例 | | |
| 91 | +|---------|---------| | |
| 92 | +| 业绩 ≤ 生命线部分 | 2% | | |
| 93 | +| 业绩 > 生命线部分 | 2.5% | | |
| 94 | + | |
| 95 | +**计算公式**: | |
| 96 | +``` | |
| 97 | +如果 业绩 ≤ 生命线: | |
| 98 | + 提成 = 业绩 × 2% | |
| 99 | + | |
| 100 | +如果 业绩 > 生命线: | |
| 101 | + 提成 = 生命线 × 2% + (业绩 - 生命线) × 2.5% | |
| 102 | +``` | |
| 103 | + | |
| 104 | +**计算示例**: | |
| 105 | +- 生命线:100,000元 | |
| 106 | +- 业绩:150,000元 | |
| 107 | + | |
| 108 | +计算过程: | |
| 109 | +1. 业绩(150,000)> 生命线(100,000) | |
| 110 | +2. ≤生命线部分:100,000 × 2% = 2,000元 | |
| 111 | +3. >生命线部分:(150,000 - 100,000) × 2.5% = 50,000 × 2.5% = 1,250元 | |
| 112 | +4. 总提成:2,000 + 1,250 = 3,250元 | |
| 113 | + | |
| 114 | +--- | |
| 115 | + | |
| 116 | +## 📊 最终工资计算 | |
| 117 | + | |
| 118 | +### 计算公式 | |
| 119 | + | |
| 120 | +``` | |
| 121 | +最终工资 = 底薪 + 提成 | |
| 122 | +``` | |
| 123 | + | |
| 124 | +其中: | |
| 125 | +- **底薪** = 3500 - (未达标指标数 × 500) | |
| 126 | +- **提成** = 根据阶梯提成规则计算 | |
| 127 | + | |
| 128 | +### 计算示例 | |
| 129 | + | |
| 130 | +#### 示例1:老店主任(A类门店,全部达标) | |
| 131 | + | |
| 132 | +**基础数据**: | |
| 133 | +- 门店分类:A类 | |
| 134 | +- 门店生命线:100,000元 | |
| 135 | +- 门店业绩:150,000元 | |
| 136 | +- 目标人头:100人 | |
| 137 | +- 实际人头:120人 | |
| 138 | +- 目标消耗:80,000元 | |
| 139 | +- 实际消耗:90,000元 | |
| 140 | +- 是否新店:否 | |
| 141 | + | |
| 142 | +**计算过程**: | |
| 143 | + | |
| 144 | +1. **底薪计算**: | |
| 145 | + - 业绩考核:150,000 ≥ 100,000 ✓(达标) | |
| 146 | + - 人头考核:120 ≥ 100 ✓(达标) | |
| 147 | + - 消耗考核:90,000 ≥ 80,000 ✓(达标) | |
| 148 | + - 未达标指标数:0 | |
| 149 | + - 底薪 = 3500 - (0 × 500) = 3500元 | |
| 150 | + | |
| 151 | +2. **提成计算**: | |
| 152 | + - 业绩(150,000)> 生命线(100,000) | |
| 153 | + - ≤生命线部分:100,000 × 2% = 2,000元 | |
| 154 | + - >生命线部分:(150,000 - 100,000) × 2.5% = 1,250元 | |
| 155 | + - 总提成 = 2,000 + 1,250 = 3,250元 | |
| 156 | + | |
| 157 | +3. **最终工资**: | |
| 158 | + - 最终工资 = 3500 + 3250 = 6,750元 | |
| 159 | + | |
| 160 | +#### 示例2:老店主任(B类门店,部分未达标) | |
| 161 | + | |
| 162 | +**基础数据**: | |
| 163 | +- 门店分类:B类 | |
| 164 | +- 门店生命线:100,000元 | |
| 165 | +- 门店业绩:80,000元 | |
| 166 | +- 目标人头:100人 | |
| 167 | +- 实际人头:90人 | |
| 168 | +- 目标消耗:80,000元 | |
| 169 | +- 实际消耗:75,000元 | |
| 170 | +- 是否新店:否 | |
| 171 | + | |
| 172 | +**计算过程**: | |
| 173 | + | |
| 174 | +1. **底薪计算**: | |
| 175 | + - 业绩考核:80,000 < 100,000 ✗(未达标) | |
| 176 | + - 人头考核:90 < 100 ✗(未达标) | |
| 177 | + - 消耗考核:75,000 < 80,000 ✗(未达标) | |
| 178 | + - 未达标指标数:3 | |
| 179 | + - 底薪 = 3500 - (3 × 500) = 3500 - 1500 = 2000元 | |
| 180 | + | |
| 181 | +2. **提成计算**: | |
| 182 | + - 业绩(80,000)< 生命线(100,000) | |
| 183 | + - 提成 = 80,000 × 2.5% = 2,000元 | |
| 184 | + | |
| 185 | +3. **最终工资**: | |
| 186 | + - 最终工资 = 2000 + 2000 = 4,000元 | |
| 187 | + | |
| 188 | +#### 示例3:新店主任(全部达标) | |
| 189 | + | |
| 190 | +**基础数据**: | |
| 191 | +- 门店生命线:100,000元 | |
| 192 | +- 门店业绩:150,000元 | |
| 193 | +- 目标人头:100人 | |
| 194 | +- 实际人头:120人 | |
| 195 | +- 是否新店:是 | |
| 196 | + | |
| 197 | +**计算过程**: | |
| 198 | + | |
| 199 | +1. **底薪计算**: | |
| 200 | + - 业绩考核:150,000 ≥ 100,000 ✓(达标) | |
| 201 | + - 人头考核:120 ≥ 100 ✓(达标) | |
| 202 | + - 未达标指标数:0 | |
| 203 | + - 底薪 = 3500 - (0 × 500) = 3500元 | |
| 204 | + | |
| 205 | +2. **提成计算**: | |
| 206 | + - 业绩(150,000)> 生命线(100,000) | |
| 207 | + - ≤生命线部分:100,000 × 2% = 2,000元 | |
| 208 | + - >生命线部分:(150,000 - 100,000) × 2.5% = 1,250元 | |
| 209 | + - 总提成 = 2,000 + 1,250 = 3,250元 | |
| 210 | + | |
| 211 | +3. **最终工资**: | |
| 212 | + - 最终工资 = 3500 + 3250 = 6,750元 | |
| 213 | + | |
| 214 | +--- | |
| 215 | + | |
| 216 | +## 🔍 数据来源说明 | |
| 217 | + | |
| 218 | +### 1. 门店业绩 | |
| 219 | + | |
| 220 | +**数据来源**: | |
| 221 | +- 开单业绩:`lq_kd_kdjlb` 表 | |
| 222 | + - 字段:`Djmd`(门店ID)、`Sfyj`(实付业绩)、`Kdrq`(开单日期) | |
| 223 | + - 条件:`F_IsEffective = 1`(有效记录) | |
| 224 | +- 退卡业绩:`lq_hytk_hytk` 表 | |
| 225 | + - 字段:`Md`(门店ID)、`F_ActualRefundAmount`(实际退卡金额,优先使用,如果没有则用`Tkje`)、`Tksj`(退卡时间) | |
| 226 | + - 条件:`F_IsEffective = 1`(有效记录) | |
| 227 | + | |
| 228 | +**计算公式**: | |
| 229 | +``` | |
| 230 | +门店业绩 = SUM(开单实付) - SUM(退卡金额) | |
| 231 | +``` | |
| 232 | + | |
| 233 | +### 2. 门店生命线 | |
| 234 | + | |
| 235 | +**数据来源**:`lq_md_target` 表 | |
| 236 | +- 字段:`F_StoreLifeline`(门店生命线) | |
| 237 | +- 查询条件:`F_StoreId` = 门店ID,`F_Month` = 统计月份(YYYYMM格式) | |
| 238 | + | |
| 239 | +### 3. 门店分类 | |
| 240 | + | |
| 241 | +**数据来源**:`lq_mdxx` 表 | |
| 242 | +- 字段:`F_StoreCategory`(门店分类) | |
| 243 | + - `1` = A类门店 | |
| 244 | + - `2` = B类门店 | |
| 245 | + - `3` = C类门店 | |
| 246 | + | |
| 247 | +### 4. 人头数据(进店消耗人数) | |
| 248 | + | |
| 249 | +**数据来源**: | |
| 250 | +- 主表:`lq_xh_hyhk`(耗卡记录表) | |
| 251 | +- 关联表:`lq_xh_jksyj`(健康师业绩表,用于判断是否有消费金额) | |
| 252 | + | |
| 253 | +**统计规则**: | |
| 254 | +- 有消费金额的,按门店按月去重客户数 | |
| 255 | +- 只统计有效记录(`F_IsEffective = 1`) | |
| 256 | +- 只统计有消费金额的记录(`jksyj > 0`) | |
| 257 | + | |
| 258 | +**目标人头数**: | |
| 259 | +- 数据来源:`lq_md_target` 表 | |
| 260 | +- 字段:`F_StoreHeadcountTarget`(门店人头目标) | |
| 261 | + | |
| 262 | +### 5. 消耗数据 | |
| 263 | + | |
| 264 | +**数据来源**:`lq_xh_jksyj` 表 | |
| 265 | +- 字段:`jksyj`(健康师业绩,即消耗金额) | |
| 266 | +- 关联:通过 `glkdbh` 关联到 `lq_xh_hyhk.F_Id` | |
| 267 | +- 条件:`F_IsEffective = 1`(有效记录) | |
| 268 | + | |
| 269 | +**统计规则**: | |
| 270 | +- 按门店统计当月总消耗金额 | |
| 271 | + | |
| 272 | +**目标消耗**: | |
| 273 | +- 数据来源:`lq_md_target` 表 | |
| 274 | +- 字段:`F_StoreConsumeTarget`(门店消耗目标) | |
| 275 | + | |
| 276 | +### 6. 新店信息 | |
| 277 | + | |
| 278 | +**数据来源**:`lq_md_xdbhsj` 表 | |
| 279 | +- 字段: | |
| 280 | + - `Mdid`(门店ID) | |
| 281 | + - `Bhkssj`(保护开始时间) | |
| 282 | + - `Bhjssj`(保护结束时间) | |
| 283 | + - `Sfqy`(是否启用,1=启用) | |
| 284 | + - `Stage`(新店保护阶段) | |
| 285 | + | |
| 286 | +**判断逻辑**: | |
| 287 | +- 如果统计月份的第一天在保护期内(`Bhkssj <= startDate && Bhjssj >= startDate`),则为新店 | |
| 288 | +- 否则为老店 | |
| 289 | + | |
| 290 | +### 7. 主任员工信息 | |
| 291 | + | |
| 292 | +**数据来源**:`BASE_USER` 表 | |
| 293 | +- 字段: | |
| 294 | + - `F_Id`(员工ID) | |
| 295 | + - `F_RealName`(员工姓名) | |
| 296 | + - `F_Gw`(岗位,应为"主任") | |
| 297 | + - `F_Mdid`(门店ID) | |
| 298 | + | |
| 299 | +**查询条件**: | |
| 300 | +- `F_Gw = "主任"` | |
| 301 | +- `F_DeleteMark = null`(未删除) | |
| 302 | +- `F_EnabledMark = 1`(启用) | |
| 303 | + | |
| 304 | +--- | |
| 305 | + | |
| 306 | +## 📝 计算流程 | |
| 307 | + | |
| 308 | +### 1. 获取基础数据 | |
| 309 | + | |
| 310 | +- 从 `BASE_USER` 获取主任员工列表(岗位为"主任") | |
| 311 | +- 获取门店信息(`lq_mdxx`) | |
| 312 | +- 获取门店目标信息(`lq_md_target`) | |
| 313 | +- 获取新店保护信息(`lq_md_xdbhsj`) | |
| 314 | + | |
| 315 | +### 2. 计算门店业绩 | |
| 316 | + | |
| 317 | +- 统计开单业绩(`lq_kd_kdjlb`) | |
| 318 | +- 统计退卡业绩(`lq_hytk_hytk`) | |
| 319 | +- 计算门店总业绩 = 开单业绩 - 退卡业绩 | |
| 320 | + | |
| 321 | +### 3. 计算门店消耗 | |
| 322 | + | |
| 323 | +- 统计门店当月总消耗金额(`lq_xh_jksyj`) | |
| 324 | + | |
| 325 | +### 4. 统计进店消耗人数 | |
| 326 | + | |
| 327 | +- 统计有消费金额的,按门店按月去重客户数 | |
| 328 | + | |
| 329 | +### 5. 判断新店/老店 | |
| 330 | + | |
| 331 | +- 根据新店保护信息判断是否为新店 | |
| 332 | + | |
| 333 | +### 6. 计算底薪 | |
| 334 | + | |
| 335 | +- 判断业绩是否达标(业绩 ≥ 生命线) | |
| 336 | +- 判断人头是否达标(实际人头 ≥ 目标人头) | |
| 337 | +- 判断消耗是否达标(实际消耗 ≥ 目标消耗,仅老店考核) | |
| 338 | +- 根据未达标指标数量计算扣款 | |
| 339 | +- 底薪 = 3500 - (未达标指标数 × 500) | |
| 340 | + | |
| 341 | +### 7. 计算提成 | |
| 342 | + | |
| 343 | +- 判断新店/老店 | |
| 344 | +- 判断业绩是否超过生命线 | |
| 345 | +- 根据门店分类(老店)和业绩是否超过生命线,确定阶梯提成比例 | |
| 346 | +- 计算提成金额(阶梯提成模式) | |
| 347 | + | |
| 348 | +### 8. 计算最终工资 | |
| 349 | + | |
| 350 | +- 最终工资 = 底薪 + 提成 | |
| 351 | + | |
| 352 | +--- | |
| 353 | + | |
| 354 | +## ⚠️ 注意事项 | |
| 355 | + | |
| 356 | +### 1. 数据校验 | |
| 357 | + | |
| 358 | +- 门店分类必须设置,未设置应报错 | |
| 359 | +- 门店生命线必须设置,未设置应报错 | |
| 360 | +- 门店人头目标必须设置(用于考核) | |
| 361 | +- 门店消耗目标必须设置(老店考核用) | |
| 362 | + | |
| 363 | +### 2. 数据一致性 | |
| 364 | + | |
| 365 | +- 门店业绩计算逻辑必须与其他统计接口保持一致 | |
| 366 | +- 人头统计逻辑必须与其他统计接口保持一致 | |
| 367 | +- 消耗统计逻辑必须与其他统计接口保持一致 | |
| 368 | + | |
| 369 | +### 3. 边界情况 | |
| 370 | + | |
| 371 | +- 如果门店没有业绩数据,业绩为0 | |
| 372 | +- 如果门店没有人头数据,人头为0 | |
| 373 | +- 如果门店没有消耗数据,消耗为0 | |
| 374 | +- 新店不考核消耗,只考核业绩和人头 | |
| 375 | + | |
| 376 | +### 4. 提成计算注意事项 | |
| 377 | + | |
| 378 | +- **阶梯提成模式**:必须严格按照阶梯提成规则计算,不能使用单一提成比例 | |
| 379 | +- **业绩分段计算**: | |
| 380 | + - 如果业绩 ≤ 生命线:只计算 ≤ 生命线部分的提成 | |
| 381 | + - 如果业绩 > 生命线:分别计算 ≤ 生命线部分和 > 生命线部分的提成,然后相加 | |
| 382 | + | |
| 383 | +--- | |
| 384 | + | |
| 385 | +## 📋 关键计算公式总结 | |
| 386 | + | |
| 387 | +### 底薪计算公式 | |
| 388 | + | |
| 389 | +``` | |
| 390 | +底薪 = 3500 - (未达标指标数 × 500) | |
| 391 | +``` | |
| 392 | + | |
| 393 | +其中: | |
| 394 | +- **老店**:未达标指标数 = 未达标的指标数量(业绩、人头、消耗,最多3个) | |
| 395 | +- **新店**:未达标指标数 = 未达标的指标数量(业绩、人头,最多2个) | |
| 396 | + | |
| 397 | +### 提成计算公式 | |
| 398 | + | |
| 399 | +#### 老店提成计算公式 | |
| 400 | + | |
| 401 | +**A类门店**: | |
| 402 | +``` | |
| 403 | +如果 业绩 ≤ 生命线: | |
| 404 | + 提成 = 业绩 × 2% | |
| 405 | +如果 业绩 > 生命线: | |
| 406 | + 提成 = 生命线 × 2% + (业绩 - 生命线) × 2.5% | |
| 407 | +``` | |
| 408 | + | |
| 409 | +**B类门店**: | |
| 410 | +``` | |
| 411 | +如果 业绩 ≤ 生命线: | |
| 412 | + 提成 = 业绩 × 2.5% | |
| 413 | +如果 业绩 > 生命线: | |
| 414 | + 提成 = 生命线 × 2.5% + (业绩 - 生命线) × 3% | |
| 415 | +``` | |
| 416 | + | |
| 417 | +**C类门店**: | |
| 418 | +``` | |
| 419 | +如果 业绩 ≤ 生命线: | |
| 420 | + 提成 = 业绩 × 3% | |
| 421 | +如果 业绩 > 生命线: | |
| 422 | + 提成 = 生命线 × 3% + (业绩 - 生命线) × 3.5% | |
| 423 | +``` | |
| 424 | + | |
| 425 | +#### 新店提成计算公式 | |
| 426 | + | |
| 427 | +``` | |
| 428 | +如果 业绩 ≤ 生命线: | |
| 429 | + 提成 = 业绩 × 2% | |
| 430 | +如果 业绩 > 生命线: | |
| 431 | + 提成 = 生命线 × 2% + (业绩 - 生命线) × 2.5% | |
| 432 | +``` | |
| 433 | + | |
| 434 | +--- | |
| 435 | + | |
| 436 | +## ✅ 验证要点 | |
| 437 | + | |
| 438 | +1. **底薪计算验证**: | |
| 439 | + - 验证考核指标判断是否正确 | |
| 440 | + - 验证扣款金额是否正确(每个未达标指标扣500元) | |
| 441 | + - 验证新店不考核消耗 | |
| 442 | + | |
| 443 | +2. **提成计算验证**: | |
| 444 | + - 验证阶梯提成计算是否正确 | |
| 445 | + - 验证业绩分段计算是否正确 | |
| 446 | + - 验证不同门店分类的提成比例是否正确 | |
| 447 | + - 验证新店统一提成标准是否正确 | |
| 448 | + | |
| 449 | +3. **数据来源验证**: | |
| 450 | + - 验证门店业绩计算是否正确 | |
| 451 | + - 验证人头统计是否正确 | |
| 452 | + - 验证消耗统计是否正确 | |
| 453 | + - 验证新店判断是否正确 | |
| 454 | + | ... | ... |
健康师工资信息说明.html
0 → 100644
| 1 | +<!DOCTYPE html> | |
| 2 | +<html lang="zh-CN"> | |
| 3 | +<head> | |
| 4 | + <meta charset="UTF-8"> | |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| 6 | + <title>绿纤美业 - 健康师工资信息说明</title> | |
| 7 | + <style> | |
| 8 | + body { | |
| 9 | + font-family: 'Microsoft YaHei', Arial, sans-serif; | |
| 10 | + background-color: #f5f5f5; | |
| 11 | + margin: 0; | |
| 12 | + padding: 20px; | |
| 13 | + color: #333; | |
| 14 | + } | |
| 15 | + .container { | |
| 16 | + max-width: 1400px; | |
| 17 | + margin: 0 auto; | |
| 18 | + } | |
| 19 | + .salary-row { | |
| 20 | + display: flex; | |
| 21 | + gap: 20px; | |
| 22 | + margin-bottom: 30px; | |
| 23 | + page-break-inside: avoid; | |
| 24 | + } | |
| 25 | + .salary-card { | |
| 26 | + flex: 2; | |
| 27 | + background-color: #fff; | |
| 28 | + border-radius: 8px; | |
| 29 | + box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| 30 | + overflow: hidden; | |
| 31 | + } | |
| 32 | + .calc-details { | |
| 33 | + flex: 1; | |
| 34 | + background-color: #f9fbfc; | |
| 35 | + border-radius: 8px; | |
| 36 | + border: 1px solid #e1e4e8; | |
| 37 | + padding: 20px; | |
| 38 | + font-size: 13px; | |
| 39 | + } | |
| 40 | + .card-header { | |
| 41 | + background-color: #007bff; | |
| 42 | + color: #fff; | |
| 43 | + padding: 15px 20px; | |
| 44 | + display: flex; | |
| 45 | + justify-content: space-between; | |
| 46 | + align-items: center; | |
| 47 | + } | |
| 48 | + .card-header h2 { | |
| 49 | + margin: 0; | |
| 50 | + font-size: 18px; | |
| 51 | + } | |
| 52 | + .card-header .record-id { | |
| 53 | + font-size: 12px; | |
| 54 | + opacity: 0.8; | |
| 55 | + } | |
| 56 | + .section { | |
| 57 | + padding: 15px 20px; | |
| 58 | + border-bottom: 1px solid #eee; | |
| 59 | + } | |
| 60 | + .section-title { | |
| 61 | + font-weight: bold; | |
| 62 | + color: #007bff; | |
| 63 | + margin-bottom: 10px; | |
| 64 | + font-size: 14px; | |
| 65 | + border-left: 3px solid #007bff; | |
| 66 | + padding-left: 8px; | |
| 67 | + } | |
| 68 | + .grid { | |
| 69 | + display: grid; | |
| 70 | + grid-template-columns: repeat(3, 1fr); | |
| 71 | + gap: 8px 15px; | |
| 72 | + } | |
| 73 | + .item { | |
| 74 | + display: flex; | |
| 75 | + justify-content: space-between; | |
| 76 | + font-size: 12px; | |
| 77 | + border-bottom: 1px dashed #f0f0f0; | |
| 78 | + padding-bottom: 2px; | |
| 79 | + } | |
| 80 | + .item .label { | |
| 81 | + color: #666; | |
| 82 | + } | |
| 83 | + .item .value { | |
| 84 | + font-weight: 500; | |
| 85 | + } | |
| 86 | + .highlight { | |
| 87 | + color: #e74c3c; | |
| 88 | + font-weight: bold; | |
| 89 | + } | |
| 90 | + .total-section { | |
| 91 | + background-color: #f9f9f9; | |
| 92 | + padding: 15px 20px; | |
| 93 | + } | |
| 94 | + .total-row { | |
| 95 | + display: flex; | |
| 96 | + justify-content: space-between; | |
| 97 | + align-items: center; | |
| 98 | + margin-bottom: 5px; | |
| 99 | + } | |
| 100 | + .total-row.final { | |
| 101 | + margin-top: 10px; | |
| 102 | + padding-top: 10px; | |
| 103 | + border-top: 1px dashed #ccc; | |
| 104 | + font-size: 18px; | |
| 105 | + font-weight: bold; | |
| 106 | + color: #e74c3c; | |
| 107 | + } | |
| 108 | + .tag { | |
| 109 | + display: inline-block; | |
| 110 | + padding: 2px 6px; | |
| 111 | + border-radius: 4px; | |
| 112 | + font-size: 10px; | |
| 113 | + margin-left: 5px; | |
| 114 | + } | |
| 115 | + .tag-new { background-color: #2ecc71; color: #fff; } | |
| 116 | + .tag-warn { background-color: #f39c12; color: #fff; } | |
| 117 | + | |
| 118 | + .calc-title { | |
| 119 | + font-weight: bold; | |
| 120 | + color: #333; | |
| 121 | + margin-bottom: 10px; | |
| 122 | + font-size: 14px; | |
| 123 | + border-bottom: 1px solid #ddd; | |
| 124 | + padding-bottom: 5px; | |
| 125 | + } | |
| 126 | + .calc-item { | |
| 127 | + margin-bottom: 15px; | |
| 128 | + } | |
| 129 | + .calc-label { | |
| 130 | + font-weight: bold; | |
| 131 | + color: #555; | |
| 132 | + margin-bottom: 5px; | |
| 133 | + } | |
| 134 | + .calc-formula { | |
| 135 | + background-color: #fff; | |
| 136 | + padding: 8px; | |
| 137 | + border-radius: 4px; | |
| 138 | + border: 1px dashed #ccc; | |
| 139 | + color: #666; | |
| 140 | + font-family: Consolas, monospace; | |
| 141 | + word-break: break-all; | |
| 142 | + font-size: 12px; | |
| 143 | + } | |
| 144 | + .calc-note { | |
| 145 | + color: #888; | |
| 146 | + font-size: 11px; | |
| 147 | + margin-top: 3px; | |
| 148 | + } | |
| 149 | + | |
| 150 | + @media print { | |
| 151 | + body { background-color: #fff; } | |
| 152 | + .salary-card { box-shadow: none; border: 1px solid #ddd; } | |
| 153 | + .calc-details { border: 1px solid #ddd; } | |
| 154 | + } | |
| 155 | + </style> | |
| 156 | +</head> | |
| 157 | +<body> | |
| 158 | + <div class="container"> | |
| 159 | + <h1 style="text-align: center; margin-bottom: 30px;">健康师工资信息说明 (2025年11月)</h1> | |
| 160 | + | |
| 161 | + | |
| 162 | + <!-- 案例: 苟小春 --> | |
| 163 | + <div class="salary-row"> | |
| 164 | + <div class="salary-card"> | |
| 165 | + <div class="card-header" style="background-color: #007bff;"> | |
| 166 | + <h2>苟小春 <span style="font-size: 14px; font-weight: normal;">(顾问)</span></h2> | |
| 167 | + <span class="record-id">ID: 766260517894358278</span> | |
| 168 | + </div> | |
| 169 | + | |
| 170 | + <div class="section"> | |
| 171 | + <div class="section-title">基本信息</div> | |
| 172 | + <div class="grid"> | |
| 173 | + <div class="item"><span class="label">姓名:</span><span class="value">苟小春</span></div> | |
| 174 | + <div class="item"><span class="label">门店:</span><span class="value">绿纤468店</span></div> | |
| 175 | + <div class="item"><span class="label">员工ID:</span><span class="value">15828942309</span></div> | |
| 176 | + <div class="item"><span class="label">统计月份:</span><span class="value">202511</span></div> | |
| 177 | + <div class="item"><span class="label">岗位:</span><span class="value">顾问</span></div> | |
| 178 | + <div class="item"><span class="label">金三角战队:</span><span class="value">精英队 (3人)</span></div> | |
| 179 | + <div class="item"><span class="label">是否新店:</span><span class="value">否</span></div> | |
| 180 | + <div class="item"><span class="label">新店保护阶段:</span><span class="value">0</span></div> | |
| 181 | + </div> | |
| 182 | + </div> | |
| 183 | + | |
| 184 | + <div class="section"> | |
| 185 | + <div class="section-title">业绩数据</div> | |
| 186 | + <div class="grid"> | |
| 187 | + <div class="item"><span class="label">总业绩:</span><span class="value">16,526.90</span></div> | |
| 188 | + <div class="item"><span class="label">基础业绩:</span><span class="value">9,215.00</span></div> | |
| 189 | + <div class="item"><span class="label">合作业绩:</span><span class="value">7,311.90</span></div> | |
| 190 | + <div class="item"><span class="label">基础奖励业绩:</span><span class="value">2,000.00</span></div> | |
| 191 | + <div class="item"><span class="label">合作奖励业绩:</span><span class="value">1,000.00</span></div> | |
| 192 | + <div class="item"><span class="label">其他业绩加:</span><span class="value">4,000.00</span></div> | |
| 193 | + <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div> | |
| 194 | + <div class="item"><span class="label">队伍业绩:</span><span class="value">92,548.30</span></div> | |
| 195 | + <div class="item"><span class="label">队伍总消耗:</span><span class="value">68,582.05</span></div> | |
| 196 | + <div class="item"><span class="label">占比:</span><span class="value">0.18</span></div> | |
| 197 | + <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div> | |
| 198 | + <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div> | |
| 199 | + <div class="item"><span class="label">升单业绩:</span><span class="value">0.00</span></div> | |
| 200 | + <div class="item"><span class="label">升单人头数:</span><span class="value">0</span></div> | |
| 201 | + <div class="item"><span class="label">实际基础业绩:</span><span class="value">11,215.00</span></div> | |
| 202 | + <div class="item"><span class="label">实际合作业绩:</span><span class="value">6,311.90</span></div> | |
| 203 | + </div> | |
| 204 | + </div> | |
| 205 | + | |
| 206 | + <div class="section"> | |
| 207 | + <div class="section-title">消耗与项目数据</div> | |
| 208 | + <div class="grid"> | |
| 209 | + <div class="item"><span class="label">消耗:</span><span class="value">22,650.24</span></div> | |
| 210 | + <div class="item"><span class="label">日均消耗:</span><span class="value">838.90</span></div> | |
| 211 | + <div class="item"><span class="label">项目数:</span><span class="value">114.00</span></div> | |
| 212 | + <div class="item"><span class="label">日均项目数:</span><span class="value">4.22</span></div> | |
| 213 | + <div class="item"><span class="label">到店人头:</span><span class="value">57</span></div> | |
| 214 | + </div> | |
| 215 | + </div> | |
| 216 | + | |
| 217 | + <div class="section"> | |
| 218 | + <div class="section-title">考勤数据</div> | |
| 219 | + <div class="grid"> | |
| 220 | + <div class="item"><span class="label">在店天数:</span><span class="value ">27</span></div> | |
| 221 | + <div class="item"><span class="label">请假天数:</span><span class="value">0</span></div> | |
| 222 | + <div class="item"><span class="label">迟到次数:</span><span class="value">0.00</span></div> | |
| 223 | + <div class="item"><span class="label">缺卡次数:</span><span class="value">0.00</span></div> | |
| 224 | + </div> | |
| 225 | + </div> | |
| 226 | + | |
| 227 | + <div class="section"> | |
| 228 | + <div class="section-title">提成计算</div> | |
| 229 | + <div class="grid"> | |
| 230 | + <div class="item"><span class="label">提点:</span><span class="value">0.05</span></div> | |
| 231 | + <div class="item"><span class="label">基础业绩提成:</span><span class="value">532.71</span></div> | |
| 232 | + <div class="item"><span class="label">合作业绩提成:</span><span class="value">194.88</span></div> | |
| 233 | + <div class="item"><span class="label">顾问提成:</span><span class="value">740.39</span></div> | |
| 234 | + <div class="item"><span class="label">新客业绩提成:</span><span class="value">0.00</span></div> | |
| 235 | + <div class="item"><span class="label">升单业绩提成:</span><span class="value">0.00</span></div> | |
| 236 | + <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">1,467.98</span></div> | |
| 237 | + </div> | |
| 238 | + </div> | |
| 239 | + | |
| 240 | + <div class="section"> | |
| 241 | + <div class="section-title">底薪与补贴</div> | |
| 242 | + <div class="grid"> | |
| 243 | + <div class="item"><span class="label">健康师底薪:</span><span class="value">2,200.00</span></div> | |
| 244 | + <div class="item"><span class="label">手工费:</span><span class="value">1,583.00</span></div> | |
| 245 | + <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div> | |
| 246 | + <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div> | |
| 247 | + <div class="item"><span class="label">少休费:</span><span class="value">0.00</span></div> | |
| 248 | + <div class="item"><span class="label">全勤奖:</span><span class="value">0.00</span></div> | |
| 249 | + </div> | |
| 250 | + </div> | |
| 251 | + | |
| 252 | + <div class="total-section"> | |
| 253 | + <div class="total-row final"> | |
| 254 | + <span>实发工资</span> | |
| 255 | + <span>5,250.98</span> | |
| 256 | + </div> | |
| 257 | + </div> | |
| 258 | + </div> | |
| 259 | + | |
| 260 | + <div class="calc-details"> | |
| 261 | + <div class="calc-title">计算过程说明</div> | |
| 262 | + <div class="calc-item"> | |
| 263 | + <div class="calc-label">1. 提成点 (5%)</div> | |
| 264 | + <div class="calc-formula">战队人数(3人) + 战队业绩(92,548.30) → 查表得5% (3人以上,业绩≥9万)</div> | |
| 265 | + <div class="calc-note">根据提成点表查询得出</div> | |
| 266 | + </div> | |
| 267 | + <div class="calc-item"> | |
| 268 | + <div class="calc-label">2. 实际基础业绩计算</div> | |
| 269 | + <div class="calc-formula">9,215.00 - 2,000.00 + 4,000.00 - 0.00 - 0.00 = 11,215.00</div> | |
| 270 | + <div class="calc-note">基础业绩 - 基础奖励业绩 + 其他业绩加 - 其他业绩减 - 新客业绩</div> | |
| 271 | + </div> | |
| 272 | + <div class="calc-item"> | |
| 273 | + <div class="calc-label">3. 实际合作业绩计算</div> | |
| 274 | + <div class="calc-formula">7,311.90 - 1,000.00 = 6,311.90</div> | |
| 275 | + <div class="calc-note">合作业绩 - 合作奖励业绩</div> | |
| 276 | + </div> | |
| 277 | + <div class="calc-item"> | |
| 278 | + <div class="calc-label">4. 基础业绩提成 (532.71)</div> | |
| 279 | + <div class="calc-formula">11,215.00 × 0.95 × 5% = 532.71</div> | |
| 280 | + <div class="calc-note">实际基础业绩 × 95% × 提成点</div> | |
| 281 | + </div> | |
| 282 | + <div class="calc-item"> | |
| 283 | + <div class="calc-label">5. 合作业绩提成 (194.88)</div> | |
| 284 | + <div class="calc-formula">6,311.90 × 0.95 × 0.65 × 5% = 194.88</div> | |
| 285 | + <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div> | |
| 286 | + </div> | |
| 287 | + <div class="calc-item"> | |
| 288 | + <div class="calc-label">6. 顾问提成 (740.39)</div> | |
| 289 | + <div class="calc-formula">92,548.30 × 0.8% = 740.39</div> | |
| 290 | + <div class="calc-note">高级顾问: 战队业绩(92,548.30)≥6万 且 战队消耗(68,582.05)≥6万 → 提成率 0.8%</div> | |
| 291 | + </div> | |
| 292 | + <div class="calc-item"> | |
| 293 | + <div class="calc-label">7. 实发工资 (5,250.98)</div> | |
| 294 | + <div class="calc-formula">2,200.00 + 1,467.98 + 1,583.00 = 5,250.98</div> | |
| 295 | + <div class="calc-note">底薪 + 提成合计 + 手工费</div> | |
| 296 | + </div> | |
| 297 | +</div> | |
| 298 | + </div> | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + <!-- 案例: 李芳 --> | |
| 303 | + <div class="salary-row"> | |
| 304 | + <div class="salary-card"> | |
| 305 | + <div class="card-header" style="background-color: #007bff;"> | |
| 306 | + <h2>李芳 <span style="font-size: 14px; font-weight: normal;">(健康师)</span></h2> | |
| 307 | + <span class="record-id">ID: 766260517806277893</span> | |
| 308 | + </div> | |
| 309 | + | |
| 310 | + <div class="section"> | |
| 311 | + <div class="section-title">基本信息</div> | |
| 312 | + <div class="grid"> | |
| 313 | + <div class="item"><span class="label">姓名:</span><span class="value">李芳</span></div> | |
| 314 | + <div class="item"><span class="label">门店:</span><span class="value">绿纤468店</span></div> | |
| 315 | + <div class="item"><span class="label">员工ID:</span><span class="value">18566028067</span></div> | |
| 316 | + <div class="item"><span class="label">统计月份:</span><span class="value">202511</span></div> | |
| 317 | + <div class="item"><span class="label">岗位:</span><span class="value">健康师</span></div> | |
| 318 | + <div class="item"><span class="label">金三角战队:</span><span class="value">精英队 (3人)</span></div> | |
| 319 | + <div class="item"><span class="label">是否新店:</span><span class="value">否</span></div> | |
| 320 | + <div class="item"><span class="label">新店保护阶段:</span><span class="value">0</span></div> | |
| 321 | + </div> | |
| 322 | + </div> | |
| 323 | + | |
| 324 | + <div class="section"> | |
| 325 | + <div class="section-title">业绩数据</div> | |
| 326 | + <div class="grid"> | |
| 327 | + <div class="item"><span class="label">总业绩:</span><span class="value">35,181.30</span></div> | |
| 328 | + <div class="item"><span class="label">基础业绩:</span><span class="value">28,635.00</span></div> | |
| 329 | + <div class="item"><span class="label">合作业绩:</span><span class="value">6,546.30</span></div> | |
| 330 | + <div class="item"><span class="label">基础奖励业绩:</span><span class="value">0.00</span></div> | |
| 331 | + <div class="item"><span class="label">合作奖励业绩:</span><span class="value">0.00</span></div> | |
| 332 | + <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div> | |
| 333 | + <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div> | |
| 334 | + <div class="item"><span class="label">队伍业绩:</span><span class="value">92,548.30</span></div> | |
| 335 | + <div class="item"><span class="label">队伍总消耗:</span><span class="value">68,582.05</span></div> | |
| 336 | + <div class="item"><span class="label">占比:</span><span class="value">0.38</span></div> | |
| 337 | + <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div> | |
| 338 | + <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div> | |
| 339 | + <div class="item"><span class="label">升单业绩:</span><span class="value">0.00</span></div> | |
| 340 | + <div class="item"><span class="label">升单人头数:</span><span class="value">0</span></div> | |
| 341 | + <div class="item"><span class="label">实际基础业绩:</span><span class="value">28,635.00</span></div> | |
| 342 | + <div class="item"><span class="label">实际合作业绩:</span><span class="value">6,546.30</span></div> | |
| 343 | + </div> | |
| 344 | + </div> | |
| 345 | + | |
| 346 | + <div class="section"> | |
| 347 | + <div class="section-title">消耗与项目数据</div> | |
| 348 | + <div class="grid"> | |
| 349 | + <div class="item"><span class="label">消耗:</span><span class="value">18,341.43</span></div> | |
| 350 | + <div class="item"><span class="label">日均消耗:</span><span class="value">797.45</span></div> | |
| 351 | + <div class="item"><span class="label">项目数:</span><span class="value">96.00</span></div> | |
| 352 | + <div class="item"><span class="label">日均项目数:</span><span class="value">4.17</span></div> | |
| 353 | + <div class="item"><span class="label">到店人头:</span><span class="value">50</span></div> | |
| 354 | + </div> | |
| 355 | + </div> | |
| 356 | + | |
| 357 | + <div class="section"> | |
| 358 | + <div class="section-title">考勤数据</div> | |
| 359 | + <div class="grid"> | |
| 360 | + <div class="item"><span class="label">在店天数:</span><span class="value ">23</span></div> | |
| 361 | + <div class="item"><span class="label">请假天数:</span><span class="value">7</span></div> | |
| 362 | + <div class="item"><span class="label">迟到次数:</span><span class="value">0.00</span></div> | |
| 363 | + <div class="item"><span class="label">缺卡次数:</span><span class="value">0.00</span></div> | |
| 364 | + </div> | |
| 365 | + </div> | |
| 366 | + | |
| 367 | + <div class="section"> | |
| 368 | + <div class="section-title">提成计算</div> | |
| 369 | + <div class="grid"> | |
| 370 | + <div class="item"><span class="label">提点:</span><span class="value">0.05</span></div> | |
| 371 | + <div class="item"><span class="label">基础业绩提成:</span><span class="value">1,360.16</span></div> | |
| 372 | + <div class="item"><span class="label">合作业绩提成:</span><span class="value">202.12</span></div> | |
| 373 | + <div class="item"><span class="label">顾问提成:</span><span class="value">0.00</span></div> | |
| 374 | + <div class="item"><span class="label">新客业绩提成:</span><span class="value">0.00</span></div> | |
| 375 | + <div class="item"><span class="label">升单业绩提成:</span><span class="value">0.00</span></div> | |
| 376 | + <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">1,562.28</span></div> | |
| 377 | + </div> | |
| 378 | + </div> | |
| 379 | + | |
| 380 | + <div class="section"> | |
| 381 | + <div class="section-title">底薪与补贴</div> | |
| 382 | + <div class="grid"> | |
| 383 | + <div class="item"><span class="label">健康师底薪:</span><span class="value">2,000.00</span></div> | |
| 384 | + <div class="item"><span class="label">手工费:</span><span class="value">1,189.00</span></div> | |
| 385 | + <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div> | |
| 386 | + <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div> | |
| 387 | + <div class="item"><span class="label">少休费:</span><span class="value">0.00</span></div> | |
| 388 | + <div class="item"><span class="label">全勤奖:</span><span class="value">0.00</span></div> | |
| 389 | + </div> | |
| 390 | + </div> | |
| 391 | + | |
| 392 | + <div class="total-section"> | |
| 393 | + <div class="total-row final"> | |
| 394 | + <span>实发工资</span> | |
| 395 | + <span>4,751.28</span> | |
| 396 | + </div> | |
| 397 | + </div> | |
| 398 | + </div> | |
| 399 | + | |
| 400 | + <div class="calc-details"> | |
| 401 | + <div class="calc-title">计算过程说明</div> | |
| 402 | + <div class="calc-item"> | |
| 403 | + <div class="calc-label">1. 提成点 (5%)</div> | |
| 404 | + <div class="calc-formula">战队人数(3人) + 战队业绩(92,548.30) → 查表得5% (3人以上,业绩≥9万)</div> | |
| 405 | + <div class="calc-note">根据提成点表查询得出</div> | |
| 406 | + </div> | |
| 407 | + <div class="calc-item"> | |
| 408 | + <div class="calc-label">2. 基础业绩提成 (1,360.16)</div> | |
| 409 | + <div class="calc-formula">28,635.00 × 0.95 × 5% = 1,360.16</div> | |
| 410 | + <div class="calc-note">实际基础业绩 × 95% × 提成点</div> | |
| 411 | + </div> | |
| 412 | + <div class="calc-item"> | |
| 413 | + <div class="calc-label">3. 合作业绩提成 (202.12)</div> | |
| 414 | + <div class="calc-formula">6,546.30 × 0.95 × 0.65 × 5% = 202.12</div> | |
| 415 | + <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div> | |
| 416 | + </div> | |
| 417 | + <div class="calc-item"> | |
| 418 | + <div class="calc-label">4. 实发工资 (4,751.28)</div> | |
| 419 | + <div class="calc-formula">2,000.00 + 1,562.28 + 1,189.00 = 4,751.28</div> | |
| 420 | + <div class="calc-note">底薪 + 提成合计 + 手工费</div> | |
| 421 | + </div> | |
| 422 | +</div> | |
| 423 | + | |
| 424 | + </div> | |
| 425 | + | |
| 426 | + | |
| 427 | + <!-- 案例: 刘恬恬 --> | |
| 428 | + <div class="salary-row"> | |
| 429 | + <div class="salary-card"> | |
| 430 | + <div class="card-header" style="background-color: #007bff;"> | |
| 431 | + <h2>刘恬恬 <span style="font-size: 14px; font-weight: normal;">(健康师)</span></h2> | |
| 432 | + <span class="record-id">ID: 766260517810472197</span> | |
| 433 | + </div> | |
| 434 | + | |
| 435 | + <div class="section"> | |
| 436 | + <div class="section-title">基本信息</div> | |
| 437 | + <div class="grid"> | |
| 438 | + <div class="item"><span class="label">姓名:</span><span class="value">刘恬恬</span></div> | |
| 439 | + <div class="item"><span class="label">门店:</span><span class="value">绿纤468店</span></div> | |
| 440 | + <div class="item"><span class="label">员工ID:</span><span class="value">18863128472</span></div> | |
| 441 | + <div class="item"><span class="label">统计月份:</span><span class="value">202511</span></div> | |
| 442 | + <div class="item"><span class="label">岗位:</span><span class="value">健康师</span></div> | |
| 443 | + <div class="item"><span class="label">金三角战队:</span><span class="value">精英队 (3人)</span></div> | |
| 444 | + <div class="item"><span class="label">是否新店:</span><span class="value">否</span></div> | |
| 445 | + <div class="item"><span class="label">新店保护阶段:</span><span class="value">0</span></div> | |
| 446 | + </div> | |
| 447 | + </div> | |
| 448 | + | |
| 449 | + <div class="section"> | |
| 450 | + <div class="section-title">业绩数据</div> | |
| 451 | + <div class="grid"> | |
| 452 | + <div class="item"><span class="label">总业绩:</span><span class="value">40,840.10</span></div> | |
| 453 | + <div class="item"><span class="label">基础业绩:</span><span class="value">28,000.50</span></div> | |
| 454 | + <div class="item"><span class="label">合作业绩:</span><span class="value">12,839.60</span></div> | |
| 455 | + <div class="item"><span class="label">基础奖励业绩:</span><span class="value">0.00</span></div> | |
| 456 | + <div class="item"><span class="label">合作奖励业绩:</span><span class="value">0.00</span></div> | |
| 457 | + <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div> | |
| 458 | + <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div> | |
| 459 | + <div class="item"><span class="label">队伍业绩:</span><span class="value">92,548.30</span></div> | |
| 460 | + <div class="item"><span class="label">队伍总消耗:</span><span class="value">68,582.05</span></div> | |
| 461 | + <div class="item"><span class="label">占比:</span><span class="value">0.44</span></div> | |
| 462 | + <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div> | |
| 463 | + <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div> | |
| 464 | + <div class="item"><span class="label">升单业绩:</span><span class="value">0.00</span></div> | |
| 465 | + <div class="item"><span class="label">升单人头数:</span><span class="value">0</span></div> | |
| 466 | + <div class="item"><span class="label">实际基础业绩:</span><span class="value">28,000.50</span></div> | |
| 467 | + <div class="item"><span class="label">实际合作业绩:</span><span class="value">12,839.60</span></div> | |
| 468 | + </div> | |
| 469 | + </div> | |
| 470 | + | |
| 471 | + <div class="section"> | |
| 472 | + <div class="section-title">消耗与项目数据</div> | |
| 473 | + <div class="grid"> | |
| 474 | + <div class="item"><span class="label">消耗:</span><span class="value">27,590.38</span></div> | |
| 475 | + <div class="item"><span class="label">日均消耗:</span><span class="value">1,061.17</span></div> | |
| 476 | + <div class="item"><span class="label">项目数:</span><span class="value">101.50</span></div> | |
| 477 | + <div class="item"><span class="label">日均项目数:</span><span class="value">3.90</span></div> | |
| 478 | + <div class="item"><span class="label">到店人头:</span><span class="value">51</span></div> | |
| 479 | + </div> | |
| 480 | + </div> | |
| 481 | + | |
| 482 | + <div class="section"> | |
| 483 | + <div class="section-title">考勤数据</div> | |
| 484 | + <div class="grid"> | |
| 485 | + <div class="item"><span class="label">在店天数:</span><span class="value ">26</span></div> | |
| 486 | + <div class="item"><span class="label">请假天数:</span><span class="value">4</span></div> | |
| 487 | + <div class="item"><span class="label">迟到次数:</span><span class="value">0.00</span></div> | |
| 488 | + <div class="item"><span class="label">缺卡次数:</span><span class="value">0.00</span></div> | |
| 489 | + </div> | |
| 490 | + </div> | |
| 491 | + | |
| 492 | + <div class="section"> | |
| 493 | + <div class="section-title">提成计算</div> | |
| 494 | + <div class="grid"> | |
| 495 | + <div class="item"><span class="label">提点:</span><span class="value">0.05</span></div> | |
| 496 | + <div class="item"><span class="label">基础业绩提成:</span><span class="value">1,330.02</span></div> | |
| 497 | + <div class="item"><span class="label">合作业绩提成:</span><span class="value">396.42</span></div> | |
| 498 | + <div class="item"><span class="label">顾问提成:</span><span class="value">0.00</span></div> | |
| 499 | + <div class="item"><span class="label">新客业绩提成:</span><span class="value">0.00</span></div> | |
| 500 | + <div class="item"><span class="label">升单业绩提成:</span><span class="value">0.00</span></div> | |
| 501 | + <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">1,726.45</span></div> | |
| 502 | + </div> | |
| 503 | + </div> | |
| 504 | + | |
| 505 | + <div class="section"> | |
| 506 | + <div class="section-title">底薪与补贴</div> | |
| 507 | + <div class="grid"> | |
| 508 | + <div class="item"><span class="label">健康师底薪:</span><span class="value">2,000.00</span></div> | |
| 509 | + <div class="item"><span class="label">手工费:</span><span class="value">1,302.00</span></div> | |
| 510 | + <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div> | |
| 511 | + <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div> | |
| 512 | + <div class="item"><span class="label">少休费:</span><span class="value">0.00</span></div> | |
| 513 | + <div class="item"><span class="label">全勤奖:</span><span class="value">0.00</span></div> | |
| 514 | + </div> | |
| 515 | + </div> | |
| 516 | + | |
| 517 | + <div class="total-section"> | |
| 518 | + <div class="total-row final"> | |
| 519 | + <span>实发工资</span> | |
| 520 | + <span>5,028.45</span> | |
| 521 | + </div> | |
| 522 | + </div> | |
| 523 | + </div> | |
| 524 | + | |
| 525 | + <div class="calc-details"> | |
| 526 | + <div class="calc-title">计算过程说明</div> | |
| 527 | + <div class="calc-item"> | |
| 528 | + <div class="calc-label">1. 提成点 (5%)</div> | |
| 529 | + <div class="calc-formula">战队人数(3人) + 战队业绩(92,548.30) → 查表得5% (3人以上,业绩≥9万)</div> | |
| 530 | + <div class="calc-note">根据提成点表查询得出</div> | |
| 531 | + </div> | |
| 532 | + <div class="calc-item"> | |
| 533 | + <div class="calc-label">2. 基础业绩提成 (1,330.02)</div> | |
| 534 | + <div class="calc-formula">28,000.50 × 0.95 × 5% = 1,330.02</div> | |
| 535 | + <div class="calc-note">实际基础业绩 × 95% × 提成点</div> | |
| 536 | + </div> | |
| 537 | + <div class="calc-item"> | |
| 538 | + <div class="calc-label">3. 合作业绩提成 (396.42)</div> | |
| 539 | + <div class="calc-formula">12,839.60 × 0.95 × 0.65 × 5% = 396.42</div> | |
| 540 | + <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div> | |
| 541 | + </div> | |
| 542 | + <div class="calc-item"> | |
| 543 | + <div class="calc-label">4. 实发工资 (5,028.45)</div> | |
| 544 | + <div class="calc-formula">2,000.00 + 1,726.45 + 1,302.00 = 5,028.45</div> | |
| 545 | + <div class="calc-note">底薪 + 提成合计 + 手工费</div> | |
| 546 | + </div> | |
| 547 | +</div> | |
| 548 | + | |
| 549 | + </div> | |
| 550 | + | |
| 551 | + | |
| 552 | + <!-- 案例: 何玲 --> | |
| 553 | + <div class="salary-row"> | |
| 554 | + <div class="salary-card"> | |
| 555 | + <div class="card-header" style="background-color: #2ecc71;"> | |
| 556 | + <h2>何玲 <span style="font-size: 14px; font-weight: normal;">(新店第1阶段)</span></h2> | |
| 557 | + <span class="record-id">ID: 766260517860803845</span> | |
| 558 | + </div> | |
| 559 | + | |
| 560 | + <div class="section"> | |
| 561 | + <div class="section-title">基本信息</div> | |
| 562 | + <div class="grid"> | |
| 563 | + <div class="item"><span class="label">姓名:</span><span class="value">何玲</span></div> | |
| 564 | + <div class="item"><span class="label">门店:</span><span class="value">绿纤金沙店</span></div> | |
| 565 | + <div class="item"><span class="label">员工ID:</span><span class="value">17628345607</span></div> | |
| 566 | + <div class="item"><span class="label">统计月份:</span><span class="value">202511</span></div> | |
| 567 | + <div class="item"><span class="label">岗位:</span><span class="value">健康师</span></div> | |
| 568 | + <div class="item"><span class="label">金三角战队:</span><span class="value">何玲 (1人)</span></div> | |
| 569 | + <div class="item"><span class="label">是否新店:</span><span class="value">是</span></div> | |
| 570 | + <div class="item"><span class="label">新店保护阶段:</span><span class="value">1</span></div> | |
| 571 | + </div> | |
| 572 | + </div> | |
| 573 | + | |
| 574 | + <div class="section"> | |
| 575 | + <div class="section-title">业绩数据</div> | |
| 576 | + <div class="grid"> | |
| 577 | + <div class="item"><span class="label">总业绩:</span><span class="value">28,235.40</span></div> | |
| 578 | + <div class="item"><span class="label">基础业绩:</span><span class="value">26,318.60</span></div> | |
| 579 | + <div class="item"><span class="label">合作业绩:</span><span class="value">1,916.80</span></div> | |
| 580 | + <div class="item"><span class="label">基础奖励业绩:</span><span class="value">7,769.10</span></div> | |
| 581 | + <div class="item"><span class="label">合作奖励业绩:</span><span class="value">84.84</span></div> | |
| 582 | + <div class="item"><span class="label">其他业绩加:</span><span class="value">6,189.21</span></div> | |
| 583 | + <div class="item"><span class="label">其他业绩减:</span><span class="value">162.07</span></div> | |
| 584 | + <div class="item"><span class="label">队伍业绩:</span><span class="value">28,235.40</span></div> | |
| 585 | + <div class="item"><span class="label">队伍总消耗:</span><span class="value">4,199.07</span></div> | |
| 586 | + <div class="item"><span class="label">占比:</span><span class="value">1.00</span></div> | |
| 587 | + <div class="item"><span class="label">新客业绩:</span><span class="value">7,679.50</span></div> | |
| 588 | + <div class="item"><span class="label">新客转化率:</span><span class="value">0.46</span></div> | |
| 589 | + <div class="item"><span class="label">升单业绩:</span><span class="value">5,771.99</span></div> | |
| 590 | + <div class="item"><span class="label">升单人头数:</span><span class="value">8</span></div> | |
| 591 | + <div class="item"><span class="label">实际基础业绩:</span><span class="value">16,897.14</span></div> | |
| 592 | + <div class="item"><span class="label">实际合作业绩:</span><span class="value">1,831.96</span></div> | |
| 593 | + </div> | |
| 594 | + </div> | |
| 595 | + | |
| 596 | + <div class="section"> | |
| 597 | + <div class="section-title">消耗与项目数据</div> | |
| 598 | + <div class="grid"> | |
| 599 | + <div class="item"><span class="label">消耗:</span><span class="value">4,199.07</span></div> | |
| 600 | + <div class="item"><span class="label">日均消耗:</span><span class="value">155.52</span></div> | |
| 601 | + <div class="item"><span class="label">项目数:</span><span class="value">89.50</span></div> | |
| 602 | + <div class="item"><span class="label">日均项目数:</span><span class="value">3.31</span></div> | |
| 603 | + <div class="item"><span class="label">到店人头:</span><span class="value">53</span></div> | |
| 604 | + </div> | |
| 605 | + </div> | |
| 606 | + | |
| 607 | + <div class="section"> | |
| 608 | + <div class="section-title">考勤数据</div> | |
| 609 | + <div class="grid"> | |
| 610 | + <div class="item"><span class="label">在店天数:</span><span class="value ">27</span></div> | |
| 611 | + <div class="item"><span class="label">请假天数:</span><span class="value">0</span></div> | |
| 612 | + <div class="item"><span class="label">迟到次数:</span><span class="value">0.00</span></div> | |
| 613 | + <div class="item"><span class="label">缺卡次数:</span><span class="value">0.00</span></div> | |
| 614 | + </div> | |
| 615 | + </div> | |
| 616 | + | |
| 617 | + <div class="section"> | |
| 618 | + <div class="section-title">提成计算</div> | |
| 619 | + <div class="grid"> | |
| 620 | + <div class="item"><span class="label">提点:</span><span class="value">0.04</span></div> | |
| 621 | + <div class="item"><span class="label">基础业绩提成:</span><span class="value">642.09</span></div> | |
| 622 | + <div class="item"><span class="label">合作业绩提成:</span><span class="value">45.25</span></div> | |
| 623 | + <div class="item"><span class="label">顾问提成:</span><span class="value">0.00</span></div> | |
| 624 | + <div class="item"><span class="label">新客业绩提成:</span><span class="value">1,094.33</span></div> | |
| 625 | + <div class="item"><span class="label">升单业绩提成:</span><span class="value">0.00</span></div> | |
| 626 | + <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">1,781.67</span></div> | |
| 627 | + </div> | |
| 628 | + </div> | |
| 629 | + | |
| 630 | + <div class="section"> | |
| 631 | + <div class="section-title">底薪与补贴</div> | |
| 632 | + <div class="grid"> | |
| 633 | + <div class="item"><span class="label">健康师底薪:</span><span class="value">2,000.00</span></div> | |
| 634 | + <div class="item"><span class="label">手工费:</span><span class="value">1,114.00</span></div> | |
| 635 | + <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div> | |
| 636 | + <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div> | |
| 637 | + <div class="item"><span class="label">少休费:</span><span class="value">0.00</span></div> | |
| 638 | + <div class="item"><span class="label">全勤奖:</span><span class="value">0.00</span></div> | |
| 639 | + </div> | |
| 640 | + </div> | |
| 641 | + | |
| 642 | + <div class="total-section"> | |
| 643 | + <div class="total-row final"> | |
| 644 | + <span>实发工资</span> | |
| 645 | + <span>4,895.67</span> | |
| 646 | + </div> | |
| 647 | + </div> | |
| 648 | + </div> | |
| 649 | + | |
| 650 | + <div class="calc-details"> | |
| 651 | + <div class="calc-title">计算过程说明</div> | |
| 652 | + <div class="calc-item"> | |
| 653 | + <div class="calc-label">1. 提成点 (4%)</div> | |
| 654 | + <div class="calc-formula">战队人数(1人) + 战队业绩(28,235.40) → 查表得4% (1人,业绩≥2万)</div> | |
| 655 | + <div class="calc-note">根据提成点表查询得出</div> | |
| 656 | + </div> | |
| 657 | + <div class="calc-item"> | |
| 658 | + <div class="calc-label">2. 实际基础业绩计算</div> | |
| 659 | + <div class="calc-formula">26,318.60 - 7,769.10 + 6,189.21 - 162.07 - 7,679.50 = 16,897.14</div> | |
| 660 | + <div class="calc-note">基础业绩 - 基础奖励业绩 + 其他业绩加 - 其他业绩减 - 新客业绩</div> | |
| 661 | + </div> | |
| 662 | + <div class="calc-item"> | |
| 663 | + <div class="calc-label">3. 实际合作业绩计算</div> | |
| 664 | + <div class="calc-formula">1,916.80 - 84.84 = 1,831.96</div> | |
| 665 | + <div class="calc-note">合作业绩 - 合作奖励业绩</div> | |
| 666 | + </div> | |
| 667 | + <div class="calc-item"> | |
| 668 | + <div class="calc-label">4. 基础业绩提成 (642.09)</div> | |
| 669 | + <div class="calc-formula">16,897.14 × 0.95 × 4% = 642.09</div> | |
| 670 | + <div class="calc-note">实际基础业绩 × 95% × 提成点</div> | |
| 671 | + </div> | |
| 672 | + <div class="calc-item"> | |
| 673 | + <div class="calc-label">5. 合作业绩提成 (45.25)</div> | |
| 674 | + <div class="calc-formula">1,831.96 × 0.95 × 0.65 × 4% = 45.25</div> | |
| 675 | + <div class="calc-note">实际合作业绩 × 95% × 65% × 提成点</div> | |
| 676 | + </div> | |
| 677 | + <div class="calc-item"> | |
| 678 | + <div class="calc-label">6. 新客转化率提成 (1,094.33)</div> | |
| 679 | + <div class="calc-formula">7,679.50 × 15% × 0.95 = 1,094.33</div> | |
| 680 | + <div class="calc-note">新客业绩 × 转化率提成比例(46% → 15%) × 0.95</div> | |
| 681 | + </div> | |
| 682 | + <div class="calc-item"> | |
| 683 | + <div class="calc-label">7. 实发工资 (4,895.67)</div> | |
| 684 | + <div class="calc-formula">2,000.00 + 1,781.67 + 1,114.00 = 4,895.67</div> | |
| 685 | + <div class="calc-note">底薪 + 提成合计 + 手工费</div> | |
| 686 | + </div> | |
| 687 | +</div> | |
| 688 | + | |
| 689 | + </div> | |
| 690 | + | |
| 691 | + | |
| 692 | + <!-- 案例: 汤倩 --> | |
| 693 | + <div class="salary-row"> | |
| 694 | + <div class="salary-card"> | |
| 695 | + <div class="card-header" style="background-color: #f39c12;"> | |
| 696 | + <h2>汤倩 <span style="font-size: 14px; font-weight: normal;">(出勤不足)</span></h2> | |
| 697 | + <span class="record-id">ID: 766260517814667397</span> | |
| 698 | + </div> | |
| 699 | + | |
| 700 | + <div class="section"> | |
| 701 | + <div class="section-title">基本信息</div> | |
| 702 | + <div class="grid"> | |
| 703 | + <div class="item"><span class="label">姓名:</span><span class="value">汤倩</span></div> | |
| 704 | + <div class="item"><span class="label">门店:</span><span class="value">绿纤荣华南路店</span></div> | |
| 705 | + <div class="item"><span class="label">员工ID:</span><span class="value">751340541496526085</span></div> | |
| 706 | + <div class="item"><span class="label">统计月份:</span><span class="value">202511</span></div> | |
| 707 | + <div class="item"><span class="label">岗位:</span><span class="value">健康师</span></div> | |
| 708 | + <div class="item"><span class="label">金三角战队:</span><span class="value">个人 (1人)</span></div> | |
| 709 | + <div class="item"><span class="label">是否新店:</span><span class="value">否</span></div> | |
| 710 | + <div class="item"><span class="label">新店保护阶段:</span><span class="value">0</span></div> | |
| 711 | + </div> | |
| 712 | + </div> | |
| 713 | + | |
| 714 | + <div class="section"> | |
| 715 | + <div class="section-title">业绩数据</div> | |
| 716 | + <div class="grid"> | |
| 717 | + <div class="item"><span class="label">总业绩:</span><span class="value">5,373.70</span></div> | |
| 718 | + <div class="item"><span class="label">基础业绩:</span><span class="value">3,085.20</span></div> | |
| 719 | + <div class="item"><span class="label">合作业绩:</span><span class="value">2,288.50</span></div> | |
| 720 | + <div class="item"><span class="label">基础奖励业绩:</span><span class="value">0.00</span></div> | |
| 721 | + <div class="item"><span class="label">合作奖励业绩:</span><span class="value">0.00</span></div> | |
| 722 | + <div class="item"><span class="label">其他业绩加:</span><span class="value">0.00</span></div> | |
| 723 | + <div class="item"><span class="label">其他业绩减:</span><span class="value">0.00</span></div> | |
| 724 | + <div class="item"><span class="label">队伍业绩:</span><span class="value">5,373.70</span></div> | |
| 725 | + <div class="item"><span class="label">队伍总消耗:</span><span class="value">10,102.27</span></div> | |
| 726 | + <div class="item"><span class="label">占比:</span><span class="value">1.00</span></div> | |
| 727 | + <div class="item"><span class="label">新客业绩:</span><span class="value">0.00</span></div> | |
| 728 | + <div class="item"><span class="label">新客转化率:</span><span class="value">0.00</span></div> | |
| 729 | + <div class="item"><span class="label">升单业绩:</span><span class="value">0.00</span></div> | |
| 730 | + <div class="item"><span class="label">升单人头数:</span><span class="value">0</span></div> | |
| 731 | + <div class="item"><span class="label">实际基础业绩:</span><span class="value">3,085.20</span></div> | |
| 732 | + <div class="item"><span class="label">实际合作业绩:</span><span class="value">2,288.50</span></div> | |
| 733 | + </div> | |
| 734 | + </div> | |
| 735 | + | |
| 736 | + <div class="section"> | |
| 737 | + <div class="section-title">消耗与项目数据</div> | |
| 738 | + <div class="grid"> | |
| 739 | + <div class="item"><span class="label">消耗:</span><span class="value">10,102.27</span></div> | |
| 740 | + <div class="item"><span class="label">日均消耗:</span><span class="value">531.70</span></div> | |
| 741 | + <div class="item"><span class="label">项目数:</span><span class="value">72.00</span></div> | |
| 742 | + <div class="item"><span class="label">日均项目数:</span><span class="value">3.79</span></div> | |
| 743 | + <div class="item"><span class="label">到店人头:</span><span class="value">59</span></div> | |
| 744 | + </div> | |
| 745 | + </div> | |
| 746 | + | |
| 747 | + <div class="section"> | |
| 748 | + <div class="section-title">考勤数据</div> | |
| 749 | + <div class="grid"> | |
| 750 | + <div class="item"><span class="label">在店天数:</span><span class="value highlight">19</span></div> | |
| 751 | + <div class="item"><span class="label">请假天数:</span><span class="value">0</span></div> | |
| 752 | + <div class="item"><span class="label">迟到次数:</span><span class="value">0.00</span></div> | |
| 753 | + <div class="item"><span class="label">缺卡次数:</span><span class="value">0.00</span></div> | |
| 754 | + </div> | |
| 755 | + </div> | |
| 756 | + | |
| 757 | + <div class="section"> | |
| 758 | + <div class="section-title">提成计算</div> | |
| 759 | + <div class="grid"> | |
| 760 | + <div class="item"><span class="label">提点:</span><span class="value">0.00</span></div> | |
| 761 | + <div class="item"><span class="label">基础业绩提成:</span><span class="value">0.00</span></div> | |
| 762 | + <div class="item"><span class="label">合作业绩提成:</span><span class="value">0.00</span></div> | |
| 763 | + <div class="item"><span class="label">顾问提成:</span><span class="value">0.00</span></div> | |
| 764 | + <div class="item"><span class="label">新客业绩提成:</span><span class="value">0.00</span></div> | |
| 765 | + <div class="item"><span class="label">升单业绩提成:</span><span class="value">0.00</span></div> | |
| 766 | + <div class="item"><span class="label highlight">提成合计:</span><span class="value highlight">0.00</span></div> | |
| 767 | + </div> | |
| 768 | + </div> | |
| 769 | + | |
| 770 | + <div class="section"> | |
| 771 | + <div class="section-title">底薪与补贴</div> | |
| 772 | + <div class="grid"> | |
| 773 | + <div class="item"><span class="label">健康师底薪:</span><span class="value">2,000.00</span></div> | |
| 774 | + <div class="item"><span class="label">手工费:</span><span class="value">880.00</span></div> | |
| 775 | + <div class="item"><span class="label">额外手工费:</span><span class="value">0.00</span></div> | |
| 776 | + <div class="item"><span class="label">车补:</span><span class="value">0.00</span></div> | |
| 777 | + <div class="item"><span class="label">少休费:</span><span class="value">0.00</span></div> | |
| 778 | + <div class="item"><span class="label">全勤奖:</span><span class="value">0.00</span></div> | |
| 779 | + </div> | |
| 780 | + </div> | |
| 781 | + | |
| 782 | + <div class="total-section"> | |
| 783 | + <div class="total-row final"> | |
| 784 | + <span>实发工资</span> | |
| 785 | + <span>2,880.00</span> | |
| 786 | + </div> | |
| 787 | + </div> | |
| 788 | + </div> | |
| 789 | + | |
| 790 | + <div class="calc-details"> | |
| 791 | + <div class="calc-title">计算过程说明</div> | |
| 792 | + <div class="calc-item"> | |
| 793 | + <div class="calc-label">1. 提成资格判定</div> | |
| 794 | + <div class="calc-formula">出勤19天 < 20天 → 无提成资格</div> | |
| 795 | + <div class="calc-note">出勤不足20天,所有提成归零</div> | |
| 796 | + </div> | |
| 797 | + <div class="calc-item"> | |
| 798 | + <div class="calc-label">2. 实发工资 (2,880.00)</div> | |
| 799 | + <div class="calc-formula">2,000.00 + 0.00 + 880.00 = 2,880.00</div> | |
| 800 | + <div class="calc-note">底薪 + 提成合计 + 手工费</div> | |
| 801 | + </div> | |
| 802 | +</div> | |
| 803 | + | |
| 804 | + </div> | |
| 805 | + | |
| 806 | + </div> | |
| 807 | + </div> | |
| 808 | +</body> | |
| 809 | +</html> | |
| 0 | 810 | \ No newline at end of file | ... | ... |
店助主任工资计算规则梳理.md
0 → 100644
| 1 | +# 店助主任工资计算规则梳理 | |
| 2 | + | |
| 3 | +## 📋 概述 | |
| 4 | + | |
| 5 | +店助主任工资由以下几个部分组成: | |
| 6 | +1. **底薪**:从工资配置表(`lq_gz`)获取,字段为 `dzzrdx` | |
| 7 | +2. **提成**:根据门店业绩与门店生命线的比例计算(阶梯提成模式) | |
| 8 | +3. **阶段奖励**:根据进店消耗人数是否达到阶段目标(同店助) | |
| 9 | +4. **固定奖励**:手机管理费 | |
| 10 | + | |
| 11 | +--- | |
| 12 | + | |
| 13 | +# 第一部分:计算规则 | |
| 14 | + | |
| 15 | +## 💰 工资组成规则 | |
| 16 | + | |
| 17 | +### 1. 底薪规则 | |
| 18 | + | |
| 19 | +**计算规则**:从工资配置表(`lq_gz`)获取,字段为 `dzzrdx` | |
| 20 | + | |
| 21 | +**重要说明**: | |
| 22 | +- 底薪从配置表获取,如果未设置,应使用默认值或报错提示 | |
| 23 | +- 底薪是固定值,不随门店分类变化 | |
| 24 | + | |
| 25 | +--- | |
| 26 | + | |
| 27 | +### 2. 提成规则 | |
| 28 | + | |
| 29 | +**计算公式**:根据门店业绩与门店生命线的比例确定提成比例,使用阶梯提成模式计算 | |
| 30 | + | |
| 31 | +#### 提成比例规则 | |
| 32 | + | |
| 33 | +| 门店业绩范围 | 提成计算方式 | | |
| 34 | +|------------|------------| | |
| 35 | +| 门店业绩 < 门店生命线 × 70% | 0%(无提成) | | |
| 36 | +| 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% | 0.4%(全部业绩按0.4%计算) | | |
| 37 | +| 门店业绩 ≥ 门店生命线 × 100% | **阶梯提成**:<br>- 超过生命线部分:1%<br>- 剩余部分(≤生命线):0.6% | | |
| 38 | + | |
| 39 | +#### 提成计算示例 | |
| 40 | + | |
| 41 | +**示例1:业绩未达到70%** | |
| 42 | +- 门店生命线 = 100,000元 | |
| 43 | +- 门店业绩 = 60,000元 | |
| 44 | +- 计算:60,000 < 100,000 × 70% = 70,000 | |
| 45 | +- 提成金额 = 0元(无提成) | |
| 46 | + | |
| 47 | +**示例2:业绩在70%-100%之间** | |
| 48 | +- 门店生命线 = 100,000元 | |
| 49 | +- 门店业绩 = 85,000元 | |
| 50 | +- 计算:70,000 ≤ 85,000 < 100,000 | |
| 51 | +- 提成金额 = 85,000 × 0.4% = 340元 | |
| 52 | + | |
| 53 | +**示例3:业绩超过100%(阶梯提成)** | |
| 54 | +- 门店生命线 = 100,000元 | |
| 55 | +- 门店业绩 = 150,000元 | |
| 56 | +- 计算过程: | |
| 57 | + 1. 业绩(150,000)> 生命线(100,000) | |
| 58 | + 2. ≤生命线部分:100,000 × 0.6% = 600元 | |
| 59 | + 3. >生命线部分:(150,000 - 100,000) × 1% = 50,000 × 1% = 500元 | |
| 60 | + 4. 总提成 = 600 + 500 = 1,100元 | |
| 61 | + | |
| 62 | +**重要说明**: | |
| 63 | +- 门店生命线必须在 `lq_md_target` 表中进行设置,如果未设置,系统应报错提示 | |
| 64 | +- 当业绩超过生命线时,必须使用阶梯提成模式,不能使用单一提成比例 | |
| 65 | +- 提成按门店业绩的百分比计算 | |
| 66 | + | |
| 67 | +--- | |
| 68 | + | |
| 69 | +### 3. 阶段奖励规则 | |
| 70 | + | |
| 71 | +**考核指标**:进店消耗人数(有消费金额的,按门店按月去重客户数) | |
| 72 | + | |
| 73 | +#### 奖励规则 | |
| 74 | + | |
| 75 | +| 阶段 | 目标人数 | 奖励金额 | 说明 | | |
| 76 | +|-----|---------|---------|------| | |
| 77 | +| 第一阶段 | 达到目标(如:100人) | 200元/月 | 只要达到第一阶段目标即可获得 | | |
| 78 | +| 第二阶段 | 达到目标(如:140人) | 再奖励200元/月 | 在达到第一阶段的基础上,再达到第二阶段目标 | | |
| 79 | +| 两个阶段都达到 | - | 总计400元/月 | 第一阶段200元 + 第二阶段200元 | | |
| 80 | + | |
| 81 | +**奖励说明**: | |
| 82 | +- 两个阶段的奖励是累加的,不是互斥的 | |
| 83 | +- 如果只达到第二阶段但未达到第一阶段,只获得第二阶段奖励(200元) | |
| 84 | +- 如果两个阶段都达到,获得总计400元奖励 | |
| 85 | + | |
| 86 | +**统计规则**: | |
| 87 | +- 按门店统计(`md` 字段) | |
| 88 | +- 只统计有消费金额的记录(消耗金额 > 0) | |
| 89 | +- 同一个会员按月去重(`COUNT(DISTINCT hy)`),其中 `hy` 为会员ID | |
| 90 | +- 只统计当月有效消耗记录(`F_IsEffective = 1`) | |
| 91 | + | |
| 92 | +**重要说明**: | |
| 93 | +- 阶段目标必须在 `lq_md_target` 表中进行设置,如果未设置,奖励金额为0(不报错) | |
| 94 | +- 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人) | |
| 95 | + | |
| 96 | +--- | |
| 97 | + | |
| 98 | +### 4. 固定奖励规则 | |
| 99 | + | |
| 100 | +**手机管理费**:150元/月 | |
| 101 | + | |
| 102 | +**说明**: | |
| 103 | +- 固定金额,无需计算 | |
| 104 | +- 每月固定发放 | |
| 105 | + | |
| 106 | +--- | |
| 107 | + | |
| 108 | +## 📊 完整计算公式 | |
| 109 | + | |
| 110 | +``` | |
| 111 | +店助主任月工资 = 底薪 + 提成 + 阶段奖励 + 固定奖励 | |
| 112 | + | |
| 113 | +其中: | |
| 114 | +- 底薪 = 从lq_gz.dzzrdx获取 | |
| 115 | +- 提成 = 根据业绩与生命线比例确定(阶梯提成模式) | |
| 116 | +- 阶段奖励 = 根据进店消耗人数是否达到阶段目标(0/200/400元) | |
| 117 | +- 固定奖励 = 150元(手机管理费) | |
| 118 | +``` | |
| 119 | + | |
| 120 | +--- | |
| 121 | + | |
| 122 | +## ⚠️ 注意事项 | |
| 123 | + | |
| 124 | +1. **门店业绩计算**: | |
| 125 | + - 门店业绩 = 开单业绩 - 退卡业绩 | |
| 126 | + - 只统计当月有效记录(`F_IsEffective = 1`) | |
| 127 | + | |
| 128 | +2. **进店消耗人数统计**: | |
| 129 | + - 必须使用 `COUNT(DISTINCT hy)` 去重(同一个会员按月去重) | |
| 130 | + - 只统计有消费金额的记录(消耗金额 > 0) | |
| 131 | + - 按门店统计(`md` 字段) | |
| 132 | + - 只统计当月有效消耗记录(`F_IsEffective = 1`) | |
| 133 | + - 通过关联 `lq_xh_jksyj` 表判断是否有消费金额 | |
| 134 | + | |
| 135 | +3. **阶段目标配置**: | |
| 136 | + - 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人) | |
| 137 | + - 必须从 `lq_md_target` 表获取对应门店的目标值 | |
| 138 | + - **重要**:如果 `lq_md_target` 表中未设置阶段目标,奖励金额为0(不报错) | |
| 139 | + | |
| 140 | +4. **提成比例判断**: | |
| 141 | + - 严格按照门店业绩与门店生命线的比例判断 | |
| 142 | + - 注意边界值:70% 和 100% | |
| 143 | + - **重要**:当业绩超过生命线时,必须使用阶梯提成模式(超过部分1%,剩余部分0.6%) | |
| 144 | + | |
| 145 | +5. **数据一致性**: | |
| 146 | + - 门店业绩的计算逻辑必须与门店总业绩统计保持一致 | |
| 147 | + - 进店消耗人数的统计逻辑必须与其他统计接口保持一致 | |
| 148 | + | |
| 149 | +6. **数据校验要求**: | |
| 150 | + - 门店分类(`F_StoreCategory`)必须设置,不允许为NULL,未设置应报错 | |
| 151 | + - 门店生命线(`F_StoreLifeline`)如果未设置,提成金额为0(不报错) | |
| 152 | + - 阶段目标(`F_AssistantHeadcountTargetStage1`、`F_AssistantHeadcountTargetStage2`)如果未设置,奖励金额为0(不报错) | |
| 153 | + | |
| 154 | +7. **在店天数比例计算**: | |
| 155 | + - 如果店助主任中间离职或请假,提成和奖励需要按在店天数比例计算 | |
| 156 | + - 计算公式: | |
| 157 | + - 店助主任提成 = 门店总提成 ÷ 当月天数 × 在店天数 | |
| 158 | + - 店助主任奖励 = 门店总奖励 ÷ 当月天数 × 在店天数 | |
| 159 | + | |
| 160 | +--- | |
| 161 | + | |
| 162 | +# 第二部分:数据来源说明 | |
| 163 | + | |
| 164 | +## 📊 数据表说明 | |
| 165 | + | |
| 166 | +### 1. `lq_gz` - 工资配置表 | |
| 167 | + | |
| 168 | +**用途**:获取店助主任底薪 | |
| 169 | + | |
| 170 | +**关键字段**: | |
| 171 | +- `dzzrdx`:店助主任底薪 | |
| 172 | + | |
| 173 | +**查询逻辑**: | |
| 174 | +- 从配置表获取底薪值 | |
| 175 | +- 如果未设置,应使用默认值或报错提示 | |
| 176 | + | |
| 177 | +--- | |
| 178 | + | |
| 179 | +### 2. `lq_mdxx` - 门店信息表 | |
| 180 | + | |
| 181 | +**用途**:获取门店分类、门店名称等信息 | |
| 182 | + | |
| 183 | +**关键字段**: | |
| 184 | +- `F_Id`:门店ID(主键) | |
| 185 | +- `dm`:门店名称 | |
| 186 | +- `F_StoreCategory`:门店分类(1=A类,2=B类,3=C类) | |
| 187 | +- `F_StoreType`:门店类型(200平/旗舰店) | |
| 188 | + | |
| 189 | +**查询逻辑**: | |
| 190 | +- 通过门店ID关联获取门店信息 | |
| 191 | +- 门店分类必须设置,不允许为NULL | |
| 192 | + | |
| 193 | +--- | |
| 194 | + | |
| 195 | +### 3. `lq_md_target` - 门店目标表 | |
| 196 | + | |
| 197 | +**用途**:获取门店生命线和阶段目标 | |
| 198 | + | |
| 199 | +**关键字段**: | |
| 200 | +- `F_StoreId`:门店ID | |
| 201 | +- `F_Month`:月份(YYYYMM格式) | |
| 202 | +- `F_StoreLifeline`:门店生命线 | |
| 203 | +- `F_AssistantHeadcountTargetStage1`:店助人头目标数阶段一 | |
| 204 | +- `F_AssistantHeadcountTargetStage2`:店助人头目标数阶段二 | |
| 205 | + | |
| 206 | +**查询逻辑**: | |
| 207 | +- 通过门店ID和月份关联获取目标数据 | |
| 208 | +- 门店生命线如果未设置,提成金额为0 | |
| 209 | +- 阶段目标如果未设置,奖励金额为0 | |
| 210 | + | |
| 211 | +--- | |
| 212 | + | |
| 213 | +### 4. `lq_kd_kdjlb` - 开单记录表 | |
| 214 | + | |
| 215 | +**用途**:计算门店开单业绩 | |
| 216 | + | |
| 217 | +**关键字段**: | |
| 218 | +- `Djmd`:登记门店(门店ID) | |
| 219 | +- `Kdrq`:开单日期 | |
| 220 | +- `Sfyj`:实付业绩 | |
| 221 | +- `F_IsEffective`:是否有效(只统计有效记录) | |
| 222 | + | |
| 223 | +**查询逻辑**: | |
| 224 | +- 按门店、月份统计开单业绩总和 | |
| 225 | +- 只统计有效记录(`F_IsEffective = 1`) | |
| 226 | + | |
| 227 | +--- | |
| 228 | + | |
| 229 | +### 5. `lq_hytk_hytk` - 退卡信息表 | |
| 230 | + | |
| 231 | +**用途**:计算门店退卡业绩 | |
| 232 | + | |
| 233 | +**关键字段**: | |
| 234 | +- `md`:门店编号(门店ID) | |
| 235 | +- `tksj`:退卡时间 | |
| 236 | +- `F_ActualRefundAmount`:实退金额(优先使用) | |
| 237 | +- `tkje`:退卡金额(如果实退金额为空则使用) | |
| 238 | +- `F_IsEffective`:是否有效(只统计有效记录) | |
| 239 | + | |
| 240 | +**查询逻辑**: | |
| 241 | +- 按门店、月份统计退卡金额总和 | |
| 242 | +- 优先使用 `F_ActualRefundAmount`,如果为空则使用 `tkje` | |
| 243 | +- 只统计有效记录(`F_IsEffective = 1`) | |
| 244 | + | |
| 245 | +--- | |
| 246 | + | |
| 247 | +### 6. `lq_xh_hyhk` - 会员耗卡表 | |
| 248 | + | |
| 249 | +**用途**:统计进店消耗人数 | |
| 250 | + | |
| 251 | +**关键字段**: | |
| 252 | +- `F_Id`:耗卡编号(主键) | |
| 253 | +- `md`:门店ID | |
| 254 | +- `hy`:会员ID(用于去重统计) | |
| 255 | +- `hksj`:耗卡时间 | |
| 256 | +- `F_IsEffective`:是否有效(只统计有效记录) | |
| 257 | + | |
| 258 | +**查询逻辑**: | |
| 259 | +- 按门店、月份统计去重会员数 | |
| 260 | +- 只统计有效记录(`F_IsEffective = 1`) | |
| 261 | +- 必须关联 `lq_xh_jksyj` 表判断是否有消费金额 | |
| 262 | + | |
| 263 | +--- | |
| 264 | + | |
| 265 | +### 7. `lq_xh_jksyj` - 耗卡健康师业绩表 | |
| 266 | + | |
| 267 | +**用途**:判断是否有消费金额 | |
| 268 | + | |
| 269 | +**关键字段**: | |
| 270 | +- `glkdbh`:关联耗卡记录ID(关联 `lq_xh_hyhk.F_Id`) | |
| 271 | +- `jksyj`:健康师业绩(消耗金额) | |
| 272 | +- `F_IsEffective`:是否有效(只统计有效记录) | |
| 273 | + | |
| 274 | +**查询逻辑**: | |
| 275 | +- 用于判断耗卡记录是否有消费金额(`jksyj > 0`) | |
| 276 | +- 只统计有效记录(`F_IsEffective = 1`) | |
| 277 | + | |
| 278 | +--- | |
| 279 | + | |
| 280 | +### 8. `lq_attendance_summary` - 考勤汇总表 | |
| 281 | + | |
| 282 | +**用途**:获取在店天数 | |
| 283 | + | |
| 284 | +**关键字段**: | |
| 285 | +- `F_UserId`:用户ID(员工ID) | |
| 286 | +- `F_Year`:年份 | |
| 287 | +- `F_Month`:月份 | |
| 288 | +- `F_WorkDays`:出勤天数(在店天数) | |
| 289 | +- `F_LeaveDays`:请假天数 | |
| 290 | +- `F_IsEffective`:是否有效(只统计有效记录) | |
| 291 | + | |
| 292 | +**查询逻辑**: | |
| 293 | +- 通过员工ID、年份、月份获取考勤数据 | |
| 294 | +- 用于计算按在店天数比例计算的提成和奖励 | |
| 295 | + | |
| 296 | +--- | |
| 297 | + | |
| 298 | +## 📝 数据查询逻辑 | |
| 299 | + | |
| 300 | +### 1. 门店业绩计算 | |
| 301 | + | |
| 302 | +**计算公式**:门店业绩 = 开单业绩 - 退卡业绩 | |
| 303 | + | |
| 304 | +**查询逻辑**: | |
| 305 | +```sql | |
| 306 | +-- 1. 计算门店开单业绩 | |
| 307 | +SELECT COALESCE(SUM(sfyj), 0) as BillingPerformance | |
| 308 | +FROM lq_kd_kdjlb | |
| 309 | +WHERE Djmd = @StoreId | |
| 310 | + AND DATE_FORMAT(Kdrq, '%Y%m') = @Month | |
| 311 | + AND F_IsEffective = 1 | |
| 312 | + | |
| 313 | +-- 2. 计算门店退卡业绩 | |
| 314 | +SELECT COALESCE(SUM(COALESCE(F_ActualRefundAmount, tkje, 0)), 0) as RefundPerformance | |
| 315 | +FROM lq_hytk_hytk | |
| 316 | +WHERE md = @StoreId | |
| 317 | + AND DATE_FORMAT(tksj, '%Y%m') = @Month | |
| 318 | + AND F_IsEffective = 1 | |
| 319 | + | |
| 320 | +-- 3. 计算门店实际业绩 | |
| 321 | +门店业绩 = 开单业绩 - 退卡业绩 | |
| 322 | +``` | |
| 323 | + | |
| 324 | +--- | |
| 325 | + | |
| 326 | +### 2. 进店消耗人数统计 | |
| 327 | + | |
| 328 | +**统计规则**:有消费金额的,按门店按月去重客户数 | |
| 329 | + | |
| 330 | +**查询逻辑**: | |
| 331 | +```sql | |
| 332 | +-- 统计门店当月进店消耗人数(有消费金额的,按门店按月去重客户数) | |
| 333 | +SELECT COUNT(DISTINCT hy) as HeadCount | |
| 334 | +FROM lq_xh_hyhk hk | |
| 335 | +WHERE hk.md = @StoreId | |
| 336 | + AND DATE_FORMAT(hk.hksj, '%Y%m') = @Month | |
| 337 | + AND hk.F_IsEffective = 1 | |
| 338 | + AND EXISTS ( | |
| 339 | + -- 确保有消费金额(通过关联消耗业绩表判断) | |
| 340 | + SELECT 1 | |
| 341 | + FROM lq_xh_jksyj jksyj | |
| 342 | + WHERE jksyj.glkdbh = hk.F_Id | |
| 343 | + AND jksyj.F_IsEffective = 1 | |
| 344 | + AND jksyj.jksyj > 0 | |
| 345 | + ) | |
| 346 | +``` | |
| 347 | + | |
| 348 | +**说明**: | |
| 349 | +- `md`:门店ID,按门店统计 | |
| 350 | +- `hy`:会员ID,用于去重统计(同一个会员按月去重) | |
| 351 | +- `hksj`:耗卡时间,用于按月过滤 | |
| 352 | +- `F_IsEffective = 1`:只统计有效记录 | |
| 353 | +- **重要**:只统计有消费金额的记录(消耗金额 > 0),通过关联 `lq_xh_jksyj` 表判断是否有消费金额 | |
| 354 | + | |
| 355 | +--- | |
| 356 | + | |
| 357 | +### 3. 门店分类获取 | |
| 358 | + | |
| 359 | +**查询逻辑**: | |
| 360 | +```sql | |
| 361 | +SELECT | |
| 362 | + F_Id as StoreId, | |
| 363 | + dm as StoreName, | |
| 364 | + F_StoreCategory as StoreCategory, | |
| 365 | + F_StoreType as StoreType | |
| 366 | +FROM lq_mdxx | |
| 367 | +WHERE F_Id = @StoreId | |
| 368 | +``` | |
| 369 | + | |
| 370 | +**说明**: | |
| 371 | +- `F_StoreCategory`:门店分类(1=A类,2=B类,3=C类) | |
| 372 | +- 门店分类必须设置,不允许为NULL | |
| 373 | + | |
| 374 | +--- | |
| 375 | + | |
| 376 | +### 4. 门店生命线和阶段目标获取 | |
| 377 | + | |
| 378 | +**查询逻辑**: | |
| 379 | +```sql | |
| 380 | +SELECT | |
| 381 | + F_StoreLifeline as StoreLifeline, | |
| 382 | + F_AssistantHeadcountTargetStage1 as Stage1Target, | |
| 383 | + F_AssistantHeadcountTargetStage2 as Stage2Target | |
| 384 | +FROM lq_md_target | |
| 385 | +WHERE F_StoreId = @StoreId | |
| 386 | + AND F_Month = @Month | |
| 387 | +``` | |
| 388 | + | |
| 389 | +**说明**: | |
| 390 | +- `F_StoreLifeline`:门店生命线,用于计算提成 | |
| 391 | +- `F_AssistantHeadcountTargetStage1`:第一阶段目标人数 | |
| 392 | +- `F_AssistantHeadcountTargetStage2`:第二阶段目标人数 | |
| 393 | +- 如果门店生命线未设置,提成金额为0 | |
| 394 | +- 如果阶段目标未设置,奖励金额为0 | |
| 395 | + | |
| 396 | +--- | |
| 397 | + | |
| 398 | +### 5. 店助主任底薪获取 | |
| 399 | + | |
| 400 | +**查询逻辑**: | |
| 401 | +```sql | |
| 402 | +SELECT dzzrdx as BaseSalary | |
| 403 | +FROM lq_gz | |
| 404 | +LIMIT 1 | |
| 405 | +``` | |
| 406 | + | |
| 407 | +**说明**: | |
| 408 | +- `dzzrdx`:店助主任底薪 | |
| 409 | +- 从配置表获取,如果未设置,应使用默认值或报错提示 | |
| 410 | + | |
| 411 | +--- | |
| 412 | + | |
| 413 | +## 🔄 店助主任与店助的主要区别 | |
| 414 | + | |
| 415 | +### 1. 底薪规则 | |
| 416 | + | |
| 417 | +| 岗位 | 底薪来源 | | |
| 418 | +|-----|---------| | |
| 419 | +| 店助 | 根据门店分类确定(A类3000,B类3100,C类3200) | | |
| 420 | +| 店助主任 | 从配置表 `lq_gz.dzzrdx` 获取 | | |
| 421 | + | |
| 422 | +### 2. 提成规则 | |
| 423 | + | |
| 424 | +| 岗位 | 业绩 ≥ 100%时的提成计算 | | |
| 425 | +|-----|---------------------| | |
| 426 | +| 店助 | 全部业绩按 0.6% 计算 | | |
| 427 | +| 店助主任 | **阶梯提成**:<br>- 超过生命线部分:1%<br>- 剩余部分(≤生命线):0.6% | | |
| 428 | + | |
| 429 | +**示例对比**: | |
| 430 | +- 门店生命线 = 100,000元 | |
| 431 | +- 门店业绩 = 150,000元 | |
| 432 | + | |
| 433 | +**店助提成**: | |
| 434 | +- 150,000 × 0.6% = 900元 | |
| 435 | + | |
| 436 | +**店助主任提成**: | |
| 437 | +- ≤生命线部分:100,000 × 0.6% = 600元 | |
| 438 | +- >生命线部分:(150,000 - 100,000) × 1% = 500元 | |
| 439 | +- 总提成 = 600 + 500 = 1,100元 | |
| 440 | + | |
| 441 | +### 3. 阶段奖励规则 | |
| 442 | + | |
| 443 | +**相同**:两个岗位的阶段奖励规则完全一致 | |
| 444 | + | |
| 445 | +--- | |
| 446 | + | |
| 447 | +## 📅 更新记录 | |
| 448 | + | |
| 449 | +- 2025-01-XX:初始版本,根据项目文档梳理店助主任工资计算规则 | |
| 450 | + | ... | ... |
店助工资表字段设计.md
0 → 100644
| 1 | +# 店助工资表字段设计 | |
| 2 | + | |
| 3 | +## 📋 表名建议 | |
| 4 | +`lq_assistant_salary_statistics`(店助工资统计表) | |
| 5 | + | |
| 6 | +--- | |
| 7 | + | |
| 8 | +## 🔍 字段分类说明 | |
| 9 | + | |
| 10 | +根据店助工资计算规则,参考健康师工资表结构,店助工资表需要包含以下字段: | |
| 11 | + | |
| 12 | +--- | |
| 13 | + | |
| 14 | +## 一、基础信息字段 | |
| 15 | + | |
| 16 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 17 | +|--------|-----------|------|------|------| | |
| 18 | +| 主键ID | F_Id | VARCHAR(50) | 主键,使用YitIdHelper生成 | ✅ | | |
| 19 | +| 门店ID | F_StoreId | VARCHAR(50) | 关联门店信息表 | ✅ | | |
| 20 | +| 门店名称 | F_StoreName | VARCHAR(200) | 门店名称(冗余字段,便于查询) | ✅ | | |
| 21 | +| 核算岗位 | F_Position | VARCHAR(50) | 固定为"店助"或"店助主任" | ✅ | | |
| 22 | +| 员工姓名 | F_EmployeeName | VARCHAR(100) | 员工姓名 | ✅ | | |
| 23 | +| 员工ID | F_EmployeeId | VARCHAR(50) | 关联BASE_USER表 | ✅ | | |
| 24 | +| 统计月份 | F_StatisticsMonth | VARCHAR(20) | 格式:YYYYMM,如:202501 | ✅ | | |
| 25 | +| 门店类型 | F_StoreType | INT | 门店类型(200平/旗舰店) | ❌ | | |
| 26 | +| 门店类别 | F_StoreCategory | INT | 门店分类(1=A类,2=B类,3=C类) | ✅ | | |
| 27 | +| 是否新店 | F_IsNewStore | VARCHAR(10) | 是/否 | ❌ | | |
| 28 | +| 新店保护阶段 | F_NewStoreProtectionStage | INT | 新店保护阶段(0/1/2) | ❌ | | |
| 29 | + | |
| 30 | +--- | |
| 31 | + | |
| 32 | +## 二、业绩相关字段 | |
| 33 | + | |
| 34 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 35 | +|--------|-----------|------|------|------| | |
| 36 | +| 门店总业绩 | F_StoreTotalPerformance | DECIMAL(18,2) | 门店开单业绩 - 门店退卡业绩 | ✅ | | |
| 37 | +| 门店开单业绩 | F_StoreBillingPerformance | DECIMAL(18,2) | 门店开单业绩总和 | ✅ | | |
| 38 | +| 门店退卡业绩 | F_StoreRefundPerformance | DECIMAL(18,2) | 门店退卡业绩总和 | ✅ | | |
| 39 | +| 门店生命线 | F_StoreLifeline | DECIMAL(18,2) | 门店生命线(从lq_md_target获取) | ✅ | | |
| 40 | +| 业绩完成率 | F_PerformanceCompletionRate | DECIMAL(18,4) | 门店业绩 / 门店生命线 | ✅ | | |
| 41 | + | |
| 42 | +--- | |
| 43 | + | |
| 44 | +## 三、提成相关字段 | |
| 45 | + | |
| 46 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 47 | +|--------|-----------|------|------|------| | |
| 48 | +| 提成比例 | F_CommissionRate | DECIMAL(18,4) | 根据业绩与生命线比例确定(0%/0.4%/0.6%) | ✅ | | |
| 49 | +| 提成金额 | F_CommissionAmount | DECIMAL(18,2) | 门店业绩 × 提成比例 | ✅ | | |
| 50 | + | |
| 51 | +--- | |
| 52 | + | |
| 53 | +## 四、阶段奖励相关字段 | |
| 54 | + | |
| 55 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 56 | +|--------|-----------|------|------|------| | |
| 57 | +| 进店消耗人数 | F_HeadCount | INT | 有消费金额的,按门店按月去重客户数 | ✅ | | |
| 58 | +| 第一阶段目标人数 | F_Stage1TargetHeadCount | INT | 从lq_md_target获取 | ✅ | | |
| 59 | +| 第二阶段目标人数 | F_Stage2TargetHeadCount | INT | 从lq_md_target获取 | ✅ | | |
| 60 | +| 是否达到第一阶段 | F_ReachedStage1 | VARCHAR(10) | 是/否 | ✅ | | |
| 61 | +| 是否达到第二阶段 | F_ReachedStage2 | VARCHAR(10) | 是/否 | ✅ | | |
| 62 | +| 阶段奖励金额 | F_StageRewardAmount | DECIMAL(18,2) | 根据阶段达成情况计算(0/200/400元) | ✅ | | |
| 63 | +| 第一阶段奖励 | F_Stage1Reward | DECIMAL(18,2) | 第一阶段奖励金额(0或200元) | ✅ | | |
| 64 | +| 第二阶段奖励 | F_Stage2Reward | DECIMAL(18,2) | 第二阶段奖励金额(0或200元) | ✅ | | |
| 65 | + | |
| 66 | +--- | |
| 67 | + | |
| 68 | +## 五、底薪相关字段 | |
| 69 | + | |
| 70 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 71 | +|--------|-----------|------|------|------| | |
| 72 | +| 门店分类 | F_StoreCategory | INT | 门店分类(1=A类,2=B类,3=C类) | ✅ | | |
| 73 | +| 底薪金额 | F_BaseSalary | DECIMAL(18,2) | 根据门店分类确定(A类3000,B类3100,C类3200) | ✅ | | |
| 74 | + | |
| 75 | +--- | |
| 76 | + | |
| 77 | +## 六、固定奖励字段 | |
| 78 | + | |
| 79 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 80 | +|--------|-----------|------|------|------| | |
| 81 | +| 手机管理费 | F_PhoneManagementFee | DECIMAL(18,2) | 固定150元/月 | ✅ | | |
| 82 | + | |
| 83 | +--- | |
| 84 | + | |
| 85 | +## 七、考勤相关字段 | |
| 86 | + | |
| 87 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 88 | +|--------|-----------|------|------|------| | |
| 89 | +| 在店天数 | F_WorkingDays | INT | 在店工作天数 | ✅ | | |
| 90 | +| 请假天数 | F_LeaveDays | INT | 请假天数 | ✅ | | |
| 91 | + | |
| 92 | +--- | |
| 93 | + | |
| 94 | +## 八、工资计算字段 | |
| 95 | + | |
| 96 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 97 | +|--------|-----------|------|------|------| | |
| 98 | +| 应发工资 | F_GrossSalary | DECIMAL(18,2) | 底薪 + 提成 + 阶段奖励 + 固定奖励 | ✅ | | |
| 99 | +| 实发工资 | F_ActualSalary | DECIMAL(18,2) | 应发工资 - 扣款合计 + 补贴合计 + 奖金 | ✅ | | |
| 100 | + | |
| 101 | +--- | |
| 102 | + | |
| 103 | +## 九、扣款相关字段 | |
| 104 | + | |
| 105 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 106 | +|--------|-----------|------|------|------| | |
| 107 | +| 缺卡扣款 | F_MissingCard | DECIMAL(18,2) | 缺卡扣款金额 | ✅ | | |
| 108 | +| 迟到扣款 | F_LateArrival | DECIMAL(18,2) | 迟到扣款金额 | ✅ | | |
| 109 | +| 请假扣款 | F_LeaveDeduction | DECIMAL(18,2) | 请假扣款金额 | ✅ | | |
| 110 | +| 扣社保 | F_SocialInsuranceDeduction | DECIMAL(18,2) | 社保扣款金额 | ✅ | | |
| 111 | +| 扣除奖励 | F_RewardDeduction | DECIMAL(18,2) | 扣除奖励金额 | ✅ | | |
| 112 | +| 扣住宿费 | F_AccommodationDeduction | DECIMAL(18,2) | 住宿费扣款金额 | ✅ | | |
| 113 | +| 扣学习期费用 | F_StudyPeriodDeduction | DECIMAL(18,2) | 学习期费用扣款 | ✅ | | |
| 114 | +| 扣工作服费用 | F_WorkClothesDeduction | DECIMAL(18,2) | 工作服费用扣款 | ✅ | | |
| 115 | +| 扣款合计 | F_TotalDeduction | DECIMAL(18,2) | 所有扣款金额总和 | ✅ | | |
| 116 | + | |
| 117 | +--- | |
| 118 | + | |
| 119 | +## 十、补贴相关字段 | |
| 120 | + | |
| 121 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 122 | +|--------|-----------|------|------|------| | |
| 123 | +| 当月培训补贴 | F_MonthlyTrainingSubsidy | DECIMAL(18,2) | 当月培训补贴 | ✅ | | |
| 124 | +| 当月交通补贴 | F_MonthlyTransportSubsidy | DECIMAL(18,2) | 当月交通补贴 | ✅ | | |
| 125 | +| 上月培训补贴 | F_LastMonthTrainingSubsidy | DECIMAL(18,2) | 上月培训补贴 | ✅ | | |
| 126 | +| 上月交通补贴 | F_LastMonthTransportSubsidy | DECIMAL(18,2) | 上月交通补贴 | ✅ | | |
| 127 | +| 补贴合计 | F_TotalSubsidy | DECIMAL(18,2) | 所有补贴金额总和 | ✅ | | |
| 128 | + | |
| 129 | +--- | |
| 130 | + | |
| 131 | +## 十一、奖金相关字段 | |
| 132 | + | |
| 133 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 134 | +|--------|-----------|------|------|------| | |
| 135 | +| 发奖金 | F_Bonus | DECIMAL(18,2) | 奖金金额 | ✅ | | |
| 136 | +| 退手机押金 | F_ReturnPhoneDeposit | DECIMAL(18,2) | 退手机押金金额 | ✅ | | |
| 137 | +| 退住宿押金 | F_ReturnAccommodationDeposit | DECIMAL(18,2) | 退住宿押金金额 | ✅ | | |
| 138 | + | |
| 139 | +--- | |
| 140 | + | |
| 141 | +## 十二、支付相关字段 | |
| 142 | + | |
| 143 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 144 | +|--------|-----------|------|------|------| | |
| 145 | +| 当月是否发放 | F_MonthlyPaymentStatus | VARCHAR(20) | 已发放/未发放/部分发放 | ✅ | | |
| 146 | +| 支付金额 | F_PaidAmount | DECIMAL(18,2) | 已支付金额 | ✅ | | |
| 147 | +| 待支付金额 | F_PendingAmount | DECIMAL(18,2) | 待支付金额 | ✅ | | |
| 148 | +| 补发上月 | F_LastMonthSupplement | DECIMAL(18,2) | 补发上月金额 | ✅ | | |
| 149 | +| 当月支付总额 | F_MonthlyTotalPayment | DECIMAL(18,2) | 当月支付总额 | ✅ | | |
| 150 | + | |
| 151 | +--- | |
| 152 | + | |
| 153 | +## 十三、系统字段 | |
| 154 | + | |
| 155 | +| 字段名 | 数据库字段 | 类型 | 说明 | 必填 | | |
| 156 | +|--------|-----------|------|------|------| | |
| 157 | +| 是否锁定 | F_IsLocked | INT | 0=未锁定,1=已锁定 | ✅ | | |
| 158 | +| 创建时间 | F_CreateTime | DATETIME | 创建时间 | ✅ | | |
| 159 | +| 更新时间 | F_UpdateTime | DATETIME | 更新时间 | ✅ | | |
| 160 | +| 创建人 | F_CreateUser | VARCHAR(50) | 创建人ID | ❌ | | |
| 161 | +| 更新人 | F_UpdateUser | VARCHAR(50) | 更新人ID | ❌ | | |
| 162 | + | |
| 163 | +--- | |
| 164 | + | |
| 165 | +## 📊 字段统计 | |
| 166 | + | |
| 167 | +- **总字段数**:约 60+ 个字段 | |
| 168 | +- **必填字段**:约 50+ 个(包含奖金、补贴、支付相关字段) | |
| 169 | +- **可选字段**:约 10 个(主要是系统字段中的创建人、更新人等) | |
| 170 | + | |
| 171 | +--- | |
| 172 | + | |
| 173 | +## 🔑 索引建议 | |
| 174 | + | |
| 175 | +1. **主键索引**:`F_Id`(PRIMARY KEY) | |
| 176 | +2. **唯一索引**:`F_EmployeeId + F_StatisticsMonth`(确保同一员工同一月份只有一条记录) | |
| 177 | +3. **普通索引**: | |
| 178 | + - `F_StoreId`(按门店查询) | |
| 179 | + - `F_StatisticsMonth`(按月份查询) | |
| 180 | + - `F_EmployeeId`(按员工查询) | |
| 181 | + - `F_StoreCategory`(按门店分类查询) | |
| 182 | + | |
| 183 | +--- | |
| 184 | + | |
| 185 | +## 📝 字段说明 | |
| 186 | + | |
| 187 | +### 1. 业绩完成率计算 | |
| 188 | +``` | |
| 189 | +业绩完成率 = 门店业绩 / 门店生命线 × 100% | |
| 190 | +``` | |
| 191 | + | |
| 192 | +### 2. 提成比例判断 | |
| 193 | +``` | |
| 194 | +if (门店业绩 < 门店生命线 × 70%) | |
| 195 | + 提成比例 = 0% | |
| 196 | +else if (门店业绩 < 门店生命线 × 100%) | |
| 197 | + 提成比例 = 0.4% | |
| 198 | +else | |
| 199 | + 提成比例 = 0.6% | |
| 200 | +``` | |
| 201 | + | |
| 202 | +### 3. 阶段奖励计算 | |
| 203 | +``` | |
| 204 | +if (进店消耗人数 >= 第二阶段目标) | |
| 205 | + 阶段奖励 = 400元(第一阶段200 + 第二阶段200) | |
| 206 | +else if (进店消耗人数 >= 第一阶段目标) | |
| 207 | + 阶段奖励 = 200元(第一阶段200) | |
| 208 | +else | |
| 209 | + 阶段奖励 = 0元 | |
| 210 | +``` | |
| 211 | + | |
| 212 | +### 4. 应发工资计算 | |
| 213 | +``` | |
| 214 | +应发工资 = 底薪 + 提成金额 + 阶段奖励金额 + 手机管理费 | |
| 215 | +``` | |
| 216 | + | |
| 217 | +### 5. 实发工资计算 | |
| 218 | +``` | |
| 219 | +实发工资 = 应发工资 - 扣款合计 + 补贴合计 + 奖金 | |
| 220 | +``` | |
| 221 | + | |
| 222 | +--- | |
| 223 | + | |
| 224 | +## ⚠️ 注意事项 | |
| 225 | + | |
| 226 | +1. **数据校验**: | |
| 227 | + - 门店分类必须设置,不允许为NULL | |
| 228 | + - 门店生命线必须设置,未设置应报错 | |
| 229 | + - 阶段目标必须设置,未设置应报错 | |
| 230 | + | |
| 231 | +2. **数据一致性**: | |
| 232 | + - 门店业绩的计算逻辑必须与门店总业绩统计保持一致 | |
| 233 | + - 进店消耗人数的统计逻辑必须与其他统计接口保持一致 | |
| 234 | + | |
| 235 | +3. **字段命名规范**: | |
| 236 | + - 所有字段使用 `F_` 前缀 | |
| 237 | + - 金额字段使用 `DECIMAL(18,2)` 类型 | |
| 238 | + - 日期字段使用 `DATETIME` 类型 | |
| 239 | + - 月份字段使用 `VARCHAR(20)` 类型,格式为 YYYYMM | |
| 240 | + | |
| 241 | +4. **与健康师工资表的差异**: | |
| 242 | + - 店助工资表不需要:个人业绩、战队业绩、新客业绩、升单业绩等个人业绩相关字段 | |
| 243 | + - 店助工资表需要:门店业绩、门店生命线、进店消耗人数、阶段奖励等门店相关字段 | |
| 244 | + - 店助工资表不需要:顾问提成、门店T区提成等健康师特有字段 | |
| 245 | + - 店助工资表需要:手机管理费固定奖励字段 | |
| 246 | + | |
| 247 | +--- | |
| 248 | + | |
| 249 | +## 🔗 相关表关联 | |
| 250 | + | |
| 251 | +1. **BASE_USER**:通过 `F_EmployeeId` 关联员工信息 | |
| 252 | +2. **lq_mdxx**:通过 `F_StoreId` 关联门店信息,获取门店分类 | |
| 253 | +3. **lq_md_target**:通过 `F_StoreId + F_StatisticsMonth` 关联门店目标,获取门店生命线和阶段目标 | |
| 254 | +4. **lq_kd_kdjlb**:用于计算门店开单业绩 | |
| 255 | +5. **lq_hytk_hytk**:用于计算门店退卡业绩 | |
| 256 | +6. **lq_xh_hyhk**:用于统计进店消耗人数 | |
| 257 | +7. **lq_xh_jksyj**:用于判断是否有消费金额 | |
| 258 | + | |
| 259 | +--- | |
| 260 | + | |
| 261 | +## 📅 更新记录 | |
| 262 | + | |
| 263 | +- 2025-01-XX:初始版本,根据健康师工资表结构和店助工资计算规则梳理店助工资表字段 | |
| 264 | + | ... | ... |
店助工资计算规则梳理.md
0 → 100644
| 1 | +# 店助工资计算规则梳理 | |
| 2 | + | |
| 3 | +## 📋 概述 | |
| 4 | + | |
| 5 | +店助工资由以下几个部分组成: | |
| 6 | +1. **底薪**:根据门店分类(A、B、C类)按规则计算 | |
| 7 | +2. **提成**:根据门店业绩与门店生命线的比例计算 | |
| 8 | +3. **阶段奖励**:根据进店消耗人数是否达到阶段目标 | |
| 9 | +4. **固定奖励**:手机管理费 | |
| 10 | + | |
| 11 | +--- | |
| 12 | + | |
| 13 | +# 第一部分:计算规则 | |
| 14 | + | |
| 15 | +## 💰 工资组成规则 | |
| 16 | + | |
| 17 | +### 1. 底薪规则 | |
| 18 | + | |
| 19 | +**计算规则**:根据门店分类(A、B、C类)确定底薪金额 | |
| 20 | + | |
| 21 | +| 门店分类 | 底薪金额 | | |
| 22 | +|---------|---------| | |
| 23 | +| A类门店 | 3000元 | | |
| 24 | +| B类门店 | 3100元 | | |
| 25 | +| C类门店 | 3200元 | | |
| 26 | + | |
| 27 | +**重要说明**: | |
| 28 | +- 门店分类必须设置,系统不允许设置为NULL | |
| 29 | +- 如果门店分类未设置,应在计算工资前进行校验并提示错误 | |
| 30 | + | |
| 31 | +--- | |
| 32 | + | |
| 33 | +### 2. 提成规则 | |
| 34 | + | |
| 35 | +**计算公式**:根据门店业绩与门店生命线的比例确定提成比例,然后按门店业绩的百分比计算提成 | |
| 36 | + | |
| 37 | +#### 提成比例规则 | |
| 38 | + | |
| 39 | +| 门店业绩范围 | 提成比例 | | |
| 40 | +|------------|---------| | |
| 41 | +| 门店业绩 < 门店生命线 × 70% | 0%(无提成) | | |
| 42 | +| 门店生命线 × 70% ≤ 门店业绩 < 门店生命线 × 100% | 0.4% | | |
| 43 | +| 门店业绩 ≥ 门店生命线 × 100% | 0.6% | | |
| 44 | + | |
| 45 | +#### 提成计算示例 | |
| 46 | + | |
| 47 | +假设: | |
| 48 | +- 门店生命线 = 100,000元 | |
| 49 | +- 门店业绩 = 85,000元 | |
| 50 | + | |
| 51 | +计算过程: | |
| 52 | +1. 判断比例:85,000 / 100,000 = 85% | |
| 53 | +2. 判断区间:70% ≤ 85% < 100% | |
| 54 | +3. 提成比例:0.4% | |
| 55 | +4. 提成金额:85,000 × 0.4% = 340元 | |
| 56 | + | |
| 57 | +**重要说明**: | |
| 58 | +- 门店生命线必须在 `lq_md_target` 表中进行设置,如果未设置,系统应报错提示 | |
| 59 | +- 提成按门店业绩的百分比计算 | |
| 60 | + | |
| 61 | +--- | |
| 62 | + | |
| 63 | +### 3. 阶段奖励规则 | |
| 64 | + | |
| 65 | +**考核指标**:进店消耗人数(有消费金额的,按门店按月去重客户数) | |
| 66 | + | |
| 67 | +#### 奖励规则 | |
| 68 | + | |
| 69 | +| 阶段 | 目标人数 | 奖励金额 | 说明 | | |
| 70 | +|-----|---------|---------|------| | |
| 71 | +| 第一阶段 | 达到目标(如:100人) | 200元/月 | 只要达到第一阶段目标即可获得 | | |
| 72 | +| 第二阶段 | 达到目标(如:140人) | 再奖励200元/月 | 在达到第一阶段的基础上,再达到第二阶段目标 | | |
| 73 | +| 两个阶段都达到 | - | 总计400元/月 | 第一阶段200元 + 第二阶段200元 | | |
| 74 | + | |
| 75 | +**奖励说明**: | |
| 76 | +- 两个阶段的奖励是累加的,不是互斥的 | |
| 77 | +- 如果只达到第二阶段但未达到第一阶段,只获得第二阶段奖励(200元) | |
| 78 | +- 如果两个阶段都达到,获得总计400元奖励 | |
| 79 | + | |
| 80 | +**统计规则**: | |
| 81 | +- 按门店统计(`md` 字段) | |
| 82 | +- 只统计有消费金额的记录(消耗金额 > 0) | |
| 83 | +- 同一个会员按月去重(`COUNT(DISTINCT hy)`),其中 `hy` 为会员ID | |
| 84 | +- 只统计当月有效消耗记录(`F_IsEffective = 1`) | |
| 85 | + | |
| 86 | +**重要说明**: | |
| 87 | +- 阶段目标必须在 `lq_md_target` 表中进行设置,如果未设置,系统应报错提示 | |
| 88 | +- 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人) | |
| 89 | + | |
| 90 | +--- | |
| 91 | + | |
| 92 | +### 4. 固定奖励规则 | |
| 93 | + | |
| 94 | +**手机管理费**:150元/月 | |
| 95 | + | |
| 96 | +**说明**: | |
| 97 | +- 固定金额,无需计算 | |
| 98 | +- 每月固定发放 | |
| 99 | + | |
| 100 | +--- | |
| 101 | + | |
| 102 | +## 📊 完整计算公式 | |
| 103 | + | |
| 104 | +``` | |
| 105 | +店助月工资 = 底薪 + 提成 + 阶段奖励 + 固定奖励 | |
| 106 | + | |
| 107 | +其中: | |
| 108 | +- 底薪 = 根据门店分类确定(A类3000元,B类3100元,C类3200元) | |
| 109 | +- 提成 = 门店业绩 × 提成比例(根据业绩与生命线比例确定) | |
| 110 | +- 阶段奖励 = 根据进店消耗人数是否达到阶段目标(0/200/400元) | |
| 111 | +- 固定奖励 = 150元(手机管理费) | |
| 112 | +``` | |
| 113 | + | |
| 114 | +--- | |
| 115 | + | |
| 116 | +## ⚠️ 注意事项 | |
| 117 | + | |
| 118 | +1. **门店业绩计算**: | |
| 119 | + - 门店业绩 = 开单业绩 - 退卡业绩 | |
| 120 | + - 只统计当月有效记录(`F_IsEffective = 1`) | |
| 121 | + | |
| 122 | +2. **进店消耗人数统计**: | |
| 123 | + - 必须使用 `COUNT(DISTINCT hy)` 去重(同一个会员按月去重) | |
| 124 | + - 只统计有消费金额的记录(消耗金额 > 0) | |
| 125 | + - 按门店统计(`md` 字段) | |
| 126 | + - 只统计当月有效消耗记录(`F_IsEffective = 1`) | |
| 127 | + - 通过关联 `lq_xh_jksyj` 表判断是否有消费金额 | |
| 128 | + | |
| 129 | +3. **阶段目标配置**: | |
| 130 | + - 不同门店的阶段目标可能不同(如:川师第一阶段100人,第二阶段140人) | |
| 131 | + - 必须从 `lq_md_target` 表获取对应门店的目标值 | |
| 132 | + - **重要**:如果 `lq_md_target` 表中未设置阶段目标,系统应报错提示 | |
| 133 | + | |
| 134 | +4. **提成比例判断**: | |
| 135 | + - 严格按照门店业绩与门店生命线的比例判断 | |
| 136 | + - 注意边界值:70% 和 100% | |
| 137 | + - 提成按门店业绩的百分比计算 | |
| 138 | + | |
| 139 | +5. **数据一致性**: | |
| 140 | + - 门店业绩的计算逻辑必须与门店总业绩统计保持一致 | |
| 141 | + - 进店消耗人数的统计逻辑必须与其他统计接口保持一致 | |
| 142 | + | |
| 143 | +6. **数据校验要求**: | |
| 144 | + - 门店分类(`F_StoreCategory`)必须设置,不允许为NULL,未设置应报错 | |
| 145 | + - 门店生命线(`F_StoreLifeline`)必须设置,未设置应报错 | |
| 146 | + - 阶段目标(`F_AssistantHeadcountTargetStage1`、`F_AssistantHeadcountTargetStage2`)必须设置,未设置应报错 | |
| 147 | + | |
| 148 | +--- | |
| 149 | + | |
| 150 | +# 第二部分:数据来源说明 | |
| 151 | + | |
| 152 | +## 🔍 核心数据表 | |
| 153 | + | |
| 154 | +### 1. `lq_mdxx` - 门店信息表 | |
| 155 | + | |
| 156 | +**用途**:获取门店分类,用于确定店助底薪 | |
| 157 | + | |
| 158 | +**关键字段**: | |
| 159 | +- `F_Id`:门店ID(主键) | |
| 160 | +- `F_StoreCategory`:门店分类 | |
| 161 | + - `1` = A类门店 | |
| 162 | + - `2` = B类门店 | |
| 163 | + - `3` = C类门店 | |
| 164 | + | |
| 165 | +**查询条件**:门店ID(`F_Id`) | |
| 166 | + | |
| 167 | +**查询示例**: | |
| 168 | +```sql | |
| 169 | +SELECT F_StoreCategory FROM lq_mdxx WHERE F_Id = @StoreId | |
| 170 | +``` | |
| 171 | + | |
| 172 | +**代码实现**: | |
| 173 | +- 实体类:`LqMdxxEntity.StoreCategory`(对应数据库字段 `F_StoreCategory`) | |
| 174 | +- 枚举类:`StoreCategoryEnum`(定义A、B、C类门店) | |
| 175 | +- 在工资计算时,已从门店信息中获取 `StoreCategory` 字段(见 `LqSalaryService.cs` 第345行) | |
| 176 | + | |
| 177 | +--- | |
| 178 | + | |
| 179 | +### 2. `lq_md_target` - 门店目标表 | |
| 180 | + | |
| 181 | +**用途**:获取门店生命线和阶段目标 | |
| 182 | + | |
| 183 | +**关键字段**: | |
| 184 | +- `F_StoreId`:门店ID | |
| 185 | +- `F_Month`:月份(YYYYMM格式) | |
| 186 | +- `F_StoreLifeline`:门店生命线(必须设置,未设置则报错) | |
| 187 | +- `F_AssistantHeadcountTargetStage1`:店助第一阶段目标人数(必须设置,未设置则报错) | |
| 188 | +- `F_AssistantHeadcountTargetStage2`:店助第二阶段目标人数(必须设置,未设置则报错) | |
| 189 | + | |
| 190 | +**查询条件**:门店ID(`F_StoreId`)+ 月份(`F_Month`,YYYYMM格式) | |
| 191 | + | |
| 192 | +**查询示例**: | |
| 193 | +```sql | |
| 194 | +SELECT | |
| 195 | + F_StoreLifeline, | |
| 196 | + F_AssistantHeadcountTargetStage1, | |
| 197 | + F_AssistantHeadcountTargetStage2 | |
| 198 | +FROM lq_md_target | |
| 199 | +WHERE F_StoreId = @StoreId AND F_Month = @Month | |
| 200 | +``` | |
| 201 | + | |
| 202 | +**重要说明**:所有目标字段都必须设置,如果未设置,系统应报错提示 | |
| 203 | + | |
| 204 | +--- | |
| 205 | + | |
| 206 | +### 3. `lq_kd_kdjlb` - 开单记录表 | |
| 207 | + | |
| 208 | +**用途**:计算门店开单业绩 | |
| 209 | + | |
| 210 | +**关键字段**: | |
| 211 | +- `sfyj`:实付业绩(用于计算门店开单业绩总和) | |
| 212 | +- `Kdrq`:开单日期(用于按月过滤) | |
| 213 | +- `F_IsEffective`:是否有效(只统计有效记录) | |
| 214 | + | |
| 215 | +**查询逻辑**: | |
| 216 | +- 按门店、月份汇总 `sfyj` 字段 | |
| 217 | +- 只统计有效记录(`F_IsEffective = 1`) | |
| 218 | + | |
| 219 | +--- | |
| 220 | + | |
| 221 | +### 4. `lq_hytk_hytk` - 退卡记录表 | |
| 222 | + | |
| 223 | +**用途**:计算门店退卡业绩 | |
| 224 | + | |
| 225 | +**关键字段**: | |
| 226 | +- `F_ActualRefundAmount`:实际退卡金额(用于计算门店退卡业绩总和) | |
| 227 | +- `tksj`:退卡时间(用于按月过滤) | |
| 228 | +- `md`:门店ID | |
| 229 | +- `F_IsEffective`:是否有效(只统计有效记录) | |
| 230 | + | |
| 231 | +**查询逻辑**: | |
| 232 | +- 按门店、月份汇总 `F_ActualRefundAmount` 字段 | |
| 233 | +- 只统计有效记录(`F_IsEffective = 1`) | |
| 234 | + | |
| 235 | +--- | |
| 236 | + | |
| 237 | +### 5. `lq_xh_hyhk` - 耗卡记录表 | |
| 238 | + | |
| 239 | +**用途**:统计进店消耗人数 | |
| 240 | + | |
| 241 | +**关键字段**: | |
| 242 | +- `F_Id`:耗卡记录ID(主键) | |
| 243 | +- `hy`:会员ID(用于去重统计) | |
| 244 | +- `md`:门店ID(按门店统计) | |
| 245 | +- `hksj`:耗卡时间(用于按月过滤) | |
| 246 | +- `F_IsEffective`:是否有效(只统计有效记录) | |
| 247 | + | |
| 248 | +**查询逻辑**: | |
| 249 | +- 按门店、月份统计去重会员数 | |
| 250 | +- 只统计有效记录(`F_IsEffective = 1`) | |
| 251 | +- 只统计有消费金额的记录(通过关联 `lq_xh_jksyj` 表判断) | |
| 252 | + | |
| 253 | +--- | |
| 254 | + | |
| 255 | +### 6. `lq_xh_jksyj` - 耗卡健康师业绩表 | |
| 256 | + | |
| 257 | +**用途**:判断是否有消费金额 | |
| 258 | + | |
| 259 | +**关键字段**: | |
| 260 | +- `glkdbh`:关联耗卡记录ID(关联 `lq_xh_hyhk.F_Id`) | |
| 261 | +- `jksyj`:健康师业绩(消耗金额) | |
| 262 | +- `F_IsEffective`:是否有效(只统计有效记录) | |
| 263 | + | |
| 264 | +**查询逻辑**: | |
| 265 | +- 用于判断耗卡记录是否有消费金额(`jksyj > 0`) | |
| 266 | +- 只统计有效记录(`F_IsEffective = 1`) | |
| 267 | + | |
| 268 | +--- | |
| 269 | + | |
| 270 | +## 📝 数据查询逻辑 | |
| 271 | + | |
| 272 | +### 1. 门店业绩计算 | |
| 273 | + | |
| 274 | +**计算公式**:门店业绩 = 开单业绩 - 退卡业绩 | |
| 275 | + | |
| 276 | +**查询逻辑**: | |
| 277 | +```sql | |
| 278 | +-- 1. 计算门店开单业绩 | |
| 279 | +SELECT COALESCE(SUM(sfyj), 0) as BillingPerformance | |
| 280 | +FROM lq_kd_kdjlb | |
| 281 | +WHERE Djmd = @StoreId | |
| 282 | + AND DATE_FORMAT(Kdrq, '%Y%m') = @Month | |
| 283 | + AND F_IsEffective = 1 | |
| 284 | + | |
| 285 | +-- 2. 计算门店退卡业绩 | |
| 286 | +SELECT COALESCE(SUM(F_ActualRefundAmount), 0) as RefundPerformance | |
| 287 | +FROM lq_hytk_hytk | |
| 288 | +WHERE md = @StoreId | |
| 289 | + AND DATE_FORMAT(tksj, '%Y%m') = @Month | |
| 290 | + AND F_IsEffective = 1 | |
| 291 | + | |
| 292 | +-- 3. 计算门店实际业绩 | |
| 293 | +门店业绩 = 开单业绩 - 退卡业绩 | |
| 294 | +``` | |
| 295 | + | |
| 296 | +--- | |
| 297 | + | |
| 298 | +### 2. 进店消耗人数统计 | |
| 299 | + | |
| 300 | +**统计规则**:有消费金额的,按门店按月去重客户数 | |
| 301 | + | |
| 302 | +**查询逻辑**: | |
| 303 | +```sql | |
| 304 | +-- 统计门店当月进店消耗人数(有消费金额的,按门店按月去重客户数) | |
| 305 | +SELECT COUNT(DISTINCT hy) as HeadCount | |
| 306 | +FROM lq_xh_hyhk hk | |
| 307 | +WHERE hk.md = @StoreId | |
| 308 | + AND DATE_FORMAT(hk.hksj, '%Y%m') = @Month | |
| 309 | + AND hk.F_IsEffective = 1 | |
| 310 | + AND EXISTS ( | |
| 311 | + -- 确保有消费金额(通过关联消耗业绩表判断) | |
| 312 | + SELECT 1 | |
| 313 | + FROM lq_xh_jksyj jksyj | |
| 314 | + WHERE jksyj.glkdbh = hk.F_Id | |
| 315 | + AND jksyj.F_IsEffective = 1 | |
| 316 | + AND jksyj.jksyj > 0 | |
| 317 | + ) | |
| 318 | +``` | |
| 319 | + | |
| 320 | +**说明**: | |
| 321 | +- `md`:门店ID,按门店统计 | |
| 322 | +- `hy`:会员ID,用于去重统计(同一个会员按月去重) | |
| 323 | +- `hksj`:耗卡时间,用于按月过滤 | |
| 324 | +- `F_IsEffective = 1`:只统计有效记录 | |
| 325 | +- **重要**:只统计有消费金额的记录(消耗金额 > 0),通过关联 `lq_xh_jksyj` 表判断是否有消费金额 | |
| 326 | + | |
| 327 | +--- | |
| 328 | + | |
| 329 | +### 3. 门店分类获取 | |
| 330 | + | |
| 331 | +**查询逻辑**: | |
| 332 | +```sql | |
| 333 | +-- 获取门店分类 | |
| 334 | +SELECT F_StoreCategory FROM lq_mdxx WHERE F_Id = @StoreId | |
| 335 | + | |
| 336 | +-- 根据门店分类确定底薪 | |
| 337 | +-- F_StoreCategory = 1 → 底薪 = 3000元 | |
| 338 | +-- F_StoreCategory = 2 → 底薪 = 3100元 | |
| 339 | +-- F_StoreCategory = 3 → 底薪 = 3200元 | |
| 340 | +``` | |
| 341 | + | |
| 342 | +**代码实现**: | |
| 343 | +```csharp | |
| 344 | +// 从门店信息中获取 | |
| 345 | +var store = await _db.Queryable<LqMdxxEntity>() | |
| 346 | + .Where(x => x.Id == storeId) | |
| 347 | + .FirstAsync(); | |
| 348 | + | |
| 349 | +int? storeCategory = store.StoreCategory; // 1=A类, 2=B类, 3=C类 | |
| 350 | + | |
| 351 | +// 根据门店分类确定店助底薪 | |
| 352 | +decimal baseSalary = storeCategory switch | |
| 353 | +{ | |
| 354 | + 1 => 3000m, // A类门店 | |
| 355 | + 2 => 3100m, // B类门店 | |
| 356 | + 3 => 3200m, // C类门店 | |
| 357 | + _ => throw new Exception($"门店分类未设置或无效:门店ID={storeId}") | |
| 358 | +}; | |
| 359 | +``` | |
| 360 | + | |
| 361 | +--- | |
| 362 | + | |
| 363 | +## 🔗 相关代码位置 | |
| 364 | + | |
| 365 | +- **门店信息查询**:`LqMdxxService.cs` | |
| 366 | +- **门店分类枚举**:`StoreCategoryEnum.cs`(`netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/`) | |
| 367 | +- **门店实体类**:`LqMdxxEntity.cs`(`F_StoreCategory` 字段) | |
| 368 | +- **工资计算服务**:`LqSalaryService.cs`(第345行获取 `StoreCategory`) | |
| 369 | +- **门店目标查询**:`LqMdTargetService.cs` | |
| 370 | +- **门店业绩统计**:`LqStatisticsService.cs` / `LqDailyReportService.cs` | |
| 371 | +- **进店消耗人数统计**:`LqStatisticsService.cs` / `LqDailyReportService.cs` | |
| 372 | + | |
| 373 | +--- | |
| 374 | + | |
| 375 | +## 📅 更新记录 | |
| 376 | + | |
| 377 | +- 2025-01-XX:初始版本,梳理店助工资计算规则 | |
| 378 | +- 2025-01-XX:更新底薪规则,明确按A、B、C类门店分类确定底薪(A类3000元,B类3100元,C类3200元) | |
| 379 | +- 2025-01-XX:明确A、B、C类门店来源:`lq_mdxx` 表的 `F_StoreCategory` 字段(枚举值:1=A类,2=B类,3=C类) | |
| 380 | +- 2025-01-XX:明确数据校验要求:门店分类必须设置,`lq_md_target` 表的目标字段必须设置,未设置应报错 | |
| 381 | +- 2025-01-XX:明确进店消耗人数统计规则:有消费金额的,按门店按月去重,同一个会员按月去重 | |
| 382 | +- 2025-01-XX:明确提成计算:按门店业绩的百分比计算 | |
| 383 | +- 2025-01-XX:重新组织文档结构,分为规则部分和数据来源说明部分 | ... | ... |
项目信息-薪酬规则与名词解释.md
| ... | ... | @@ -68,7 +68,6 @@ |
| 68 | 68 | |
| 69 | 69 | ### 通用规则 |
| 70 | 70 | |
| 71 | -1. **单人业绩提成门槛**:单人业绩 ≤ 6000元,无提成 | |
| 72 | 71 | 2. **金三角(战队)标准**:组员当月考勤天数 ≥ 20天,则算战队组成成功,可按战队提成计算 |
| 73 | 72 | |
| 74 | 73 | ### 健康师薪酬规则 |
| ... | ... | @@ -129,6 +128,11 @@ |
| 129 | 128 | |
| 130 | 129 | ### 店助考核规则 |
| 131 | 130 | |
| 131 | +店助底薪规则 | |
| 132 | +A类门店:3000 元 | |
| 133 | +B类门店:3100 元 | |
| 134 | +C类门店:3200 元 | |
| 135 | + | |
| 132 | 136 | #### 考核阶段 |
| 133 | 137 | 每个门店均有两个阶段的考核(如:川师) |
| 134 | 138 | - 第一阶段:100人(进店消耗人数) |
| ... | ... | @@ -141,8 +145,8 @@ |
| 141 | 145 | |
| 142 | 146 | #### 提成计算 |
| 143 | 147 | - 当月门店业绩 < 门店生命线×70% → 无提成 |
| 144 | -- 门店生命线×70% ≤ 当月门店业绩 < 门店生命线×100% → 0.4% | |
| 145 | -- 当月门店业绩 > 门店生命线 → 0.6% | |
| 148 | +- 门店生命线×70% ≤ 当月门店业绩 ≤门店生命线×100% → 0.4% | |
| 149 | +- 当月门店业绩 > 门店生命线*100%→ 0.6% | |
| 146 | 150 | |
| 147 | 151 | #### 固定奖励 |
| 148 | 152 | - 手机管理费:150元 | ... | ... |