Commit c63026bcaa223e67e238bb9c9cde58fc3017bb5c
1 parent
a32e3ff6
feat: 优化工资计算逻辑
1. 主任工资计算改为基于毛利 - 新增毛利相关字段(销售业绩、产品物料、合作项目成本、店内支出、洗毛巾费用、毛利) - 提成计算基于毛利而非销售业绩 - 业绩完成率和业绩达标判断基于毛利 2. 店助工资计算优化 - 底薪按在店天数比例计算 - 提成按在店天数比例计算 - 手机管理费按在店天数比例计算 - 阶段奖励按在店天数比例计算 3. 主任工资计算优化 - 底薪按在店天数比例计算 - 提成按在店天数比例计算 4. 新增工资计算功能 - 大项目主管工资计算 - 科技部总经理工资计算
Showing
32 changed files
with
4638 additions
and
257 deletions
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMajorProjectDirectorSalary/MajorProjectDirectorSalaryInput.cs
0 → 100644
| 1 | +using NCC.Common.Filter; | |
| 2 | +using System; | |
| 3 | + | |
| 4 | +namespace NCC.Extend.Entitys.Dto.LqMajorProjectDirectorSalary | |
| 5 | +{ | |
| 6 | + /// <summary> | |
| 7 | + /// 大项目主管工资查询参数 | |
| 8 | + /// </summary> | |
| 9 | + public class MajorProjectDirectorSalaryInput : 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 | + /// 岗位(大项目一部/大项目二部等,不传则查询全部) | |
| 23 | + /// </summary> | |
| 24 | + public string Position { 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/LqMajorProjectDirectorSalary/MajorProjectDirectorSalaryOutput.cs
0 → 100644
| 1 | +using System; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Dto.LqMajorProjectDirectorSalary | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 大项目主管工资输出 | |
| 7 | + /// </summary> | |
| 8 | + public class MajorProjectDirectorSalaryOutput | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 主键ID | |
| 12 | + /// </summary> | |
| 13 | + public string Id { get; set; } | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 统计月份 | |
| 17 | + /// </summary> | |
| 18 | + public string StatisticsMonth { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 核算岗位(大项目一部/大项目二部等) | |
| 22 | + /// </summary> | |
| 23 | + public string Position { get; set; } | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 员工姓名 | |
| 27 | + /// </summary> | |
| 28 | + public string EmployeeName { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 员工ID | |
| 32 | + /// </summary> | |
| 33 | + public string EmployeeId { get; set; } | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 员工账号 | |
| 37 | + /// </summary> | |
| 38 | + public string EmployeeAccount { get; set; } | |
| 39 | + | |
| 40 | + /// <summary> | |
| 41 | + /// 是否离职 | |
| 42 | + /// </summary> | |
| 43 | + public int IsTerminated { get; set; } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 管理的门店明细(JSON格式) | |
| 47 | + /// </summary> | |
| 48 | + public string StoreDetail { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 总业绩 | |
| 52 | + /// </summary> | |
| 53 | + public decimal TotalPerformance { get; set; } | |
| 54 | + | |
| 55 | + /// <summary> | |
| 56 | + /// 开单金额 | |
| 57 | + /// </summary> | |
| 58 | + public decimal BillingAmount { get; set; } | |
| 59 | + | |
| 60 | + /// <summary> | |
| 61 | + /// 退卡金额 | |
| 62 | + /// </summary> | |
| 63 | + public decimal RefundAmount { get; set; } | |
| 64 | + | |
| 65 | + /// <summary> | |
| 66 | + /// 底薪 | |
| 67 | + /// </summary> | |
| 68 | + public decimal BaseSalary { get; set; } | |
| 69 | + | |
| 70 | + /// <summary> | |
| 71 | + /// 提成比例 | |
| 72 | + /// </summary> | |
| 73 | + public decimal? CommissionRate { get; set; } | |
| 74 | + | |
| 75 | + /// <summary> | |
| 76 | + /// 提成金额 | |
| 77 | + /// </summary> | |
| 78 | + public decimal CommissionAmount { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 在店天数 | |
| 82 | + /// </summary> | |
| 83 | + public decimal WorkingDays { get; set; } | |
| 84 | + | |
| 85 | + /// <summary> | |
| 86 | + /// 请假天数 | |
| 87 | + /// </summary> | |
| 88 | + public decimal LeaveDays { get; set; } | |
| 89 | + | |
| 90 | + /// <summary> | |
| 91 | + /// 核算应发工资 | |
| 92 | + /// </summary> | |
| 93 | + public decimal CalculatedGrossSalary { get; set; } | |
| 94 | + | |
| 95 | + /// <summary> | |
| 96 | + /// 最终应发工资 | |
| 97 | + /// </summary> | |
| 98 | + public decimal FinalGrossSalary { get; set; } | |
| 99 | + | |
| 100 | + /// <summary> | |
| 101 | + /// 当月培训补贴 | |
| 102 | + /// </summary> | |
| 103 | + public decimal MonthlyTrainingSubsidy { get; set; } | |
| 104 | + | |
| 105 | + /// <summary> | |
| 106 | + /// 当月交通补贴 | |
| 107 | + /// </summary> | |
| 108 | + public decimal MonthlyTransportSubsidy { get; set; } | |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// 上月培训补贴 | |
| 112 | + /// </summary> | |
| 113 | + public decimal LastMonthTrainingSubsidy { get; set; } | |
| 114 | + | |
| 115 | + /// <summary> | |
| 116 | + /// 上月交通补贴 | |
| 117 | + /// </summary> | |
| 118 | + public decimal LastMonthTransportSubsidy { get; set; } | |
| 119 | + | |
| 120 | + /// <summary> | |
| 121 | + /// 补贴合计 | |
| 122 | + /// </summary> | |
| 123 | + public decimal TotalSubsidy { get; set; } | |
| 124 | + | |
| 125 | + /// <summary> | |
| 126 | + /// 缺卡扣款 | |
| 127 | + /// </summary> | |
| 128 | + public decimal MissingCard { get; set; } | |
| 129 | + | |
| 130 | + /// <summary> | |
| 131 | + /// 迟到扣款 | |
| 132 | + /// </summary> | |
| 133 | + public decimal LateArrival { get; set; } | |
| 134 | + | |
| 135 | + /// <summary> | |
| 136 | + /// 请假扣款 | |
| 137 | + /// </summary> | |
| 138 | + public decimal LeaveDeduction { get; set; } | |
| 139 | + | |
| 140 | + /// <summary> | |
| 141 | + /// 扣社保 | |
| 142 | + /// </summary> | |
| 143 | + public decimal SocialInsuranceDeduction { get; set; } | |
| 144 | + | |
| 145 | + /// <summary> | |
| 146 | + /// 扣除奖励 | |
| 147 | + /// </summary> | |
| 148 | + public decimal RewardDeduction { get; set; } | |
| 149 | + | |
| 150 | + /// <summary> | |
| 151 | + /// 扣住宿费 | |
| 152 | + /// </summary> | |
| 153 | + public decimal AccommodationDeduction { get; set; } | |
| 154 | + | |
| 155 | + /// <summary> | |
| 156 | + /// 扣学习期费用 | |
| 157 | + /// </summary> | |
| 158 | + public decimal StudyPeriodDeduction { get; set; } | |
| 159 | + | |
| 160 | + /// <summary> | |
| 161 | + /// 扣工作服费用 | |
| 162 | + /// </summary> | |
| 163 | + public decimal WorkClothesDeduction { get; set; } | |
| 164 | + | |
| 165 | + /// <summary> | |
| 166 | + /// 扣款合计 | |
| 167 | + /// </summary> | |
| 168 | + public decimal TotalDeduction { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 发奖金 | |
| 172 | + /// </summary> | |
| 173 | + public decimal Bonus { get; set; } | |
| 174 | + | |
| 175 | + /// <summary> | |
| 176 | + /// 退手机押金 | |
| 177 | + /// </summary> | |
| 178 | + public decimal ReturnPhoneDeposit { get; set; } | |
| 179 | + | |
| 180 | + /// <summary> | |
| 181 | + /// 退住宿押金 | |
| 182 | + /// </summary> | |
| 183 | + public decimal ReturnAccommodationDeposit { get; set; } | |
| 184 | + | |
| 185 | + /// <summary> | |
| 186 | + /// 实发工资 | |
| 187 | + /// </summary> | |
| 188 | + public decimal ActualSalary { get; set; } | |
| 189 | + | |
| 190 | + /// <summary> | |
| 191 | + /// 当月是否发放 | |
| 192 | + /// </summary> | |
| 193 | + public string MonthlyPaymentStatus { get; set; } | |
| 194 | + | |
| 195 | + /// <summary> | |
| 196 | + /// 支付金额 | |
| 197 | + /// </summary> | |
| 198 | + public decimal PaidAmount { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 待支付金额 | |
| 202 | + /// </summary> | |
| 203 | + public decimal PendingAmount { get; set; } | |
| 204 | + | |
| 205 | + /// <summary> | |
| 206 | + /// 补发上月 | |
| 207 | + /// </summary> | |
| 208 | + public decimal LastMonthSupplement { get; set; } | |
| 209 | + | |
| 210 | + /// <summary> | |
| 211 | + /// 当月支付总额 | |
| 212 | + /// </summary> | |
| 213 | + public decimal MonthlyTotalPayment { get; set; } | |
| 214 | + | |
| 215 | + /// <summary> | |
| 216 | + /// 是否锁定 | |
| 217 | + /// </summary> | |
| 218 | + public int IsLocked { get; set; } | |
| 219 | + | |
| 220 | + /// <summary> | |
| 221 | + /// 更新时间 | |
| 222 | + /// </summary> | |
| 223 | + public DateTime UpdateTime { get; set; } | |
| 224 | + } | |
| 225 | +} | |
| 226 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInfoOutput.cs
| ... | ... | @@ -23,6 +23,11 @@ namespace NCC.Extend.Entitys.Dto.LqProduct |
| 23 | 23 | public decimal price { get; set; } |
| 24 | 24 | |
| 25 | 25 | /// <summary> |
| 26 | + /// 平均单价(加权平均成本,用于出库计价) | |
| 27 | + /// </summary> | |
| 28 | + public decimal averagePrice { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 26 | 31 | /// 产品类别 |
| 27 | 32 | /// </summary> |
| 28 | 33 | public string productCategory { get; set; } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductListOutput.cs
| ... | ... | @@ -23,6 +23,11 @@ namespace NCC.Extend.Entitys.Dto.LqProduct |
| 23 | 23 | public decimal price { get; set; } |
| 24 | 24 | |
| 25 | 25 | /// <summary> |
| 26 | + /// 平均单价(加权平均成本,用于出库计价) | |
| 27 | + /// </summary> | |
| 28 | + public decimal averagePrice { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 26 | 31 | /// 产品类别 |
| 27 | 32 | /// </summary> |
| 28 | 33 | public string productCategory { get; set; } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechGeneralManagerSalary/TechGeneralManagerSalaryInput.cs
0 → 100644
| 1 | +using NCC.Common.Filter; | |
| 2 | +using System; | |
| 3 | + | |
| 4 | +namespace NCC.Extend.Entitys.Dto.LqTechGeneralManagerSalary | |
| 5 | +{ | |
| 6 | + /// <summary> | |
| 7 | + /// 科技部总经理工资查询参数 | |
| 8 | + /// </summary> | |
| 9 | + public class TechGeneralManagerSalaryInput : 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 | + /// 岗位(科技一部/科技二部等,不传则查询全部) | |
| 23 | + /// </summary> | |
| 24 | + public string Position { 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/LqTechGeneralManagerSalary/TechGeneralManagerSalaryOutput.cs
0 → 100644
| 1 | +using System; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Dto.LqTechGeneralManagerSalary | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 科技部总经理工资输出 | |
| 7 | + /// </summary> | |
| 8 | + public class TechGeneralManagerSalaryOutput | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 主键ID | |
| 12 | + /// </summary> | |
| 13 | + public string Id { get; set; } | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 统计月份 | |
| 17 | + /// </summary> | |
| 18 | + public string StatisticsMonth { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 核算岗位(科技一部/科技二部等) | |
| 22 | + /// </summary> | |
| 23 | + public string Position { get; set; } | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 员工姓名 | |
| 27 | + /// </summary> | |
| 28 | + public string EmployeeName { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 员工ID | |
| 32 | + /// </summary> | |
| 33 | + public string EmployeeId { get; set; } | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 员工账号 | |
| 37 | + /// </summary> | |
| 38 | + public string EmployeeAccount { get; set; } | |
| 39 | + | |
| 40 | + /// <summary> | |
| 41 | + /// 是否离职 | |
| 42 | + /// </summary> | |
| 43 | + public int IsTerminated { get; set; } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 管理的门店明细(JSON格式) | |
| 47 | + /// </summary> | |
| 48 | + public string StoreDetail { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 溯源金额 | |
| 52 | + /// </summary> | |
| 53 | + public decimal TraceabilityAmount { get; set; } | |
| 54 | + | |
| 55 | + /// <summary> | |
| 56 | + /// Cell金额 | |
| 57 | + /// </summary> | |
| 58 | + public decimal CellAmount { get; set; } | |
| 59 | + | |
| 60 | + /// <summary> | |
| 61 | + /// 底薪 | |
| 62 | + /// </summary> | |
| 63 | + public decimal BaseSalary { get; set; } | |
| 64 | + | |
| 65 | + /// <summary> | |
| 66 | + /// 溯源金额提成比例 | |
| 67 | + /// </summary> | |
| 68 | + public decimal? TraceabilityCommissionRate { get; set; } | |
| 69 | + | |
| 70 | + /// <summary> | |
| 71 | + /// 溯源金额提成金额 | |
| 72 | + /// </summary> | |
| 73 | + public decimal TraceabilityCommissionAmount { get; set; } | |
| 74 | + | |
| 75 | + /// <summary> | |
| 76 | + /// Cell金额提成比例 | |
| 77 | + /// </summary> | |
| 78 | + public decimal? CellCommissionRate { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// Cell金额提成金额 | |
| 82 | + /// </summary> | |
| 83 | + public decimal CellCommissionAmount { get; set; } | |
| 84 | + | |
| 85 | + /// <summary> | |
| 86 | + /// 提成合计 | |
| 87 | + /// </summary> | |
| 88 | + public decimal TotalCommission { get; set; } | |
| 89 | + | |
| 90 | + /// <summary> | |
| 91 | + /// 在店天数 | |
| 92 | + /// </summary> | |
| 93 | + public decimal WorkingDays { get; set; } | |
| 94 | + | |
| 95 | + /// <summary> | |
| 96 | + /// 请假天数 | |
| 97 | + /// </summary> | |
| 98 | + public decimal LeaveDays { get; set; } | |
| 99 | + | |
| 100 | + /// <summary> | |
| 101 | + /// 核算应发工资 | |
| 102 | + /// </summary> | |
| 103 | + public decimal CalculatedGrossSalary { get; set; } | |
| 104 | + | |
| 105 | + /// <summary> | |
| 106 | + /// 最终应发工资 | |
| 107 | + /// </summary> | |
| 108 | + public decimal FinalGrossSalary { get; set; } | |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// 当月培训补贴 | |
| 112 | + /// </summary> | |
| 113 | + public decimal MonthlyTrainingSubsidy { get; set; } | |
| 114 | + | |
| 115 | + /// <summary> | |
| 116 | + /// 当月交通补贴 | |
| 117 | + /// </summary> | |
| 118 | + public decimal MonthlyTransportSubsidy { get; set; } | |
| 119 | + | |
| 120 | + /// <summary> | |
| 121 | + /// 上月培训补贴 | |
| 122 | + /// </summary> | |
| 123 | + public decimal LastMonthTrainingSubsidy { get; set; } | |
| 124 | + | |
| 125 | + /// <summary> | |
| 126 | + /// 上月交通补贴 | |
| 127 | + /// </summary> | |
| 128 | + public decimal LastMonthTransportSubsidy { get; set; } | |
| 129 | + | |
| 130 | + /// <summary> | |
| 131 | + /// 补贴合计 | |
| 132 | + /// </summary> | |
| 133 | + public decimal TotalSubsidy { get; set; } | |
| 134 | + | |
| 135 | + /// <summary> | |
| 136 | + /// 缺卡扣款 | |
| 137 | + /// </summary> | |
| 138 | + public decimal MissingCard { get; set; } | |
| 139 | + | |
| 140 | + /// <summary> | |
| 141 | + /// 迟到扣款 | |
| 142 | + /// </summary> | |
| 143 | + public decimal LateArrival { get; set; } | |
| 144 | + | |
| 145 | + /// <summary> | |
| 146 | + /// 请假扣款 | |
| 147 | + /// </summary> | |
| 148 | + public decimal LeaveDeduction { get; set; } | |
| 149 | + | |
| 150 | + /// <summary> | |
| 151 | + /// 扣社保 | |
| 152 | + /// </summary> | |
| 153 | + public decimal SocialInsuranceDeduction { get; set; } | |
| 154 | + | |
| 155 | + /// <summary> | |
| 156 | + /// 扣除奖励 | |
| 157 | + /// </summary> | |
| 158 | + public decimal RewardDeduction { get; set; } | |
| 159 | + | |
| 160 | + /// <summary> | |
| 161 | + /// 扣住宿费 | |
| 162 | + /// </summary> | |
| 163 | + public decimal AccommodationDeduction { get; set; } | |
| 164 | + | |
| 165 | + /// <summary> | |
| 166 | + /// 扣学习期费用 | |
| 167 | + /// </summary> | |
| 168 | + public decimal StudyPeriodDeduction { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 扣工作服费用 | |
| 172 | + /// </summary> | |
| 173 | + public decimal WorkClothesDeduction { get; set; } | |
| 174 | + | |
| 175 | + /// <summary> | |
| 176 | + /// 扣款合计 | |
| 177 | + /// </summary> | |
| 178 | + public decimal TotalDeduction { get; set; } | |
| 179 | + | |
| 180 | + /// <summary> | |
| 181 | + /// 发奖金 | |
| 182 | + /// </summary> | |
| 183 | + public decimal Bonus { get; set; } | |
| 184 | + | |
| 185 | + /// <summary> | |
| 186 | + /// 退手机押金 | |
| 187 | + /// </summary> | |
| 188 | + public decimal ReturnPhoneDeposit { get; set; } | |
| 189 | + | |
| 190 | + /// <summary> | |
| 191 | + /// 退住宿押金 | |
| 192 | + /// </summary> | |
| 193 | + public decimal ReturnAccommodationDeposit { get; set; } | |
| 194 | + | |
| 195 | + /// <summary> | |
| 196 | + /// 实发工资 | |
| 197 | + /// </summary> | |
| 198 | + public decimal ActualSalary { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 当月是否发放 | |
| 202 | + /// </summary> | |
| 203 | + public string MonthlyPaymentStatus { get; set; } | |
| 204 | + | |
| 205 | + /// <summary> | |
| 206 | + /// 支付金额 | |
| 207 | + /// </summary> | |
| 208 | + public decimal PaidAmount { get; set; } | |
| 209 | + | |
| 210 | + /// <summary> | |
| 211 | + /// 待支付金额 | |
| 212 | + /// </summary> | |
| 213 | + public decimal PendingAmount { get; set; } | |
| 214 | + | |
| 215 | + /// <summary> | |
| 216 | + /// 补发上月 | |
| 217 | + /// </summary> | |
| 218 | + public decimal LastMonthSupplement { get; set; } | |
| 219 | + | |
| 220 | + /// <summary> | |
| 221 | + /// 当月支付总额 | |
| 222 | + /// </summary> | |
| 223 | + public decimal MonthlyTotalPayment { get; set; } | |
| 224 | + | |
| 225 | + /// <summary> | |
| 226 | + /// 是否锁定 | |
| 227 | + /// </summary> | |
| 228 | + public int IsLocked { get; set; } | |
| 229 | + | |
| 230 | + /// <summary> | |
| 231 | + /// 更新时间 | |
| 232 | + /// </summary> | |
| 233 | + public DateTime UpdateTime { get; set; } | |
| 234 | + } | |
| 235 | +} | |
| 236 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_director_salary_statistics/LqDirectorSalaryStatisticsEntity.cs
| ... | ... | @@ -96,6 +96,42 @@ namespace NCC.Extend.Entitys.lq_director_salary_statistics |
| 96 | 96 | public decimal StoreRefundPerformance { get; set; } |
| 97 | 97 | |
| 98 | 98 | /// <summary> |
| 99 | + /// 销售业绩(开单业绩-退款业绩) | |
| 100 | + /// </summary> | |
| 101 | + [SugarColumn(ColumnName = "F_SalesPerformance")] | |
| 102 | + public decimal SalesPerformance { get; set; } | |
| 103 | + | |
| 104 | + /// <summary> | |
| 105 | + /// 产品物料(仓库领用金额) | |
| 106 | + /// </summary> | |
| 107 | + [SugarColumn(ColumnName = "F_ProductMaterial")] | |
| 108 | + public decimal ProductMaterial { get; set; } | |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// 合作项目成本 | |
| 112 | + /// </summary> | |
| 113 | + [SugarColumn(ColumnName = "F_CooperationCost")] | |
| 114 | + public decimal CooperationCost { get; set; } | |
| 115 | + | |
| 116 | + /// <summary> | |
| 117 | + /// 店内支出 | |
| 118 | + /// </summary> | |
| 119 | + [SugarColumn(ColumnName = "F_StoreExpense")] | |
| 120 | + public decimal StoreExpense { get; set; } | |
| 121 | + | |
| 122 | + /// <summary> | |
| 123 | + /// 洗毛巾费用 | |
| 124 | + /// </summary> | |
| 125 | + [SugarColumn(ColumnName = "F_LaundryCost")] | |
| 126 | + public decimal LaundryCost { get; set; } | |
| 127 | + | |
| 128 | + /// <summary> | |
| 129 | + /// 毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾) | |
| 130 | + /// </summary> | |
| 131 | + [SugarColumn(ColumnName = "F_GrossProfit")] | |
| 132 | + public decimal GrossProfit { get; set; } | |
| 133 | + | |
| 134 | + /// <summary> | |
| 99 | 135 | /// 门店生命线 |
| 100 | 136 | /// </summary> |
| 101 | 137 | [SugarColumn(ColumnName = "F_StoreLifeline")] | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_major_project_director_salary_statistics/LqMajorProjectDirectorSalaryStatisticsEntity.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using NCC.Common.Const; | |
| 3 | +using SqlSugar; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.lq_major_project_director_salary_statistics | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 大项目主管工资统计表 | |
| 9 | + /// </summary> | |
| 10 | + [SugarTable("lq_major_project_director_salary_statistics")] | |
| 11 | + [Tenant(ClaimConst.TENANT_ID)] | |
| 12 | + public class LqMajorProjectDirectorSalaryStatisticsEntity | |
| 13 | + { | |
| 14 | + /// <summary> | |
| 15 | + /// 主键ID | |
| 16 | + /// </summary> | |
| 17 | + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] | |
| 18 | + public string Id { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 统计月份(YYYYMM) | |
| 22 | + /// </summary> | |
| 23 | + [SugarColumn(ColumnName = "F_StatisticsMonth", Length = 6)] | |
| 24 | + public string StatisticsMonth { get; set; } | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 核算岗位(大项目一部/大项目二部等) | |
| 28 | + /// </summary> | |
| 29 | + [SugarColumn(ColumnName = "F_Position")] | |
| 30 | + public string Position { get; set; } | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 员工姓名 | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_EmployeeName")] | |
| 36 | + public string EmployeeName { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 员工ID | |
| 40 | + /// </summary> | |
| 41 | + [SugarColumn(ColumnName = "F_EmployeeId")] | |
| 42 | + public string EmployeeId { get; set; } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 员工账号 | |
| 46 | + /// </summary> | |
| 47 | + [SugarColumn(ColumnName = "F_EmployeeAccount")] | |
| 48 | + public string EmployeeAccount { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 是否离职(0=在职,1=离职) | |
| 52 | + /// </summary> | |
| 53 | + [SugarColumn(ColumnName = "F_IsTerminated")] | |
| 54 | + public int IsTerminated { get; set; } | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 管理的门店明细(JSON格式) | |
| 58 | + /// </summary> | |
| 59 | + [SugarColumn(ColumnName = "F_StoreDetail", ColumnDataType = "TEXT")] | |
| 60 | + public string StoreDetail { get; set; } | |
| 61 | + | |
| 62 | + /// <summary> | |
| 63 | + /// 总业绩(管理的所有门店的总业绩总和,开单-退卡) | |
| 64 | + /// </summary> | |
| 65 | + [SugarColumn(ColumnName = "F_TotalPerformance")] | |
| 66 | + public decimal TotalPerformance { get; set; } | |
| 67 | + | |
| 68 | + /// <summary> | |
| 69 | + /// 开单金额(管理的所有门店的开单金额总和) | |
| 70 | + /// </summary> | |
| 71 | + [SugarColumn(ColumnName = "F_BillingAmount")] | |
| 72 | + public decimal BillingAmount { get; set; } | |
| 73 | + | |
| 74 | + /// <summary> | |
| 75 | + /// 退卡金额(管理的所有门店的退卡金额总和) | |
| 76 | + /// </summary> | |
| 77 | + [SugarColumn(ColumnName = "F_RefundAmount")] | |
| 78 | + public decimal RefundAmount { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 底薪金额(固定3500元) | |
| 82 | + /// </summary> | |
| 83 | + [SugarColumn(ColumnName = "F_BaseSalary")] | |
| 84 | + public decimal BaseSalary { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 提成比例(根据总业绩分段:0%/1%/1.5%) | |
| 88 | + /// </summary> | |
| 89 | + [SugarColumn(ColumnName = "F_CommissionRate")] | |
| 90 | + public decimal? CommissionRate { get; set; } | |
| 91 | + | |
| 92 | + /// <summary> | |
| 93 | + /// 提成金额(总业绩 × 提成比例) | |
| 94 | + /// </summary> | |
| 95 | + [SugarColumn(ColumnName = "F_CommissionAmount")] | |
| 96 | + public decimal CommissionAmount { get; set; } | |
| 97 | + | |
| 98 | + /// <summary> | |
| 99 | + /// 在店天数 | |
| 100 | + /// </summary> | |
| 101 | + [SugarColumn(ColumnName = "F_WorkingDays")] | |
| 102 | + public decimal WorkingDays { get; set; } | |
| 103 | + | |
| 104 | + /// <summary> | |
| 105 | + /// 请假天数 | |
| 106 | + /// </summary> | |
| 107 | + [SugarColumn(ColumnName = "F_LeaveDays")] | |
| 108 | + public decimal LeaveDays { get; set; } | |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// 核算应发工资(底薪 + 提成金额) | |
| 112 | + /// </summary> | |
| 113 | + [SugarColumn(ColumnName = "F_CalculatedGrossSalary")] | |
| 114 | + public decimal CalculatedGrossSalary { get; set; } | |
| 115 | + | |
| 116 | + /// <summary> | |
| 117 | + /// 最终应发工资 | |
| 118 | + /// </summary> | |
| 119 | + [SugarColumn(ColumnName = "F_FinalGrossSalary")] | |
| 120 | + public decimal FinalGrossSalary { get; set; } | |
| 121 | + | |
| 122 | + /// <summary> | |
| 123 | + /// 当月培训补贴 | |
| 124 | + /// </summary> | |
| 125 | + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")] | |
| 126 | + public decimal MonthlyTrainingSubsidy { get; set; } | |
| 127 | + | |
| 128 | + /// <summary> | |
| 129 | + /// 当月交通补贴 | |
| 130 | + /// </summary> | |
| 131 | + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")] | |
| 132 | + public decimal MonthlyTransportSubsidy { get; set; } | |
| 133 | + | |
| 134 | + /// <summary> | |
| 135 | + /// 上月培训补贴 | |
| 136 | + /// </summary> | |
| 137 | + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")] | |
| 138 | + public decimal LastMonthTrainingSubsidy { get; set; } | |
| 139 | + | |
| 140 | + /// <summary> | |
| 141 | + /// 上月交通补贴 | |
| 142 | + /// </summary> | |
| 143 | + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")] | |
| 144 | + public decimal LastMonthTransportSubsidy { get; set; } | |
| 145 | + | |
| 146 | + /// <summary> | |
| 147 | + /// 补贴合计 | |
| 148 | + /// </summary> | |
| 149 | + [SugarColumn(ColumnName = "F_TotalSubsidy")] | |
| 150 | + public decimal TotalSubsidy { get; set; } | |
| 151 | + | |
| 152 | + /// <summary> | |
| 153 | + /// 缺卡扣款 | |
| 154 | + /// </summary> | |
| 155 | + [SugarColumn(ColumnName = "F_MissingCard")] | |
| 156 | + public decimal MissingCard { get; set; } | |
| 157 | + | |
| 158 | + /// <summary> | |
| 159 | + /// 迟到扣款 | |
| 160 | + /// </summary> | |
| 161 | + [SugarColumn(ColumnName = "F_LateArrival")] | |
| 162 | + public decimal LateArrival { get; set; } | |
| 163 | + | |
| 164 | + /// <summary> | |
| 165 | + /// 请假扣款 | |
| 166 | + /// </summary> | |
| 167 | + [SugarColumn(ColumnName = "F_LeaveDeduction")] | |
| 168 | + public decimal LeaveDeduction { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 扣社保 | |
| 172 | + /// </summary> | |
| 173 | + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")] | |
| 174 | + public decimal SocialInsuranceDeduction { get; set; } | |
| 175 | + | |
| 176 | + /// <summary> | |
| 177 | + /// 扣除奖励 | |
| 178 | + /// </summary> | |
| 179 | + [SugarColumn(ColumnName = "F_RewardDeduction")] | |
| 180 | + public decimal RewardDeduction { get; set; } | |
| 181 | + | |
| 182 | + /// <summary> | |
| 183 | + /// 扣住宿费 | |
| 184 | + /// </summary> | |
| 185 | + [SugarColumn(ColumnName = "F_AccommodationDeduction")] | |
| 186 | + public decimal AccommodationDeduction { get; set; } | |
| 187 | + | |
| 188 | + /// <summary> | |
| 189 | + /// 扣学习期费用 | |
| 190 | + /// </summary> | |
| 191 | + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")] | |
| 192 | + public decimal StudyPeriodDeduction { get; set; } | |
| 193 | + | |
| 194 | + /// <summary> | |
| 195 | + /// 扣工作服费用 | |
| 196 | + /// </summary> | |
| 197 | + [SugarColumn(ColumnName = "F_WorkClothesDeduction")] | |
| 198 | + public decimal WorkClothesDeduction { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 扣款合计 | |
| 202 | + /// </summary> | |
| 203 | + [SugarColumn(ColumnName = "F_TotalDeduction")] | |
| 204 | + public decimal TotalDeduction { get; set; } | |
| 205 | + | |
| 206 | + /// <summary> | |
| 207 | + /// 发奖金 | |
| 208 | + /// </summary> | |
| 209 | + [SugarColumn(ColumnName = "F_Bonus")] | |
| 210 | + public decimal Bonus { get; set; } | |
| 211 | + | |
| 212 | + /// <summary> | |
| 213 | + /// 退手机押金 | |
| 214 | + /// </summary> | |
| 215 | + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")] | |
| 216 | + public decimal ReturnPhoneDeposit { get; set; } | |
| 217 | + | |
| 218 | + /// <summary> | |
| 219 | + /// 退住宿押金 | |
| 220 | + /// </summary> | |
| 221 | + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")] | |
| 222 | + public decimal ReturnAccommodationDeposit { 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_MonthlyPaymentStatus")] | |
| 234 | + public string MonthlyPaymentStatus { get; set; } | |
| 235 | + | |
| 236 | + /// <summary> | |
| 237 | + /// 支付金额 | |
| 238 | + /// </summary> | |
| 239 | + [SugarColumn(ColumnName = "F_PaidAmount")] | |
| 240 | + public decimal PaidAmount { get; set; } | |
| 241 | + | |
| 242 | + /// <summary> | |
| 243 | + /// 待支付金额 | |
| 244 | + /// </summary> | |
| 245 | + [SugarColumn(ColumnName = "F_PendingAmount")] | |
| 246 | + public decimal PendingAmount { get; set; } | |
| 247 | + | |
| 248 | + /// <summary> | |
| 249 | + /// 补发上月 | |
| 250 | + /// </summary> | |
| 251 | + [SugarColumn(ColumnName = "F_LastMonthSupplement")] | |
| 252 | + public decimal LastMonthSupplement { get; set; } | |
| 253 | + | |
| 254 | + /// <summary> | |
| 255 | + /// 当月支付总额 | |
| 256 | + /// </summary> | |
| 257 | + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")] | |
| 258 | + public decimal MonthlyTotalPayment { get; set; } | |
| 259 | + | |
| 260 | + /// <summary> | |
| 261 | + /// 是否锁定(0=未锁定,1=已锁定) | |
| 262 | + /// </summary> | |
| 263 | + [SugarColumn(ColumnName = "F_IsLocked")] | |
| 264 | + public int IsLocked { get; set; } | |
| 265 | + | |
| 266 | + /// <summary> | |
| 267 | + /// 创建时间 | |
| 268 | + /// </summary> | |
| 269 | + [SugarColumn(ColumnName = "F_CreateTime")] | |
| 270 | + public DateTime CreateTime { get; set; } | |
| 271 | + | |
| 272 | + /// <summary> | |
| 273 | + /// 更新时间 | |
| 274 | + /// </summary> | |
| 275 | + [SugarColumn(ColumnName = "F_UpdateTime")] | |
| 276 | + public DateTime UpdateTime { get; set; } | |
| 277 | + | |
| 278 | + /// <summary> | |
| 279 | + /// 创建人 | |
| 280 | + /// </summary> | |
| 281 | + [SugarColumn(ColumnName = "F_CreateUser")] | |
| 282 | + public string CreateUser { get; set; } | |
| 283 | + | |
| 284 | + /// <summary> | |
| 285 | + /// 更新人 | |
| 286 | + /// </summary> | |
| 287 | + [SugarColumn(ColumnName = "F_UpdateUser")] | |
| 288 | + public string UpdateUser { get; set; } | |
| 289 | + } | |
| 290 | +} | |
| 291 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_product/LqProductEntity.cs
| ... | ... | @@ -30,6 +30,12 @@ namespace NCC.Extend.Entitys.lq_product |
| 30 | 30 | public decimal Price { get; set; } |
| 31 | 31 | |
| 32 | 32 | /// <summary> |
| 33 | + /// 平均单价(加权平均成本,用于出库计价) | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_AveragePrice")] | |
| 36 | + public decimal AveragePrice { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 33 | 39 | /// 产品类别 |
| 34 | 40 | /// </summary> |
| 35 | 41 | [SugarColumn(ColumnName = "F_ProductCategory")] | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_tech_general_manager_salary_statistics/LqTechGeneralManagerSalaryStatisticsEntity.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using NCC.Common.Const; | |
| 3 | +using SqlSugar; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.lq_tech_general_manager_salary_statistics | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 科技部总经理工资统计表 | |
| 9 | + /// </summary> | |
| 10 | + [SugarTable("lq_tech_general_manager_salary_statistics")] | |
| 11 | + [Tenant(ClaimConst.TENANT_ID)] | |
| 12 | + public class LqTechGeneralManagerSalaryStatisticsEntity | |
| 13 | + { | |
| 14 | + /// <summary> | |
| 15 | + /// 主键ID | |
| 16 | + /// </summary> | |
| 17 | + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] | |
| 18 | + public string Id { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 统计月份(YYYYMM) | |
| 22 | + /// </summary> | |
| 23 | + [SugarColumn(ColumnName = "F_StatisticsMonth", Length = 6)] | |
| 24 | + public string StatisticsMonth { get; set; } | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 核算岗位(科技一部/科技二部等) | |
| 28 | + /// </summary> | |
| 29 | + [SugarColumn(ColumnName = "F_Position")] | |
| 30 | + public string Position { get; set; } | |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 员工姓名 | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_EmployeeName")] | |
| 36 | + public string EmployeeName { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 员工ID | |
| 40 | + /// </summary> | |
| 41 | + [SugarColumn(ColumnName = "F_EmployeeId")] | |
| 42 | + public string EmployeeId { get; set; } | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// 员工账号 | |
| 46 | + /// </summary> | |
| 47 | + [SugarColumn(ColumnName = "F_EmployeeAccount")] | |
| 48 | + public string EmployeeAccount { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 是否离职(0=在职,1=离职) | |
| 52 | + /// </summary> | |
| 53 | + [SugarColumn(ColumnName = "F_IsTerminated")] | |
| 54 | + public int IsTerminated { get; set; } | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 管理的门店明细(JSON格式) | |
| 58 | + /// </summary> | |
| 59 | + [SugarColumn(ColumnName = "F_StoreDetail", ColumnDataType = "TEXT")] | |
| 60 | + public string StoreDetail { get; set; } | |
| 61 | + | |
| 62 | + /// <summary> | |
| 63 | + /// 溯源金额(管理的所有门店的溯源金额总和,开单-退卡) | |
| 64 | + /// </summary> | |
| 65 | + [SugarColumn(ColumnName = "F_TraceabilityAmount")] | |
| 66 | + public decimal TraceabilityAmount { get; set; } | |
| 67 | + | |
| 68 | + /// <summary> | |
| 69 | + /// Cell金额(管理的所有门店的Cell金额总和,开单-退卡) | |
| 70 | + /// </summary> | |
| 71 | + [SugarColumn(ColumnName = "F_CellAmount")] | |
| 72 | + public decimal CellAmount { get; set; } | |
| 73 | + | |
| 74 | + /// <summary> | |
| 75 | + /// 底薪金额(固定4000元) | |
| 76 | + /// </summary> | |
| 77 | + [SugarColumn(ColumnName = "F_BaseSalary")] | |
| 78 | + public decimal BaseSalary { get; set; } | |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 溯源金额提成比例(分段计算,存储平均比例) | |
| 82 | + /// </summary> | |
| 83 | + [SugarColumn(ColumnName = "F_TraceabilityCommissionRate")] | |
| 84 | + public decimal? TraceabilityCommissionRate { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 溯源金额提成金额 | |
| 88 | + /// </summary> | |
| 89 | + [SugarColumn(ColumnName = "F_TraceabilityCommissionAmount")] | |
| 90 | + public decimal TraceabilityCommissionAmount { get; set; } | |
| 91 | + | |
| 92 | + /// <summary> | |
| 93 | + /// Cell金额提成比例(分段计算,存储平均比例) | |
| 94 | + /// </summary> | |
| 95 | + [SugarColumn(ColumnName = "F_CellCommissionRate")] | |
| 96 | + public decimal? CellCommissionRate { get; set; } | |
| 97 | + | |
| 98 | + /// <summary> | |
| 99 | + /// Cell金额提成金额 | |
| 100 | + /// </summary> | |
| 101 | + [SugarColumn(ColumnName = "F_CellCommissionAmount")] | |
| 102 | + public decimal CellCommissionAmount { get; set; } | |
| 103 | + | |
| 104 | + /// <summary> | |
| 105 | + /// 提成合计(溯源提成+Cell提成) | |
| 106 | + /// </summary> | |
| 107 | + [SugarColumn(ColumnName = "F_TotalCommission")] | |
| 108 | + public decimal TotalCommission { get; set; } | |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// 在店天数 | |
| 112 | + /// </summary> | |
| 113 | + [SugarColumn(ColumnName = "F_WorkingDays")] | |
| 114 | + public decimal WorkingDays { get; set; } | |
| 115 | + | |
| 116 | + /// <summary> | |
| 117 | + /// 请假天数 | |
| 118 | + /// </summary> | |
| 119 | + [SugarColumn(ColumnName = "F_LeaveDays")] | |
| 120 | + public decimal LeaveDays { get; set; } | |
| 121 | + | |
| 122 | + /// <summary> | |
| 123 | + /// 核算应发工资(底薪 + 提成合计) | |
| 124 | + /// </summary> | |
| 125 | + [SugarColumn(ColumnName = "F_CalculatedGrossSalary")] | |
| 126 | + public decimal CalculatedGrossSalary { get; set; } | |
| 127 | + | |
| 128 | + /// <summary> | |
| 129 | + /// 最终应发工资 | |
| 130 | + /// </summary> | |
| 131 | + [SugarColumn(ColumnName = "F_FinalGrossSalary")] | |
| 132 | + public decimal FinalGrossSalary { get; set; } | |
| 133 | + | |
| 134 | + /// <summary> | |
| 135 | + /// 当月培训补贴 | |
| 136 | + /// </summary> | |
| 137 | + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")] | |
| 138 | + public decimal MonthlyTrainingSubsidy { get; set; } | |
| 139 | + | |
| 140 | + /// <summary> | |
| 141 | + /// 当月交通补贴 | |
| 142 | + /// </summary> | |
| 143 | + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")] | |
| 144 | + public decimal MonthlyTransportSubsidy { get; set; } | |
| 145 | + | |
| 146 | + /// <summary> | |
| 147 | + /// 上月培训补贴 | |
| 148 | + /// </summary> | |
| 149 | + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")] | |
| 150 | + public decimal LastMonthTrainingSubsidy { get; set; } | |
| 151 | + | |
| 152 | + /// <summary> | |
| 153 | + /// 上月交通补贴 | |
| 154 | + /// </summary> | |
| 155 | + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")] | |
| 156 | + public decimal LastMonthTransportSubsidy { get; set; } | |
| 157 | + | |
| 158 | + /// <summary> | |
| 159 | + /// 补贴合计 | |
| 160 | + /// </summary> | |
| 161 | + [SugarColumn(ColumnName = "F_TotalSubsidy")] | |
| 162 | + public decimal TotalSubsidy { get; set; } | |
| 163 | + | |
| 164 | + /// <summary> | |
| 165 | + /// 缺卡扣款 | |
| 166 | + /// </summary> | |
| 167 | + [SugarColumn(ColumnName = "F_MissingCard")] | |
| 168 | + public decimal MissingCard { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 迟到扣款 | |
| 172 | + /// </summary> | |
| 173 | + [SugarColumn(ColumnName = "F_LateArrival")] | |
| 174 | + public decimal LateArrival { get; set; } | |
| 175 | + | |
| 176 | + /// <summary> | |
| 177 | + /// 请假扣款 | |
| 178 | + /// </summary> | |
| 179 | + [SugarColumn(ColumnName = "F_LeaveDeduction")] | |
| 180 | + public decimal LeaveDeduction { get; set; } | |
| 181 | + | |
| 182 | + /// <summary> | |
| 183 | + /// 扣社保 | |
| 184 | + /// </summary> | |
| 185 | + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")] | |
| 186 | + public decimal SocialInsuranceDeduction { get; set; } | |
| 187 | + | |
| 188 | + /// <summary> | |
| 189 | + /// 扣除奖励 | |
| 190 | + /// </summary> | |
| 191 | + [SugarColumn(ColumnName = "F_RewardDeduction")] | |
| 192 | + public decimal RewardDeduction { get; set; } | |
| 193 | + | |
| 194 | + /// <summary> | |
| 195 | + /// 扣住宿费 | |
| 196 | + /// </summary> | |
| 197 | + [SugarColumn(ColumnName = "F_AccommodationDeduction")] | |
| 198 | + public decimal AccommodationDeduction { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 扣学习期费用 | |
| 202 | + /// </summary> | |
| 203 | + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")] | |
| 204 | + public decimal StudyPeriodDeduction { get; set; } | |
| 205 | + | |
| 206 | + /// <summary> | |
| 207 | + /// 扣工作服费用 | |
| 208 | + /// </summary> | |
| 209 | + [SugarColumn(ColumnName = "F_WorkClothesDeduction")] | |
| 210 | + public decimal WorkClothesDeduction { get; set; } | |
| 211 | + | |
| 212 | + /// <summary> | |
| 213 | + /// 扣款合计 | |
| 214 | + /// </summary> | |
| 215 | + [SugarColumn(ColumnName = "F_TotalDeduction")] | |
| 216 | + public decimal TotalDeduction { get; set; } | |
| 217 | + | |
| 218 | + /// <summary> | |
| 219 | + /// 发奖金 | |
| 220 | + /// </summary> | |
| 221 | + [SugarColumn(ColumnName = "F_Bonus")] | |
| 222 | + public decimal Bonus { get; set; } | |
| 223 | + | |
| 224 | + /// <summary> | |
| 225 | + /// 退手机押金 | |
| 226 | + /// </summary> | |
| 227 | + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")] | |
| 228 | + public decimal ReturnPhoneDeposit { get; set; } | |
| 229 | + | |
| 230 | + /// <summary> | |
| 231 | + /// 退住宿押金 | |
| 232 | + /// </summary> | |
| 233 | + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")] | |
| 234 | + public decimal ReturnAccommodationDeposit { get; set; } | |
| 235 | + | |
| 236 | + /// <summary> | |
| 237 | + /// 实发工资 | |
| 238 | + /// </summary> | |
| 239 | + [SugarColumn(ColumnName = "F_ActualSalary")] | |
| 240 | + public decimal ActualSalary { get; set; } | |
| 241 | + | |
| 242 | + /// <summary> | |
| 243 | + /// 当月是否发放 | |
| 244 | + /// </summary> | |
| 245 | + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")] | |
| 246 | + public string MonthlyPaymentStatus { get; set; } | |
| 247 | + | |
| 248 | + /// <summary> | |
| 249 | + /// 支付金额 | |
| 250 | + /// </summary> | |
| 251 | + [SugarColumn(ColumnName = "F_PaidAmount")] | |
| 252 | + public decimal PaidAmount { get; set; } | |
| 253 | + | |
| 254 | + /// <summary> | |
| 255 | + /// 待支付金额 | |
| 256 | + /// </summary> | |
| 257 | + [SugarColumn(ColumnName = "F_PendingAmount")] | |
| 258 | + public decimal PendingAmount { get; set; } | |
| 259 | + | |
| 260 | + /// <summary> | |
| 261 | + /// 补发上月 | |
| 262 | + /// </summary> | |
| 263 | + [SugarColumn(ColumnName = "F_LastMonthSupplement")] | |
| 264 | + public decimal LastMonthSupplement { get; set; } | |
| 265 | + | |
| 266 | + /// <summary> | |
| 267 | + /// 当月支付总额 | |
| 268 | + /// </summary> | |
| 269 | + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")] | |
| 270 | + public decimal MonthlyTotalPayment { get; set; } | |
| 271 | + | |
| 272 | + /// <summary> | |
| 273 | + /// 是否锁定(0=未锁定,1=已锁定) | |
| 274 | + /// </summary> | |
| 275 | + [SugarColumn(ColumnName = "F_IsLocked")] | |
| 276 | + public int IsLocked { get; set; } | |
| 277 | + | |
| 278 | + /// <summary> | |
| 279 | + /// 创建时间 | |
| 280 | + /// </summary> | |
| 281 | + [SugarColumn(ColumnName = "F_CreateTime")] | |
| 282 | + public DateTime CreateTime { get; set; } | |
| 283 | + | |
| 284 | + /// <summary> | |
| 285 | + /// 更新时间 | |
| 286 | + /// </summary> | |
| 287 | + [SugarColumn(ColumnName = "F_UpdateTime")] | |
| 288 | + public DateTime UpdateTime { get; set; } | |
| 289 | + | |
| 290 | + /// <summary> | |
| 291 | + /// 创建人 | |
| 292 | + /// </summary> | |
| 293 | + [SugarColumn(ColumnName = "F_CreateUser")] | |
| 294 | + public string CreateUser { get; set; } | |
| 295 | + | |
| 296 | + /// <summary> | |
| 297 | + /// 更新人 | |
| 298 | + /// </summary> | |
| 299 | + [SugarColumn(ColumnName = "F_UpdateUser")] | |
| 300 | + public string UpdateUser { get; set; } | |
| 301 | + } | |
| 302 | +} | |
| 303 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs
| ... | ... | @@ -311,54 +311,69 @@ namespace NCC.Extend |
| 311 | 311 | salary.PerformanceCompletionRate = 0; |
| 312 | 312 | } |
| 313 | 313 | |
| 314 | - // 2.5 计算门店总提成(先计算门店级别的提成) | |
| 314 | + // 2.5 计算提成比例(固定比例,不随在店天数变化) | |
| 315 | 315 | // 判断岗位类型:店助 或 店助主任 |
| 316 | 316 | bool isDirector = salary.Position == "店助主任"; |
| 317 | 317 | |
| 318 | - decimal storeTotalCommission = 0; | |
| 319 | 318 | decimal commissionRate = 0; |
| 320 | 319 | |
| 321 | 320 | // 如果门店生命线未设置(<=0),则没有提成 |
| 322 | 321 | if (salary.StoreLifeline <= 0) |
| 323 | 322 | { |
| 324 | 323 | commissionRate = 0; |
| 325 | - storeTotalCommission = 0; | |
| 326 | 324 | } |
| 327 | 325 | else |
| 328 | 326 | { |
| 329 | - // 根据岗位类型计算提成 | |
| 327 | + // 计算业绩完成率 | |
| 328 | + decimal performanceRatio = salary.StoreTotalPerformance / salary.StoreLifeline; | |
| 329 | + | |
| 330 | + // 根据岗位类型确定提成比例 | |
| 330 | 331 | if (isDirector) |
| 331 | 332 | { |
| 332 | - // 店助主任:使用阶梯提成模式 | |
| 333 | + // 店助主任:使用固定比例(按规则文档,业绩≥100%时使用阶梯提成,但为保持比例固定,使用平均比例) | |
| 333 | 334 | // 业绩 < 70%:0% |
| 334 | - // 70% ≤ 业绩 < 100%:0.4%(阶梯) | |
| 335 | - // 超过生命线部分:1.6%(阶梯) | |
| 336 | - storeTotalCommission = CalculateDirectorCommission(salary.StoreTotalPerformance, salary.StoreLifeline); | |
| 337 | - // 店助主任的提成比例用于显示,计算平均比例 | |
| 338 | - if (salary.StoreTotalPerformance > 0) | |
| 335 | + // 70% ≤ 业绩 < 100%:0.4% | |
| 336 | + // 业绩 ≥ 100%:使用阶梯提成计算总提成,然后计算平均比例 | |
| 337 | + if (performanceRatio < 0.7m) | |
| 338 | + { | |
| 339 | + commissionRate = 0; | |
| 340 | + } | |
| 341 | + else if (performanceRatio < 1.0m) | |
| 339 | 342 | { |
| 340 | - commissionRate = storeTotalCommission / salary.StoreTotalPerformance; | |
| 343 | + commissionRate = 0.004m; // 0.4% | |
| 341 | 344 | } |
| 342 | 345 | else |
| 343 | 346 | { |
| 344 | - commissionRate = 0; | |
| 347 | + // 业绩 ≥ 100%:使用阶梯提成计算总提成,然后计算平均比例 | |
| 348 | + // ≤生命线部分:0.6%,>生命线部分:1% | |
| 349 | + decimal storeTotalCommission = CalculateDirectorCommission(salary.StoreTotalPerformance, salary.StoreLifeline); | |
| 350 | + if (salary.StoreTotalPerformance > 0) | |
| 351 | + { | |
| 352 | + commissionRate = storeTotalCommission / salary.StoreTotalPerformance; | |
| 353 | + } | |
| 354 | + else | |
| 355 | + { | |
| 356 | + commissionRate = 0; | |
| 357 | + } | |
| 345 | 358 | } |
| 346 | 359 | } |
| 347 | 360 | else |
| 348 | 361 | { |
| 349 | - // 店助:使用阶梯提成模式 | |
| 362 | + // 店助:使用固定比例 | |
| 350 | 363 | // 业绩 < 70%:0% |
| 351 | - // 70% ≤ 业绩 < 100%:0.4%(阶梯) | |
| 352 | - // 业绩 ≥ 100%:0.6%(阶梯) | |
| 353 | - storeTotalCommission = CalculateAssistantCommission(salary.StoreTotalPerformance, salary.StoreLifeline); | |
| 354 | - // 店助的提成比例用于显示,计算平均比例 | |
| 355 | - if (salary.StoreTotalPerformance > 0) | |
| 364 | + // 70% ≤ 业绩 < 100%:0.4% | |
| 365 | + // 业绩 ≥ 100%:0.6% | |
| 366 | + if (performanceRatio < 0.7m) | |
| 356 | 367 | { |
| 357 | - commissionRate = storeTotalCommission / salary.StoreTotalPerformance; | |
| 368 | + commissionRate = 0; | |
| 369 | + } | |
| 370 | + else if (performanceRatio < 1.0m) | |
| 371 | + { | |
| 372 | + commissionRate = 0.004m; // 0.4% | |
| 358 | 373 | } |
| 359 | 374 | else |
| 360 | 375 | { |
| 361 | - commissionRate = 0; | |
| 376 | + commissionRate = 0.006m; // 0.6% | |
| 362 | 377 | } |
| 363 | 378 | } |
| 364 | 379 | } |
| ... | ... | @@ -408,13 +423,7 @@ namespace NCC.Extend |
| 408 | 423 | } |
| 409 | 424 | } |
| 410 | 425 | |
| 411 | - // 2.8 计算底薪(店助和店助主任底薪规则相同,都根据门店分类确定) | |
| 412 | - salary.BaseSalary = CalculateBaseSalary(salary.StoreCategory.Value); | |
| 413 | - | |
| 414 | - // 2.9 固定奖励(手机管理费) | |
| 415 | - salary.PhoneManagementFee = 150m; | |
| 416 | - | |
| 417 | - // 2.10 考勤数据 | |
| 426 | + // 2.8 考勤数据 | |
| 418 | 427 | int workingDays = 0; |
| 419 | 428 | if (attendanceDict.ContainsKey(assistantUser.Id)) |
| 420 | 429 | { |
| ... | ... | @@ -429,13 +438,35 @@ namespace NCC.Extend |
| 429 | 438 | salary.LeaveDays = 0; |
| 430 | 439 | } |
| 431 | 440 | |
| 441 | + // 2.9 计算底薪(店助和店助主任底薪规则相同,都根据门店分类确定,按在店天数比例计算) | |
| 442 | + decimal baseSalaryFull = CalculateBaseSalary(salary.StoreCategory.Value); | |
| 443 | + if (daysInMonth > 0 && workingDays > 0) | |
| 444 | + { | |
| 445 | + salary.BaseSalary = baseSalaryFull / daysInMonth * workingDays; | |
| 446 | + } | |
| 447 | + else | |
| 448 | + { | |
| 449 | + salary.BaseSalary = 0; | |
| 450 | + } | |
| 451 | + | |
| 452 | + // 2.10 计算手机管理费(按在店天数比例计算) | |
| 453 | + decimal phoneManagementFeeFull = 150m; | |
| 454 | + if (daysInMonth > 0 && workingDays > 0) | |
| 455 | + { | |
| 456 | + salary.PhoneManagementFee = phoneManagementFeeFull / daysInMonth * workingDays; | |
| 457 | + } | |
| 458 | + else | |
| 459 | + { | |
| 460 | + salary.PhoneManagementFee = 0; | |
| 461 | + } | |
| 462 | + | |
| 432 | 463 | // 2.11 按在店天数比例计算店助的提成和奖励 |
| 433 | - // 逻辑:门店总提成 / 当月天数 * 在店天数 = 店助提成 | |
| 434 | - // 门店总奖励 / 当月天数 * 在店天数 = 店助奖励 | |
| 464 | + // 逻辑:提成金额 = 门店业绩 × 提成比例 / 当月天数 × 在店天数 | |
| 465 | + // 阶段奖励 = 门店总奖励 / 当月天数 × 在店天数 | |
| 435 | 466 | if (daysInMonth > 0 && workingDays > 0) |
| 436 | 467 | { |
| 437 | - // 按比例计算提成 | |
| 438 | - salary.CommissionAmount = storeTotalCommission / daysInMonth * workingDays; | |
| 468 | + // 按比例计算提成:门店业绩 × 提成比例 / 当月天数 × 在店天数 | |
| 469 | + salary.CommissionAmount = salary.StoreTotalPerformance * commissionRate / daysInMonth * workingDays; | |
| 439 | 470 | |
| 440 | 471 | // 按比例计算奖励 |
| 441 | 472 | salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays; |
| ... | ... | @@ -615,14 +646,17 @@ namespace NCC.Extend |
| 615 | 646 | // 门店业绩 ≥ 门店生命线 × 100% → 阶梯提成 |
| 616 | 647 | // 70%以下部分:0% |
| 617 | 648 | // 70%-100%部分:0.4% |
| 618 | - // 超过生命线部分:1.6% | |
| 649 | + // ≤生命线部分:0.6%,>生命线部分:1% | |
| 619 | 650 | decimal stage70 = storeLifeline * 0.7m; |
| 620 | 651 | decimal stage100 = storeLifeline; |
| 621 | 652 | decimal performance70To100 = stage100 - stage70; |
| 622 | 653 | decimal performanceAbove100 = storePerformance - stage100; |
| 623 | 654 | |
| 655 | + // 70%-100%部分:0.4% | |
| 624 | 656 | decimal commission70To100 = performance70To100 * 0.004m; |
| 625 | - decimal commissionAbove100 = performanceAbove100 * 0.016m; | |
| 657 | + // ≤生命线部分(0-70%):0%,70%-100%部分:0.4%,已计算 | |
| 658 | + // >生命线部分:1% | |
| 659 | + decimal commissionAbove100 = performanceAbove100 * 0.01m; // 1% | |
| 626 | 660 | |
| 627 | 661 | return commission70To100 + commissionAbove100; |
| 628 | 662 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
| ... | ... | @@ -866,7 +866,7 @@ namespace NCC.Extend |
| 866 | 866 | /// - ManagerId: 经理用户ID |
| 867 | 867 | /// - ManagerName: 经理姓名 |
| 868 | 868 | /// - StoreId/StoreName: 门店信息 |
| 869 | - /// - Target1/2/3: 目标业绩一/二/三阶段(来自门店总经理生命线设置表lq_md_general_manager_lifeline的F_Lifeline1/F_Lifeline2/F_Lifeline3字段,根据开始时间所在月份获取,如果未查询到则为0) | |
| 869 | + /// - Target1/2/3: 目标业绩一/二/三阶段(来自门店目标表lq_md_target的F_StoreTarget字段,根据开始时间所在月份获取,如果未查询到则为0,三个阶段都使用同一个门店目标值) | |
| 870 | 870 | /// - CompletedPerformance: 完成业绩(指定时间范围内的开单业绩总和) |
| 871 | 871 | /// - CompletionRate1/2/3: 完成率各阶段(百分比) |
| 872 | 872 | /// </remarks> |
| ... | ... | @@ -889,40 +889,42 @@ namespace NCC.Extend |
| 889 | 889 | var managerFilter = ""; |
| 890 | 890 | if (!string.IsNullOrWhiteSpace(input.ManagerId)) |
| 891 | 891 | { |
| 892 | - managerFilter = $"AND target.F_GeneralManagerId = '{input.ManagerId}'"; | |
| 892 | + managerFilter = $"AND gm.F_GeneralManagerId = '{input.ManagerId}'"; | |
| 893 | 893 | } |
| 894 | 894 | |
| 895 | 895 | // 构建经理类型过滤条件 |
| 896 | 896 | if (input.ManagerType.HasValue) |
| 897 | 897 | { |
| 898 | - managerFilter += $" AND target.F_ManagerType = {input.ManagerType.Value}"; | |
| 898 | + managerFilter += $" AND gm.F_ManagerType = {input.ManagerType.Value}"; | |
| 899 | 899 | } |
| 900 | 900 | |
| 901 | 901 | // SQL查询:获取经理在各门店的目标业绩和完成业绩 |
| 902 | + // 目标业绩从门店目标表(lq_md_target)的F_StoreTarget字段获取 | |
| 902 | 903 | var sql = $@" |
| 903 | 904 | SELECT |
| 904 | - target.F_GeneralManagerId as ManagerId, | |
| 905 | + gm.F_GeneralManagerId as ManagerId, | |
| 905 | 906 | u.F_RealName as ManagerName, |
| 906 | - target.F_StoreId as StoreId, | |
| 907 | + gm.F_StoreId as StoreId, | |
| 907 | 908 | store.dm as StoreName, |
| 908 | - target.F_Lifeline1 as Target1, | |
| 909 | - target.F_Lifeline2 as Target2, | |
| 910 | - target.F_Lifeline3 as Target3, | |
| 909 | + COALESCE(md_target.F_StoreTarget, 0) as Target1, | |
| 910 | + COALESCE(md_target.F_StoreTarget, 0) as Target2, | |
| 911 | + COALESCE(md_target.F_StoreTarget, 0) as Target3, | |
| 911 | 912 | -- 完成业绩 |
| 912 | 913 | COALESCE(( |
| 913 | 914 | SELECT SUM(billing.sfyj) |
| 914 | 915 | FROM lq_kd_kdjlb billing |
| 915 | - WHERE billing.djmd = target.F_StoreId | |
| 916 | + WHERE billing.djmd = gm.F_StoreId | |
| 916 | 917 | AND billing.F_IsEffective = 1 |
| 917 | - AND DATE(billing.kdrq) >= '{startDate:yyyy-MM-dd}' | |
| 918 | - AND DATE(billing.kdrq) <= '{endDate:yyyy-MM-dd}' | |
| 918 | + AND DATE(billing.kdrq) >= '{startDate.ToString("yyyy-MM-dd")}' | |
| 919 | + AND DATE(billing.kdrq) <= '{endDate.ToString("yyyy-MM-dd")}' | |
| 919 | 920 | ), 0) as CompletedPerformance |
| 920 | - FROM lq_md_general_manager_lifeline target | |
| 921 | - INNER JOIN BASE_USER u ON target.F_GeneralManagerId = u.F_Id | |
| 922 | - INNER JOIN lq_mdxx store ON target.F_StoreId = store.F_Id | |
| 923 | - WHERE target.F_Month = '{month}' | |
| 921 | + FROM lq_md_general_manager_lifeline gm | |
| 922 | + INNER JOIN BASE_USER u ON gm.F_GeneralManagerId = u.F_Id | |
| 923 | + INNER JOIN lq_mdxx store ON gm.F_StoreId = store.F_Id | |
| 924 | + LEFT JOIN lq_md_target md_target ON gm.F_StoreId = md_target.F_StoreId AND md_target.F_Month = '{month}' | |
| 925 | + WHERE gm.F_Month = '{month}' | |
| 924 | 926 | {managerFilter} |
| 925 | - ORDER BY target.F_GeneralManagerId, store.dm"; | |
| 927 | + ORDER BY gm.F_GeneralManagerId, store.dm"; | |
| 926 | 928 | |
| 927 | 929 | var result = await _db.Ado.SqlQueryAsync<dynamic>(sql); |
| 928 | 930 | |
| ... | ... | @@ -981,7 +983,7 @@ namespace NCC.Extend |
| 981 | 983 | /// 返回说明: |
| 982 | 984 | /// - ManagerId: 经理用户ID |
| 983 | 985 | /// - ManagerName: 经理姓名 |
| 984 | - /// - TotalTarget1/2/3: 总目标业绩一/二/三阶段(来自门店总经理生命线设置表lq_md_general_manager_lifeline的F_Lifeline1/F_Lifeline2/F_Lifeline3字段,根据开始时间所在月份获取并汇总) | |
| 986 | + /// - TotalTarget1/2/3: 总目标业绩一/二/三阶段(来自门店目标表lq_md_target的F_StoreTarget字段,根据开始时间所在月份获取并汇总所有管理门店的门店目标,三个阶段都使用同一个汇总值) | |
| 985 | 987 | /// - TotalCompletedPerformance: 总完成业绩(指定时间范围内的开单业绩总和) |
| 986 | 988 | /// - TotalCompletionRate1/2/3: 总完成率各阶段 |
| 987 | 989 | /// - StoreCount: 管理门店数量(根据门店总经理生命线设置表中归属该经理的门店数统计) |
| ... | ... | @@ -1005,39 +1007,41 @@ namespace NCC.Extend |
| 1005 | 1007 | var managerFilter = ""; |
| 1006 | 1008 | if (!string.IsNullOrWhiteSpace(input.ManagerId)) |
| 1007 | 1009 | { |
| 1008 | - managerFilter = $"AND target.F_GeneralManagerId = '{input.ManagerId}'"; | |
| 1010 | + managerFilter = $"AND gm.F_GeneralManagerId = '{input.ManagerId}'"; | |
| 1009 | 1011 | } |
| 1010 | 1012 | |
| 1011 | 1013 | // 构建经理类型过滤条件 |
| 1012 | 1014 | if (input.ManagerType.HasValue) |
| 1013 | 1015 | { |
| 1014 | - managerFilter += $" AND target.F_ManagerType = {input.ManagerType.Value}"; | |
| 1016 | + managerFilter += $" AND gm.F_ManagerType = {input.ManagerType.Value}"; | |
| 1015 | 1017 | } |
| 1016 | 1018 | |
| 1017 | 1019 | // SQL查询:获取经理汇总业绩(基于lq_md_general_manager_lifeline表中的经理和门店关系) |
| 1020 | + // 目标业绩从门店目标表(lq_md_target)的F_StoreTarget字段获取并汇总 | |
| 1018 | 1021 | var sql = $@" |
| 1019 | 1022 | SELECT |
| 1020 | - target.F_GeneralManagerId as ManagerId, | |
| 1023 | + gm.F_GeneralManagerId as ManagerId, | |
| 1021 | 1024 | u.F_RealName as ManagerName, |
| 1022 | - SUM(target.F_Lifeline1) as TotalTarget1, | |
| 1023 | - COALESCE(SUM(target.F_Lifeline2), 0) as TotalTarget2, | |
| 1024 | - COALESCE(SUM(target.F_Lifeline3), 0) as TotalTarget3, | |
| 1025 | - COUNT(DISTINCT target.F_StoreId) as StoreCount, | |
| 1025 | + COALESCE(SUM(md_target.F_StoreTarget), 0) as TotalTarget1, | |
| 1026 | + COALESCE(SUM(md_target.F_StoreTarget), 0) as TotalTarget2, | |
| 1027 | + COALESCE(SUM(md_target.F_StoreTarget), 0) as TotalTarget3, | |
| 1028 | + COUNT(DISTINCT gm.F_StoreId) as StoreCount, | |
| 1026 | 1029 | -- 总完成业绩(基于lq_md_general_manager_lifeline表中的门店关系计算) |
| 1027 | 1030 | SUM(COALESCE(( |
| 1028 | 1031 | SELECT SUM(billing.sfyj) |
| 1029 | 1032 | FROM lq_kd_kdjlb billing |
| 1030 | - WHERE billing.djmd = target.F_StoreId | |
| 1033 | + WHERE billing.djmd = gm.F_StoreId | |
| 1031 | 1034 | AND billing.F_IsEffective = 1 |
| 1032 | - AND DATE(billing.kdrq) >= '{startDate:yyyy-MM-dd}' | |
| 1033 | - AND DATE(billing.kdrq) <= '{endDate:yyyy-MM-dd}' | |
| 1035 | + AND DATE(billing.kdrq) >= '{startDate.ToString("yyyy-MM-dd")}' | |
| 1036 | + AND DATE(billing.kdrq) <= '{endDate.ToString("yyyy-MM-dd")}' | |
| 1034 | 1037 | ), 0)) as TotalCompletedPerformance |
| 1035 | - FROM lq_md_general_manager_lifeline target | |
| 1036 | - INNER JOIN BASE_USER u ON target.F_GeneralManagerId = u.F_Id | |
| 1037 | - INNER JOIN lq_mdxx store ON target.F_StoreId = store.F_Id | |
| 1038 | - WHERE target.F_Month = '{month}' | |
| 1038 | + FROM lq_md_general_manager_lifeline gm | |
| 1039 | + INNER JOIN BASE_USER u ON gm.F_GeneralManagerId = u.F_Id | |
| 1040 | + INNER JOIN lq_mdxx store ON gm.F_StoreId = store.F_Id | |
| 1041 | + LEFT JOIN lq_md_target md_target ON gm.F_StoreId = md_target.F_StoreId AND md_target.F_Month = '{month}' | |
| 1042 | + WHERE gm.F_Month = '{month}' | |
| 1039 | 1043 | {managerFilter} |
| 1040 | - GROUP BY target.F_GeneralManagerId, u.F_RealName | |
| 1044 | + GROUP BY gm.F_GeneralManagerId, u.F_RealName | |
| 1041 | 1045 | ORDER BY TotalCompletedPerformance DESC"; |
| 1042 | 1046 | |
| 1043 | 1047 | var result = await _db.Ado.SqlQueryAsync<dynamic>(sql); | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs
| ... | ... | @@ -5,13 +5,17 @@ using NCC.Common.Helper; |
| 5 | 5 | using NCC.Dependency; |
| 6 | 6 | using NCC.DynamicApiController; |
| 7 | 7 | using NCC.Extend.Entitys.Dto.LqDirectorSalary; |
| 8 | +using NCC.Extend.Entitys.Enum; | |
| 8 | 9 | using NCC.Extend.Entitys.lq_attendance_summary; |
| 10 | +using NCC.Extend.Entitys.lq_cooperation_cost; | |
| 9 | 11 | using NCC.Extend.Entitys.lq_director_salary_statistics; |
| 10 | 12 | using NCC.Extend.Entitys.lq_hytk_hytk; |
| 11 | 13 | using NCC.Extend.Entitys.lq_kd_kdjlb; |
| 14 | +using NCC.Extend.Entitys.lq_laundry_flow; | |
| 12 | 15 | using NCC.Extend.Entitys.lq_md_target; |
| 13 | 16 | using NCC.Extend.Entitys.lq_md_xdbhsj; |
| 14 | 17 | using NCC.Extend.Entitys.lq_mdxx; |
| 18 | +using NCC.Extend.Entitys.lq_store_expense; | |
| 15 | 19 | using NCC.Extend.Entitys.lq_xh_hyhk; |
| 16 | 20 | using NCC.Extend.Entitys.lq_xh_jksyj; |
| 17 | 21 | using NCC.System.Entitys.Permission; |
| ... | ... | @@ -140,6 +144,7 @@ namespace NCC.Extend |
| 140 | 144 | var startDate = new DateTime(year, month, 1); |
| 141 | 145 | var endDate = startDate.AddMonths(1).AddDays(-1); |
| 142 | 146 | var monthStr = $"{year}{month:D2}"; |
| 147 | + var daysInMonth = DateTime.DaysInMonth(year, month); // 当月天数 | |
| 143 | 148 | |
| 144 | 149 | // 1. 获取基础数据 |
| 145 | 150 | |
| ... | ... | @@ -244,6 +249,68 @@ namespace NCC.Extend |
| 244 | 249 | .ToListAsync(); |
| 245 | 250 | var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x); |
| 246 | 251 | |
| 252 | + // 1.9 产品物料统计(仓库领用金额,注意11月特殊规则) | |
| 253 | + var queryMonth = monthStr; | |
| 254 | + if (month == 11) | |
| 255 | + { | |
| 256 | + // 11月工资算10月数据 | |
| 257 | + queryMonth = $"{year}10"; | |
| 258 | + } | |
| 259 | + var productMaterialSql = $@" | |
| 260 | + SELECT | |
| 261 | + F_StoreId as StoreId, | |
| 262 | + COALESCE(SUM(F_TotalAmount), 0) as MaterialAmount | |
| 263 | + FROM lq_inventory_usage | |
| 264 | + WHERE F_IsEffective = 1 | |
| 265 | + AND DATE_FORMAT(F_UsageTime, '%Y%m') = @queryMonth | |
| 266 | + GROUP BY F_StoreId"; | |
| 267 | + | |
| 268 | + var productMaterialData = await _db.Ado.SqlQueryAsync<dynamic>(productMaterialSql, new { queryMonth }); | |
| 269 | + var productMaterialDict = productMaterialData | |
| 270 | + .Where(x => x.StoreId != null) | |
| 271 | + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.MaterialAmount ?? 0)); | |
| 272 | + | |
| 273 | + // 1.10 合作项目成本统计 | |
| 274 | + var cooperationCostList = await _db.Queryable<LqCooperationCostEntity>() | |
| 275 | + .Where(x => x.Year == year && x.Month == monthStr && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 276 | + .Select(x => new { x.StoreId, x.TotalAmount }) | |
| 277 | + .ToListAsync(); | |
| 278 | + var cooperationCostDict = cooperationCostList | |
| 279 | + .Where(x => !string.IsNullOrEmpty(x.StoreId)) | |
| 280 | + .GroupBy(x => x.StoreId) | |
| 281 | + .ToDictionary(g => g.Key, g => g.Sum(x => x.TotalAmount)); | |
| 282 | + | |
| 283 | + // 1.11 店内支出统计 | |
| 284 | + var storeExpenseSql = $@" | |
| 285 | + SELECT | |
| 286 | + F_StoreId as StoreId, | |
| 287 | + COALESCE(SUM(F_Amount), 0) as ExpenseAmount | |
| 288 | + FROM lq_store_expense | |
| 289 | + WHERE F_IsEffective = 1 | |
| 290 | + AND DATE_FORMAT(F_ExpenseDate, '%Y%m') = @monthStr | |
| 291 | + GROUP BY F_StoreId"; | |
| 292 | + | |
| 293 | + var storeExpenseData = await _db.Ado.SqlQueryAsync<dynamic>(storeExpenseSql, new { monthStr }); | |
| 294 | + var storeExpenseDict = storeExpenseData | |
| 295 | + .Where(x => x.StoreId != null) | |
| 296 | + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ExpenseAmount ?? 0)); | |
| 297 | + | |
| 298 | + // 1.12 洗毛巾费用统计(只统计送出的记录,F_FlowType = 0) | |
| 299 | + var laundryCostSql = $@" | |
| 300 | + SELECT | |
| 301 | + F_StoreId as StoreId, | |
| 302 | + COALESCE(SUM(F_TotalPrice), 0) as LaundryAmount | |
| 303 | + FROM lq_laundry_flow | |
| 304 | + WHERE F_IsEffective = 1 | |
| 305 | + AND F_FlowType = 0 | |
| 306 | + AND DATE_FORMAT(F_CreateTime, '%Y%m') = @monthStr | |
| 307 | + GROUP BY F_StoreId"; | |
| 308 | + | |
| 309 | + var laundryCostData = await _db.Ado.SqlQueryAsync<dynamic>(laundryCostSql, new { monthStr }); | |
| 310 | + var laundryCostDict = laundryCostData | |
| 311 | + .Where(x => x.StoreId != null) | |
| 312 | + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.LaundryAmount ?? 0)); | |
| 313 | + | |
| 247 | 314 | // 2. 计算每个主任的工资 |
| 248 | 315 | var directorSalaryList = new List<LqDirectorSalaryStatisticsEntity>(); |
| 249 | 316 | |
| ... | ... | @@ -326,31 +393,52 @@ namespace NCC.Extend |
| 326 | 393 | throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标消耗未设置,无法计算主任工资(老店需要考核消耗)"); |
| 327 | 394 | } |
| 328 | 395 | |
| 329 | - // 2.4 计算门店业绩 | |
| 396 | + // 2.4 计算销售业绩(开单业绩-退款业绩) | |
| 330 | 397 | decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; |
| 331 | 398 | decimal refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0; |
| 332 | 399 | salary.StoreBillingPerformance = billing; |
| 333 | 400 | salary.StoreRefundPerformance = refund; |
| 334 | - salary.StoreTotalPerformance = billing - refund; | |
| 401 | + salary.SalesPerformance = billing - refund; | |
| 402 | + | |
| 403 | + // 2.5 统计各项成本 | |
| 404 | + // 产品物料(注意11月特殊规则已在查询时处理) | |
| 405 | + salary.ProductMaterial = productMaterialDict.ContainsKey(storeId) ? productMaterialDict[storeId] : 0; | |
| 406 | + | |
| 407 | + // 合作项目成本 | |
| 408 | + salary.CooperationCost = cooperationCostDict.ContainsKey(storeId) ? cooperationCostDict[storeId] : 0; | |
| 409 | + | |
| 410 | + // 店内支出 | |
| 411 | + salary.StoreExpense = storeExpenseDict.ContainsKey(storeId) ? storeExpenseDict[storeId] : 0; | |
| 412 | + | |
| 413 | + // 洗毛巾费用 | |
| 414 | + salary.LaundryCost = laundryCostDict.ContainsKey(storeId) ? laundryCostDict[storeId] : 0; | |
| 415 | + | |
| 416 | + // 2.6 计算毛利 | |
| 417 | + // 毛利 = 销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾 | |
| 418 | + salary.GrossProfit = salary.SalesPerformance - salary.ProductMaterial - salary.CooperationCost - salary.StoreExpense - salary.LaundryCost; | |
| 419 | + | |
| 420 | + // 2.7 将毛利赋值给StoreTotalPerformance(用于提成计算) | |
| 421 | + salary.StoreTotalPerformance = salary.GrossProfit; | |
| 335 | 422 | |
| 336 | - // 计算业绩完成率 | |
| 423 | + // 2.8 计算业绩完成率(基于毛利与生命线比较) | |
| 337 | 424 | if (salary.StoreLifeline > 0) |
| 338 | 425 | { |
| 339 | - salary.PerformanceCompletionRate = salary.StoreTotalPerformance / salary.StoreLifeline; | |
| 426 | + salary.PerformanceCompletionRate = salary.GrossProfit / salary.StoreLifeline; | |
| 340 | 427 | } |
| 341 | 428 | else |
| 342 | 429 | { |
| 343 | 430 | salary.PerformanceCompletionRate = 0; |
| 344 | 431 | } |
| 345 | 432 | |
| 346 | - // 2.5 统计门店消耗金额 | |
| 433 | + // 2.9 统计门店消耗金额 | |
| 347 | 434 | salary.StoreConsume = storeConsumeDict.ContainsKey(storeId) ? storeConsumeDict[storeId] : 0; |
| 348 | 435 | |
| 349 | - // 2.6 统计进店消耗人数 | |
| 436 | + // 2.10 统计进店消耗人数 | |
| 350 | 437 | salary.HeadCount = headcountDict.ContainsKey(storeId) ? headcountDict[storeId] : 0; |
| 351 | 438 | |
| 352 | - // 2.7 计算考核指标(业绩、人头、消耗是否达标) | |
| 353 | - bool performanceReached = salary.StoreTotalPerformance >= salary.StoreLifeline; | |
| 439 | + // 2.11 计算考核指标(业绩、人头、消耗是否达标) | |
| 440 | + // 业绩达标判断基于毛利 | |
| 441 | + bool performanceReached = salary.GrossProfit >= salary.StoreLifeline; | |
| 354 | 442 | bool headCountReached = salary.HeadCount >= salary.TargetHeadCount; |
| 355 | 443 | bool consumeReached = salary.StoreConsume >= salary.TargetConsume; |
| 356 | 444 | |
| ... | ... | @@ -368,18 +456,13 @@ namespace NCC.Extend |
| 368 | 456 | salary.UnreachedIndicatorCount = unreachedCount; |
| 369 | 457 | salary.AssessmentDeduction = unreachedCount * 500m; |
| 370 | 458 | |
| 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 考勤数据 | |
| 459 | + // 2.12 考勤数据 | |
| 460 | + int workingDays = 0; | |
| 379 | 461 | if (attendanceDict.ContainsKey(directorUser.Id)) |
| 380 | 462 | { |
| 381 | 463 | var attendance = attendanceDict[directorUser.Id]; |
| 382 | - salary.WorkingDays = (int)attendance.WorkDays; | |
| 464 | + workingDays = (int)attendance.WorkDays; | |
| 465 | + salary.WorkingDays = workingDays; | |
| 383 | 466 | salary.LeaveDays = (int)attendance.LeaveDays; |
| 384 | 467 | } |
| 385 | 468 | else |
| ... | ... | @@ -388,10 +471,44 @@ namespace NCC.Extend |
| 388 | 471 | salary.LeaveDays = 0; |
| 389 | 472 | } |
| 390 | 473 | |
| 391 | - // 2.11 计算应发工资 | |
| 474 | + // 2.13 计算底薪(按在店天数比例计算) | |
| 475 | + decimal baseSalaryFull = 3500m; // 固定底薪3500元 | |
| 476 | + decimal actualBaseSalaryFull = baseSalaryFull - salary.AssessmentDeduction; // 实际底薪 = 底薪 - 考核扣款 | |
| 477 | + | |
| 478 | + if (daysInMonth > 0 && workingDays > 0) | |
| 479 | + { | |
| 480 | + salary.BaseSalary = baseSalaryFull / daysInMonth * workingDays; | |
| 481 | + salary.ActualBaseSalary = actualBaseSalaryFull / daysInMonth * workingDays; | |
| 482 | + } | |
| 483 | + else | |
| 484 | + { | |
| 485 | + salary.BaseSalary = 0; | |
| 486 | + salary.ActualBaseSalary = 0; | |
| 487 | + } | |
| 488 | + | |
| 489 | + // 2.14 计算阶梯提成(先计算门店总提成,基于毛利) | |
| 490 | + CalculateCommission(salary, isNewStore); | |
| 491 | + | |
| 492 | + // 2.15 按在店天数比例计算提成金额 | |
| 493 | + if (daysInMonth > 0 && workingDays > 0) | |
| 494 | + { | |
| 495 | + // 提成金额按在店天数比例计算 | |
| 496 | + salary.CommissionAmountBelowLifeline = salary.CommissionAmountBelowLifeline / daysInMonth * workingDays; | |
| 497 | + salary.CommissionAmountAboveLifeline = salary.CommissionAmountAboveLifeline / daysInMonth * workingDays; | |
| 498 | + salary.TotalCommissionAmount = salary.CommissionAmountBelowLifeline + salary.CommissionAmountAboveLifeline; | |
| 499 | + } | |
| 500 | + else | |
| 501 | + { | |
| 502 | + // 如果当月天数为0或在店天数为0,则提成为0 | |
| 503 | + salary.CommissionAmountBelowLifeline = 0; | |
| 504 | + salary.CommissionAmountAboveLifeline = 0; | |
| 505 | + salary.TotalCommissionAmount = 0; | |
| 506 | + } | |
| 507 | + | |
| 508 | + // 2.16 计算应发工资 | |
| 392 | 509 | salary.GrossSalary = salary.ActualBaseSalary + salary.TotalCommissionAmount; |
| 393 | 510 | |
| 394 | - // 2.12 初始化扣款、补贴、奖金字段(默认值为0) | |
| 511 | + // 2.17 初始化扣款、补贴、奖金字段(默认值为0) | |
| 395 | 512 | salary.MissingCard = 0; |
| 396 | 513 | salary.LateArrival = 0; |
| 397 | 514 | salary.LeaveDeduction = 0; |
| ... | ... | @@ -412,10 +529,10 @@ namespace NCC.Extend |
| 412 | 529 | salary.ReturnPhoneDeposit = 0; |
| 413 | 530 | salary.ReturnAccommodationDeposit = 0; |
| 414 | 531 | |
| 415 | - // 2.13 计算实发工资 | |
| 532 | + // 2.18 计算实发工资 | |
| 416 | 533 | salary.ActualSalary = salary.GrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus; |
| 417 | 534 | |
| 418 | - // 2.14 初始化支付相关字段 | |
| 535 | + // 2.19 初始化支付相关字段 | |
| 419 | 536 | salary.PaidAmount = 0; |
| 420 | 537 | salary.PendingAmount = salary.ActualSalary; |
| 421 | 538 | salary.LastMonthSupplement = 0; |
| ... | ... | @@ -437,7 +554,7 @@ namespace NCC.Extend |
| 437 | 554 | } |
| 438 | 555 | |
| 439 | 556 | /// <summary> |
| 440 | - /// 计算阶梯提成 | |
| 557 | + /// 计算阶梯提成(基于毛利) | |
| 441 | 558 | /// </summary> |
| 442 | 559 | /// <param name="salary">工资实体</param> |
| 443 | 560 | /// <param name="isNewStore">是否新店</param> |
| ... | ... | @@ -454,7 +571,8 @@ namespace NCC.Extend |
| 454 | 571 | return; |
| 455 | 572 | } |
| 456 | 573 | |
| 457 | - decimal performance = salary.StoreTotalPerformance; | |
| 574 | + // 提成计算基于毛利(StoreTotalPerformance存储的是毛利) | |
| 575 | + decimal performance = salary.StoreTotalPerformance; // 这里已经是毛利了 | |
| 458 | 576 | decimal lifeline = salary.StoreLifeline; |
| 459 | 577 | |
| 460 | 578 | // 确定提成比例(根据新店/老店和门店分类) | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
| ... | ... | @@ -183,8 +183,23 @@ namespace NCC.Extend |
| 183 | 183 | CreateTime = DateTime.Now |
| 184 | 184 | }; |
| 185 | 185 | |
| 186 | - var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync(); | |
| 187 | - if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); | |
| 186 | + _db.Ado.BeginTran(); | |
| 187 | + try | |
| 188 | + { | |
| 189 | + var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync(); | |
| 190 | + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); | |
| 191 | + | |
| 192 | + // 计算并更新产品的平均单价(加权平均成本法) | |
| 193 | + // 普通入库和采购入库都需要更新平均单价 | |
| 194 | + await UpdateProductAveragePriceAsync(input.ProductId, stockInType, purchaseUnitPrice, input.Quantity); | |
| 195 | + | |
| 196 | + _db.Ado.CommitTran(); | |
| 197 | + } | |
| 198 | + catch | |
| 199 | + { | |
| 200 | + _db.Ado.RollbackTran(); | |
| 201 | + throw; | |
| 202 | + } | |
| 188 | 203 | } |
| 189 | 204 | catch (Exception ex) |
| 190 | 205 | { |
| ... | ... | @@ -192,6 +207,79 @@ namespace NCC.Extend |
| 192 | 207 | throw NCCException.Oh($"创建失败:{ex.Message}"); |
| 193 | 208 | } |
| 194 | 209 | } |
| 210 | + | |
| 211 | + /// <summary> | |
| 212 | + /// 更新产品的平均单价(加权平均成本法) | |
| 213 | + /// </summary> | |
| 214 | + /// <param name="productId">产品ID</param> | |
| 215 | + /// <param name="stockInType">入库类型(1:普通入库 2:采购入库)</param> | |
| 216 | + /// <param name="incomingUnitPrice">入库单价(采购入库时使用采购单价,普通入库时使用产品价格)</param> | |
| 217 | + /// <param name="incomingQuantity">入库数量</param> | |
| 218 | + private async Task UpdateProductAveragePriceAsync(string productId, int stockInType, decimal? incomingUnitPrice, int incomingQuantity) | |
| 219 | + { | |
| 220 | + // 获取产品信息 | |
| 221 | + var product = await _db.Queryable<LqProductEntity>() | |
| 222 | + .Where(x => x.Id == productId) | |
| 223 | + .FirstAsync(); | |
| 224 | + | |
| 225 | + if (product == null) | |
| 226 | + { | |
| 227 | + return; | |
| 228 | + } | |
| 229 | + | |
| 230 | + // 计算当前可用库存数量(总库存数量 - 已领取数量) | |
| 231 | + // 注意:由于入库记录已经插入,需要减去本次入库的数量,得到入库前的库存数量 | |
| 232 | + var totalInventoryQuantity = await _db.Queryable<LqInventoryEntity>() | |
| 233 | + .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 234 | + .SumAsync(x => (int?)x.Quantity) ?? 0; | |
| 235 | + | |
| 236 | + var totalUsageQuantity = await _db.Queryable<LqInventoryUsageEntity>() | |
| 237 | + .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 238 | + .SumAsync(x => (int?)x.UsageQuantity) ?? 0; | |
| 239 | + | |
| 240 | + // 计算入库前的可用库存数量(需要减去本次入库的数量) | |
| 241 | + var currentAvailableQuantity = (totalInventoryQuantity - incomingQuantity) - totalUsageQuantity; | |
| 242 | + | |
| 243 | + // 确定入库单价 | |
| 244 | + decimal incomingPrice = 0; | |
| 245 | + if (stockInType == 2 && incomingUnitPrice.HasValue && incomingUnitPrice.Value > 0) | |
| 246 | + { | |
| 247 | + // 采购入库:使用采购单价 | |
| 248 | + incomingPrice = incomingUnitPrice.Value; | |
| 249 | + } | |
| 250 | + else | |
| 251 | + { | |
| 252 | + // 普通入库:使用产品价格 | |
| 253 | + incomingPrice = product.Price; | |
| 254 | + } | |
| 255 | + | |
| 256 | + // 计算新的平均单价 | |
| 257 | + decimal newAveragePrice = 0; | |
| 258 | + if (currentAvailableQuantity <= 0) | |
| 259 | + { | |
| 260 | + // 如果当前没有可用库存,新平均单价就是入库单价 | |
| 261 | + newAveragePrice = incomingPrice; | |
| 262 | + } | |
| 263 | + else | |
| 264 | + { | |
| 265 | + // 加权平均成本法计算 | |
| 266 | + // 当前库存总金额 = 当前平均单价 × 当前可用库存数量 | |
| 267 | + var currentTotalAmount = product.AveragePrice > 0 ? product.AveragePrice * currentAvailableQuantity : product.Price * currentAvailableQuantity; | |
| 268 | + | |
| 269 | + // 入库金额 = 入库单价 × 入库数量 | |
| 270 | + var incomingAmount = incomingPrice * incomingQuantity; | |
| 271 | + | |
| 272 | + // 新平均单价 = (当前库存总金额 + 入库金额) / (当前可用库存数量 + 入库数量) | |
| 273 | + newAveragePrice = (currentTotalAmount + incomingAmount) / (currentAvailableQuantity + incomingQuantity); | |
| 274 | + } | |
| 275 | + | |
| 276 | + // 更新产品的平均单价 | |
| 277 | + product.AveragePrice = newAveragePrice; | |
| 278 | + product.UpdateTime = DateTime.Now; | |
| 279 | + await _db.Updateable(product) | |
| 280 | + .UpdateColumns(x => new { x.AveragePrice, x.UpdateTime }) | |
| 281 | + .ExecuteCommandAsync(); | |
| 282 | + } | |
| 195 | 283 | #endregion |
| 196 | 284 | |
| 197 | 285 | #region 更新库存信息 |
| ... | ... | @@ -307,8 +395,24 @@ namespace NCC.Extend |
| 307 | 395 | existingInventory.UpdateUser = _userManager.UserId; |
| 308 | 396 | existingInventory.UpdateTime = DateTime.Now; |
| 309 | 397 | |
| 310 | - var isOk = await _db.Updateable(existingInventory).ExecuteCommandAsync(); | |
| 311 | - if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); | |
| 398 | + _db.Ado.BeginTran(); | |
| 399 | + try | |
| 400 | + { | |
| 401 | + var isOk = await _db.Updateable(existingInventory).ExecuteCommandAsync(); | |
| 402 | + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); | |
| 403 | + | |
| 404 | + // 如果数量或单价发生变化,需要重新计算平均单价 | |
| 405 | + // 注意:更新库存时,如果数量或单价变化,需要重新计算整个产品的平均单价 | |
| 406 | + // 因为更新操作可能改变数量或单价,所以需要基于所有有效库存重新计算 | |
| 407 | + await RecalculateProductAveragePriceAsync(input.ProductId); | |
| 408 | + | |
| 409 | + _db.Ado.CommitTran(); | |
| 410 | + } | |
| 411 | + catch | |
| 412 | + { | |
| 413 | + _db.Ado.RollbackTran(); | |
| 414 | + throw; | |
| 415 | + } | |
| 312 | 416 | } |
| 313 | 417 | catch (Exception ex) |
| 314 | 418 | { |
| ... | ... | @@ -316,6 +420,90 @@ namespace NCC.Extend |
| 316 | 420 | throw NCCException.Oh($"更新失败:{ex.Message}"); |
| 317 | 421 | } |
| 318 | 422 | } |
| 423 | + | |
| 424 | + /// <summary> | |
| 425 | + /// 重新计算产品的平均单价(基于所有有效库存) | |
| 426 | + /// </summary> | |
| 427 | + /// <param name="productId">产品ID</param> | |
| 428 | + private async Task RecalculateProductAveragePriceAsync(string productId) | |
| 429 | + { | |
| 430 | + // 获取产品信息 | |
| 431 | + var product = await _db.Queryable<LqProductEntity>() | |
| 432 | + .Where(x => x.Id == productId) | |
| 433 | + .FirstAsync(); | |
| 434 | + | |
| 435 | + if (product == null) | |
| 436 | + { | |
| 437 | + return; | |
| 438 | + } | |
| 439 | + | |
| 440 | + // 计算当前可用库存数量(总库存数量 - 已领取数量) | |
| 441 | + var totalInventoryQuantity = await _db.Queryable<LqInventoryEntity>() | |
| 442 | + .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 443 | + .SumAsync(x => (int?)x.Quantity) ?? 0; | |
| 444 | + | |
| 445 | + var totalUsageQuantity = await _db.Queryable<LqInventoryUsageEntity>() | |
| 446 | + .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 447 | + .SumAsync(x => (int?)x.UsageQuantity) ?? 0; | |
| 448 | + | |
| 449 | + var currentAvailableQuantity = totalInventoryQuantity - totalUsageQuantity; | |
| 450 | + | |
| 451 | + if (currentAvailableQuantity <= 0) | |
| 452 | + { | |
| 453 | + // 如果没有可用库存,平均单价保持为产品价格 | |
| 454 | + product.AveragePrice = product.Price; | |
| 455 | + product.UpdateTime = DateTime.Now; | |
| 456 | + await _db.Updateable(product) | |
| 457 | + .UpdateColumns(x => new { x.AveragePrice, x.UpdateTime }) | |
| 458 | + .ExecuteCommandAsync(); | |
| 459 | + return; | |
| 460 | + } | |
| 461 | + | |
| 462 | + // 获取所有有效库存记录 | |
| 463 | + var inventoryList = await _db.Queryable<LqInventoryEntity>() | |
| 464 | + .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Quantity > 0) | |
| 465 | + .Select(x => new { x.Quantity, x.FinalAmount, x.PurchaseUnitPrice }) | |
| 466 | + .ToListAsync(); | |
| 467 | + | |
| 468 | + decimal totalAmount = 0; | |
| 469 | + int totalQuantity = 0; | |
| 470 | + | |
| 471 | + foreach (var inventory in inventoryList) | |
| 472 | + { | |
| 473 | + decimal unitPrice = 0; | |
| 474 | + | |
| 475 | + // 优先使用 F_FinalAmount(产品最终金额)计算单价 | |
| 476 | + if (inventory.FinalAmount.HasValue && inventory.FinalAmount.Value > 0) | |
| 477 | + { | |
| 478 | + unitPrice = inventory.FinalAmount.Value / inventory.Quantity; | |
| 479 | + } | |
| 480 | + // 其次使用 F_PurchaseUnitPrice(采购单价) | |
| 481 | + else if (inventory.PurchaseUnitPrice.HasValue && inventory.PurchaseUnitPrice.Value > 0) | |
| 482 | + { | |
| 483 | + unitPrice = inventory.PurchaseUnitPrice.Value; | |
| 484 | + } | |
| 485 | + // 如果都没有,使用产品价格 | |
| 486 | + else | |
| 487 | + { | |
| 488 | + unitPrice = product.Price; | |
| 489 | + } | |
| 490 | + | |
| 491 | + totalAmount += unitPrice * inventory.Quantity; | |
| 492 | + totalQuantity += inventory.Quantity; | |
| 493 | + } | |
| 494 | + | |
| 495 | + // 计算新的平均单价(基于总库存,但只考虑可用库存部分) | |
| 496 | + // 这里需要按比例计算:可用库存的平均单价 = 总库存的平均单价 | |
| 497 | + // 因为已经领取的库存已经按照当时的平均单价计价了,所以这里直接计算总库存的平均单价即可 | |
| 498 | + decimal newAveragePrice = totalQuantity > 0 ? totalAmount / totalQuantity : product.Price; | |
| 499 | + | |
| 500 | + // 更新产品的平均单价 | |
| 501 | + product.AveragePrice = newAveragePrice; | |
| 502 | + product.UpdateTime = DateTime.Now; | |
| 503 | + await _db.Updateable(product) | |
| 504 | + .UpdateColumns(x => new { x.AveragePrice, x.UpdateTime }) | |
| 505 | + .ExecuteCommandAsync(); | |
| 506 | + } | |
| 319 | 507 | #endregion |
| 320 | 508 | |
| 321 | 509 | #region 获取库存列表 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
| ... | ... | @@ -161,29 +161,11 @@ namespace NCC.Extend |
| 161 | 161 | throw NCCException.Oh("产品不存在"); |
| 162 | 162 | } |
| 163 | 163 | |
| 164 | - // 计算该产品的总库存数量(所有有效库存的总和) | |
| 165 | - var totalInventory = await _db.Queryable<LqInventoryEntity>() | |
| 166 | - .Where(x => x.ProductId == input.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 167 | - .SumAsync(x => (int?)x.Quantity) ?? 0; | |
| 168 | - | |
| 169 | - // 计算该产品的已使用数量 | |
| 170 | - var totalUsage = await _db.Queryable<LqInventoryUsageEntity>() | |
| 171 | - .Where(x => x.ProductId == input.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 172 | - .SumAsync(x => (int?)x.UsageQuantity) ?? 0; | |
| 173 | - | |
| 174 | - // 计算可用库存 | |
| 175 | - var availableInventory = totalInventory - totalUsage; | |
| 176 | - | |
| 177 | - // 检查库存数量是否足够 | |
| 178 | - if (availableInventory < input.UsageQuantity) | |
| 179 | - { | |
| 180 | - throw NCCException.Oh($"库存不足,当前可用库存:{availableInventory},需要数量:{input.UsageQuantity}"); | |
| 181 | - } | |
| 182 | - | |
| 183 | 164 | _db.Ado.BeginTran(); |
| 184 | 165 | |
| 185 | - // 根据商品现存的库存计算平均价格(加权平均) | |
| 186 | - var unitPrice = await CalculateAveragePriceFromInventoryAsync(input.ProductId, product.Price); | |
| 166 | + // 使用产品的平均单价(加权平均成本法) | |
| 167 | + // 如果产品平均单价为0或未设置,则使用产品价格 | |
| 168 | + var unitPrice = product.AveragePrice > 0 ? product.AveragePrice : product.Price; | |
| 187 | 169 | var totalAmount = unitPrice * input.UsageQuantity; |
| 188 | 170 | |
| 189 | 171 | // 创建使用记录 |
| ... | ... | @@ -281,70 +263,16 @@ namespace NCC.Extend |
| 281 | 263 | var batchId = string.IsNullOrWhiteSpace(input.BatchId) ? YitIdHelper.NextId().ToString() : input.BatchId; |
| 282 | 264 | var successIds = new List<string>(); |
| 283 | 265 | |
| 284 | - // 按产品ID分组,批量验证库存(在事务外先检查,避免不必要的回滚) | |
| 285 | - var productGroups = input.UsageItems.Select((item, index) => new { Item = item, Index = index }).GroupBy(x => x.Item.ProductId).ToList(); | |
| 286 | - | |
| 287 | - // 获取所有需要检查的产品ID | |
| 288 | - var productIds = productGroups.Select(x => x.Key).Distinct().ToList(); | |
| 289 | - | |
| 290 | - // 批量查询所有产品的库存信息(总库存) | |
| 291 | - var inventoryList = await _db.Queryable<LqInventoryEntity>().Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()).GroupBy(x => x.ProductId).Select(x => new { ProductId = x.ProductId, TotalInventory = SqlFunc.AggregateSum(x.Quantity) }).ToListAsync(); | |
| 292 | - var inventoryMap = inventoryList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalInventory)); | |
| 293 | - | |
| 294 | - // 批量查询所有产品的已使用数量 | |
| 295 | - var usageList = await _db.Queryable<LqInventoryUsageEntity>().Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()).GroupBy(x => x.ProductId).Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum(x.UsageQuantity) }).ToListAsync(); | |
| 296 | - var usageMap = usageList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalUsage)); | |
| 297 | - | |
| 298 | - // 批量查询所有产品的名称和价格 | |
| 299 | - var productDict = await _db.Queryable<LqProductEntity>() | |
| 300 | - .Where(x => productIds.Contains(x.Id)) | |
| 301 | - .Select(x => new { x.Id, x.ProductName, x.Price }) | |
| 302 | - .ToListAsync(); | |
| 303 | - var productNameMap = productDict.ToDictionary(x => x.Id, x => x.ProductName ?? "未知产品"); | |
| 304 | - var productPriceMap = productDict.ToDictionary(x => x.Id, x => x.Price); | |
| 305 | - | |
| 306 | - // 批量计算每个产品的平均价格(根据库存计算加权平均) | |
| 307 | - var productAveragePriceMap = new Dictionary<string, decimal>(); | |
| 308 | - foreach (var productId in productIds) | |
| 309 | - { | |
| 310 | - var defaultPrice = productPriceMap.GetValueOrDefault(productId, 0); | |
| 311 | - var averagePrice = await CalculateAveragePriceFromInventoryAsync(productId, defaultPrice); | |
| 312 | - productAveragePriceMap[productId] = averagePrice; | |
| 313 | - } | |
| 314 | - | |
| 315 | - // 检查每个产品的库存 | |
| 316 | - foreach (var productGroup in productGroups) | |
| 317 | - { | |
| 318 | - var productId = productGroup.Key; | |
| 319 | - var totalRequired = productGroup.Sum(x => x.Item.UsageQuantity); | |
| 320 | - | |
| 321 | - // 从字典中获取库存信息 | |
| 322 | - var totalInventory = inventoryMap.GetValueOrDefault(productId, 0); | |
| 323 | - var totalUsage = usageMap.GetValueOrDefault(productId, 0); | |
| 324 | - var availableInventory = totalInventory - totalUsage; | |
| 325 | - | |
| 326 | - // 检查库存是否足够,如果不足则直接抛出异常 | |
| 327 | - if (availableInventory < totalRequired) | |
| 328 | - { | |
| 329 | - var productName = productNameMap.GetValueOrDefault(productId, "未知产品"); | |
| 330 | - throw NCCException.Oh($"产品【{productName}】(ID: {productId}) 库存不足,当前可用库存:{availableInventory},需要数量:{totalRequired}"); | |
| 331 | - } | |
| 332 | - } | |
| 333 | - | |
| 334 | 266 | _db.Ado.BeginTran(); |
| 335 | 267 | |
| 336 | 268 | try |
| 337 | 269 | { |
| 338 | - // 创建使用记录(自动计算单价和合计金额) | |
| 270 | + // 创建使用记录(不计算价格,价格在确认领用时计算) | |
| 339 | 271 | var entitiesToInsert = new List<LqInventoryUsageEntity>(); |
| 340 | 272 | for (int i = 0; i < input.UsageItems.Count; i++) |
| 341 | 273 | { |
| 342 | 274 | var item = input.UsageItems[i]; |
| 343 | 275 | |
| 344 | - // 从平均价格字典获取单价(根据库存计算的加权平均价格) | |
| 345 | - var unitPrice = productAveragePriceMap.GetValueOrDefault(item.ProductId, 0); | |
| 346 | - var totalAmount = unitPrice * item.UsageQuantity; | |
| 347 | - | |
| 348 | 276 | var usageEntity = new LqInventoryUsageEntity |
| 349 | 277 | { |
| 350 | 278 | Id = YitIdHelper.NextId().ToString(), |
| ... | ... | @@ -352,8 +280,8 @@ namespace NCC.Extend |
| 352 | 280 | StoreId = item.StoreId, |
| 353 | 281 | UsageTime = item.UsageTime, |
| 354 | 282 | UsageQuantity = item.UsageQuantity, |
| 355 | - UnitPrice = unitPrice, | |
| 356 | - TotalAmount = totalAmount, | |
| 283 | + UnitPrice = 0, // 创建时不计算价格,在确认领用时计算 | |
| 284 | + TotalAmount = 0, // 创建时不计算价格,在确认领用时计算 | |
| 357 | 285 | RelatedConsumeId = item.RelatedConsumeId, |
| 358 | 286 | UsageBatchId = batchId, |
| 359 | 287 | CreateUser = _userManager.UserId, |
| ... | ... | @@ -375,8 +303,8 @@ namespace NCC.Extend |
| 375 | 303 | } |
| 376 | 304 | } |
| 377 | 305 | |
| 378 | - // 计算这一单所有商品的总价 | |
| 379 | - var batchTotalAmount = entitiesToInsert.Sum(x => x.TotalAmount); | |
| 306 | + // 申请总金额初始为0,在确认领用时计算 | |
| 307 | + var batchTotalAmount = 0m; | |
| 380 | 308 | |
| 381 | 309 | // 创建申请记录并提交审批(审批人ID为必填) |
| 382 | 310 | if (string.IsNullOrWhiteSpace(input.ApproverId)) |
| ... | ... | @@ -1247,15 +1175,115 @@ namespace NCC.Extend |
| 1247 | 1175 | // 获取当前用户信息 |
| 1248 | 1176 | var userInfo = await _userManager.GetUserInfo(); |
| 1249 | 1177 | |
| 1250 | - // 更新申请记录 | |
| 1251 | - application.IsReceived = 1; | |
| 1252 | - application.ReceiveTime = DateTime.Now; | |
| 1253 | - application.ReceiveUser = userInfo.userId; | |
| 1254 | - application.UpdateUser = userInfo.userId; | |
| 1255 | - application.UpdateTime = DateTime.Now; | |
| 1178 | + _db.Ado.BeginTran(); | |
| 1256 | 1179 | |
| 1257 | - var isOk = await _db.Updateable(application).ExecuteCommandAsync(); | |
| 1258 | - if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1003); | |
| 1180 | + try | |
| 1181 | + { | |
| 1182 | + // 1. 获取该批次的所有使用记录 | |
| 1183 | + var usageRecords = await _db.Queryable<LqInventoryUsageEntity>() | |
| 1184 | + .Where(x => x.UsageBatchId == application.UsageBatchId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1185 | + .ToListAsync(); | |
| 1186 | + | |
| 1187 | + if (!usageRecords.Any()) | |
| 1188 | + { | |
| 1189 | + throw NCCException.Oh("该申请没有使用记录,无法确认领用"); | |
| 1190 | + } | |
| 1191 | + | |
| 1192 | + // 2. 批量获取所有产品信息(包含平均单价和价格) | |
| 1193 | + var productIds = usageRecords.Select(x => x.ProductId).Distinct().ToList(); | |
| 1194 | + var productDict = await _db.Queryable<LqProductEntity>() | |
| 1195 | + .Where(x => productIds.Contains(x.Id)) | |
| 1196 | + .Select(x => new { x.Id, x.ProductName, x.Price, x.AveragePrice }) | |
| 1197 | + .ToListAsync(); | |
| 1198 | + var productMap = productDict.ToDictionary(x => x.Id, x => new { x.ProductName, x.Price, x.AveragePrice }); | |
| 1199 | + | |
| 1200 | + // 3. 在确认领用前,验证所有产品的库存是否充足 | |
| 1201 | + // 先获取所有已确认领用的批次ID(用于计算已使用数量) | |
| 1202 | + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>() | |
| 1203 | + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() | |
| 1204 | + && x.IsReceived == 1 | |
| 1205 | + && x.UsageBatchId != application.UsageBatchId) // 排除当前批次 | |
| 1206 | + .Select(x => x.UsageBatchId) | |
| 1207 | + .ToListAsync(); | |
| 1208 | + | |
| 1209 | + foreach (var usageRecord in usageRecords) | |
| 1210 | + { | |
| 1211 | + if (!productMap.ContainsKey(usageRecord.ProductId)) | |
| 1212 | + { | |
| 1213 | + throw NCCException.Oh($"产品不存在:{usageRecord.ProductId}"); | |
| 1214 | + } | |
| 1215 | + | |
| 1216 | + var productInfo = productMap[usageRecord.ProductId]; | |
| 1217 | + | |
| 1218 | + // 计算该产品的总库存数量(所有有效库存的总和) | |
| 1219 | + var totalInventory = await _db.Queryable<LqInventoryEntity>() | |
| 1220 | + .Where(x => x.ProductId == usageRecord.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1221 | + .SumAsync(x => (int?)x.Quantity) ?? 0; | |
| 1222 | + | |
| 1223 | + // 计算该产品的已使用数量(只计算已确认领用的批次) | |
| 1224 | + // 注意:只有已确认领用的批次才算真正领取,未确认领用的批次不应该计入已使用数量 | |
| 1225 | + var totalUsage = 0; | |
| 1226 | + if (receivedBatchIds != null && receivedBatchIds.Any()) | |
| 1227 | + { | |
| 1228 | + totalUsage = await _db.Queryable<LqInventoryUsageEntity>() | |
| 1229 | + .Where(x => x.ProductId == usageRecord.ProductId | |
| 1230 | + && x.IsEffective == StatusEnum.有效.GetHashCode() | |
| 1231 | + && receivedBatchIds.Contains(x.UsageBatchId)) // 只计算已确认领用的批次 | |
| 1232 | + .SumAsync(x => (int?)x.UsageQuantity) ?? 0; | |
| 1233 | + } | |
| 1234 | + | |
| 1235 | + // 计算可用库存 | |
| 1236 | + var availableInventory = totalInventory - totalUsage; | |
| 1237 | + | |
| 1238 | + // 检查库存数量是否足够 | |
| 1239 | + if (availableInventory < usageRecord.UsageQuantity) | |
| 1240 | + { | |
| 1241 | + throw NCCException.Oh($"产品 {productInfo.ProductName ?? usageRecord.ProductId} 库存不足,当前可用库存:{availableInventory},需要数量:{usageRecord.UsageQuantity}"); | |
| 1242 | + } | |
| 1243 | + } | |
| 1244 | + | |
| 1245 | + // 4. 为每个使用记录计算价格(使用产品的平均单价) | |
| 1246 | + decimal totalAmount = 0; | |
| 1247 | + foreach (var usageRecord in usageRecords) | |
| 1248 | + { | |
| 1249 | + // 获取产品信息(包含平均单价) | |
| 1250 | + var productInfo = productMap[usageRecord.ProductId]; | |
| 1251 | + | |
| 1252 | + // 使用产品的平均单价(加权平均成本法) | |
| 1253 | + // 如果平均单价为0或未设置,则使用产品价格 | |
| 1254 | + var unitPrice = productInfo.AveragePrice > 0 ? productInfo.AveragePrice : productInfo.Price; | |
| 1255 | + var recordTotalAmount = unitPrice * usageRecord.UsageQuantity; | |
| 1256 | + | |
| 1257 | + // 更新使用记录的单价和总金额 | |
| 1258 | + usageRecord.UnitPrice = unitPrice; | |
| 1259 | + usageRecord.TotalAmount = recordTotalAmount; | |
| 1260 | + usageRecord.UpdateUser = userInfo.userId; | |
| 1261 | + usageRecord.UpdateTime = DateTime.Now; | |
| 1262 | + | |
| 1263 | + totalAmount += recordTotalAmount; | |
| 1264 | + } | |
| 1265 | + | |
| 1266 | + // 5. 批量更新使用记录 | |
| 1267 | + await _db.Updateable(usageRecords).ExecuteCommandAsync(); | |
| 1268 | + | |
| 1269 | + // 6. 更新申请记录(包括总金额、领取信息) | |
| 1270 | + application.TotalAmount = totalAmount; | |
| 1271 | + application.IsReceived = 1; | |
| 1272 | + application.ReceiveTime = DateTime.Now; | |
| 1273 | + application.ReceiveUser = userInfo.userId; | |
| 1274 | + application.UpdateUser = userInfo.userId; | |
| 1275 | + application.UpdateTime = DateTime.Now; | |
| 1276 | + | |
| 1277 | + var isOk = await _db.Updateable(application).ExecuteCommandAsync(); | |
| 1278 | + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1003); | |
| 1279 | + | |
| 1280 | + _db.Ado.CommitTran(); | |
| 1281 | + } | |
| 1282 | + catch | |
| 1283 | + { | |
| 1284 | + _db.Ado.RollbackTran(); | |
| 1285 | + throw; | |
| 1286 | + } | |
| 1259 | 1287 | } |
| 1260 | 1288 | catch (Exception ex) |
| 1261 | 1289 | { |
| ... | ... | @@ -1460,59 +1488,6 @@ namespace NCC.Extend |
| 1460 | 1488 | .Where(x => x.UsageBatchId == application.UsageBatchId && x.IsEffective == StatusEnum.有效.GetHashCode()) |
| 1461 | 1489 | .ToListAsync(); |
| 1462 | 1490 | |
| 1463 | - // 按产品ID分组,批量验证库存(在事务外先检查,避免不必要的回滚) | |
| 1464 | - var productGroups = input.UsageItems.Select((item, index) => new { Item = item, Index = index }).GroupBy(x => x.Item.ProductId).ToList(); | |
| 1465 | - | |
| 1466 | - // 获取所有需要检查的产品ID | |
| 1467 | - var productIds = productGroups.Select(x => x.Key).Distinct().ToList(); | |
| 1468 | - | |
| 1469 | - // 批量查询所有产品的库存信息(总库存) | |
| 1470 | - var inventoryList = await _db.Queryable<LqInventoryEntity>() | |
| 1471 | - .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1472 | - .GroupBy(x => x.ProductId) | |
| 1473 | - .Select(x => new { ProductId = x.ProductId, TotalInventory = SqlFunc.AggregateSum(x.Quantity) }) | |
| 1474 | - .ToListAsync(); | |
| 1475 | - var inventoryMap = inventoryList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalInventory)); | |
| 1476 | - | |
| 1477 | - // 批量查询所有产品的已使用数量(排除当前批次的使用记录,因为我们要替换它们) | |
| 1478 | - var oldUsageRecordIds = oldUsageRecords.Select(x => x.Id).ToList(); | |
| 1479 | - var allUsageList = await _db.Queryable<LqInventoryUsageEntity>() | |
| 1480 | - .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1481 | - .WhereIF(oldUsageRecordIds.Any(), x => !oldUsageRecordIds.Contains(x.Id)) // 排除当前批次的使用记录 | |
| 1482 | - .GroupBy(x => x.ProductId) | |
| 1483 | - .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum(x.UsageQuantity) }) | |
| 1484 | - .ToListAsync(); | |
| 1485 | - var usageMap = allUsageList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalUsage)); | |
| 1486 | - | |
| 1487 | - // 批量查询所有产品的名称和价格 | |
| 1488 | - var productDict = await _db.Queryable<LqProductEntity>() | |
| 1489 | - .Where(x => productIds.Contains(x.Id)) | |
| 1490 | - .Select(x => new { x.Id, x.ProductName, x.Price }) | |
| 1491 | - .ToListAsync(); | |
| 1492 | - var productInfoMap = productDict.ToDictionary(k => k.Id, v => new { v.ProductName, v.Price }); | |
| 1493 | - | |
| 1494 | - // 验证每个产品的库存是否充足 | |
| 1495 | - foreach (var group in productGroups) | |
| 1496 | - { | |
| 1497 | - var productId = group.Key; | |
| 1498 | - var totalNewQuantity = group.Sum(x => x.Item.UsageQuantity); | |
| 1499 | - | |
| 1500 | - if (!productInfoMap.ContainsKey(productId)) | |
| 1501 | - { | |
| 1502 | - throw NCCException.Oh($"产品ID {productId} 不存在"); | |
| 1503 | - } | |
| 1504 | - | |
| 1505 | - var totalInventory = inventoryMap.GetValueOrDefault(productId, 0); | |
| 1506 | - var totalUsage = usageMap.GetValueOrDefault(productId, 0); | |
| 1507 | - var availableInventory = totalInventory - totalUsage; | |
| 1508 | - | |
| 1509 | - if (availableInventory < totalNewQuantity) | |
| 1510 | - { | |
| 1511 | - var productName = productInfoMap[productId].ProductName; | |
| 1512 | - throw NCCException.Oh($"产品 {productName} 库存不足,当前可用库存:{availableInventory},需要数量:{totalNewQuantity}"); | |
| 1513 | - } | |
| 1514 | - } | |
| 1515 | - | |
| 1516 | 1491 | _db.Ado.BeginTran(); |
| 1517 | 1492 | |
| 1518 | 1493 | try |
| ... | ... | @@ -1529,23 +1504,10 @@ namespace NCC.Extend |
| 1529 | 1504 | await _db.Updateable(oldUsageRecords).ExecuteCommandAsync(); |
| 1530 | 1505 | } |
| 1531 | 1506 | |
| 1532 | - // 2. 批量计算新使用记录的平均价格(根据库存计算的加权平均价格) | |
| 1533 | - var productAveragePriceMap = new Dictionary<string, decimal>(); | |
| 1534 | - foreach (var productId in productIds) | |
| 1535 | - { | |
| 1536 | - var product = productInfoMap[productId]; | |
| 1537 | - var averagePrice = await CalculateAveragePriceFromInventoryAsync(productId, product.Price); | |
| 1538 | - productAveragePriceMap[productId] = averagePrice; | |
| 1539 | - } | |
| 1540 | - | |
| 1541 | - // 3. 创建新的使用记录 | |
| 1507 | + // 2. 创建新的使用记录(不计算价格,价格在确认领用时计算) | |
| 1542 | 1508 | var entitiesToInsert = new List<LqInventoryUsageEntity>(); |
| 1543 | 1509 | foreach (var item in input.UsageItems) |
| 1544 | 1510 | { |
| 1545 | - // 从平均价格字典获取单价(根据库存计算的加权平均价格) | |
| 1546 | - var unitPrice = productAveragePriceMap.GetValueOrDefault(item.ProductId, 0); | |
| 1547 | - var totalAmount = unitPrice * item.UsageQuantity; | |
| 1548 | - | |
| 1549 | 1511 | var usageEntity = new LqInventoryUsageEntity |
| 1550 | 1512 | { |
| 1551 | 1513 | Id = YitIdHelper.NextId().ToString(), |
| ... | ... | @@ -1553,8 +1515,8 @@ namespace NCC.Extend |
| 1553 | 1515 | StoreId = item.StoreId, |
| 1554 | 1516 | UsageTime = item.UsageTime, |
| 1555 | 1517 | UsageQuantity = item.UsageQuantity, |
| 1556 | - UnitPrice = unitPrice, | |
| 1557 | - TotalAmount = totalAmount, | |
| 1518 | + UnitPrice = 0, // 修改时不计算价格,在确认领用时计算 | |
| 1519 | + TotalAmount = 0, // 修改时不计算价格,在确认领用时计算 | |
| 1558 | 1520 | RelatedConsumeId = item.RelatedConsumeId, |
| 1559 | 1521 | UsageBatchId = application.UsageBatchId, // 使用相同的批次ID |
| 1560 | 1522 | CreateUser = _userManager.UserId, |
| ... | ... | @@ -1571,9 +1533,8 @@ namespace NCC.Extend |
| 1571 | 1533 | await _db.Insertable(entitiesToInsert).ExecuteCommandAsync(); |
| 1572 | 1534 | } |
| 1573 | 1535 | |
| 1574 | - // 4. 重新计算申请总金额 | |
| 1575 | - var newTotalAmount = entitiesToInsert.Sum(x => x.TotalAmount); | |
| 1576 | - application.TotalAmount = newTotalAmount; | |
| 1536 | + // 3. 申请总金额重置为0,在确认领用时计算 | |
| 1537 | + application.TotalAmount = 0; | |
| 1577 | 1538 | application.UpdateUser = _userManager.UserId; |
| 1578 | 1539 | application.UpdateTime = DateTime.Now; |
| 1579 | 1540 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.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.LqMajorProjectDirectorSalary; | |
| 8 | +using NCC.Extend.Entitys.lq_attendance_summary; | |
| 9 | +using NCC.Extend.Entitys.lq_hytk_hytk; | |
| 10 | +using NCC.Extend.Entitys.lq_kd_kdjlb; | |
| 11 | +using NCC.Extend.Entitys.lq_md_target; | |
| 12 | +using NCC.Extend.Entitys.lq_mdxx; | |
| 13 | +using NCC.Extend.Entitys.lq_major_project_director_salary_statistics; | |
| 14 | +using NCC.System.Entitys.Permission; | |
| 15 | +using SqlSugar; | |
| 16 | +using System; | |
| 17 | +using System.Collections.Generic; | |
| 18 | +using System.Linq; | |
| 19 | +using System.Threading.Tasks; | |
| 20 | +using Yitter.IdGenerator; | |
| 21 | +using Newtonsoft.Json; | |
| 22 | + | |
| 23 | +namespace NCC.Extend | |
| 24 | +{ | |
| 25 | + /// <summary> | |
| 26 | + /// 大项目主管薪酬服务 | |
| 27 | + /// </summary> | |
| 28 | + [ApiDescriptionSettings(Tag = "大项目主管薪酬服务", Name = "LqMajorProjectDirectorSalary", Order = 306)] | |
| 29 | + [Route("api/Extend/[controller]")] | |
| 30 | + public class LqMajorProjectDirectorSalaryService : IDynamicApiController, ITransient | |
| 31 | + { | |
| 32 | + private readonly ISqlSugarClient _db; | |
| 33 | + | |
| 34 | + /// <summary> | |
| 35 | + /// 初始化一个<see cref="LqMajorProjectDirectorSalaryService"/>类型的新实例 | |
| 36 | + /// </summary> | |
| 37 | + public LqMajorProjectDirectorSalaryService(ISqlSugarClient db) | |
| 38 | + { | |
| 39 | + _db = db; | |
| 40 | + } | |
| 41 | + | |
| 42 | + /// <summary> | |
| 43 | + /// 获取大项目主管工资列表 | |
| 44 | + /// </summary> | |
| 45 | + /// <param name="input">查询参数</param> | |
| 46 | + /// <returns>大项目主管工资分页列表</returns> | |
| 47 | + [HttpGet("major-project-director")] | |
| 48 | + public async Task<dynamic> GetMajorProjectDirectorSalaryList([FromQuery] MajorProjectDirectorSalaryInput input) | |
| 49 | + { | |
| 50 | + var monthStr = $"{input.Year}{input.Month:D2}"; | |
| 51 | + | |
| 52 | + // 1. 检查当月是否已生成工资数据 | |
| 53 | + var exists = await _db.Queryable<LqMajorProjectDirectorSalaryStatisticsEntity>() | |
| 54 | + .AnyAsync(x => x.StatisticsMonth == monthStr); | |
| 55 | + | |
| 56 | + // 2. 如果没有数据,则进行计算 | |
| 57 | + if (!exists) | |
| 58 | + { | |
| 59 | + await CalculateMajorProjectDirectorSalary(input.Year, input.Month); | |
| 60 | + } | |
| 61 | + | |
| 62 | + // 3. 查询数据 | |
| 63 | + var query = _db.Queryable<LqMajorProjectDirectorSalaryStatisticsEntity>() | |
| 64 | + .Where(x => x.StatisticsMonth == monthStr); | |
| 65 | + | |
| 66 | + if (!string.IsNullOrEmpty(input.Position)) | |
| 67 | + { | |
| 68 | + query = query.Where(x => x.Position == input.Position); | |
| 69 | + } | |
| 70 | + | |
| 71 | + if (!string.IsNullOrEmpty(input.Keyword)) | |
| 72 | + { | |
| 73 | + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeAccount.Contains(input.Keyword)); | |
| 74 | + } | |
| 75 | + | |
| 76 | + var list = await query.Select(x => new MajorProjectDirectorSalaryOutput | |
| 77 | + { | |
| 78 | + Id = x.Id, | |
| 79 | + StatisticsMonth = x.StatisticsMonth, | |
| 80 | + Position = x.Position, | |
| 81 | + EmployeeName = x.EmployeeName, | |
| 82 | + EmployeeId = x.EmployeeId, | |
| 83 | + EmployeeAccount = x.EmployeeAccount, | |
| 84 | + IsTerminated = x.IsTerminated, | |
| 85 | + StoreDetail = x.StoreDetail, | |
| 86 | + TotalPerformance = x.TotalPerformance, | |
| 87 | + BillingAmount = x.BillingAmount, | |
| 88 | + RefundAmount = x.RefundAmount, | |
| 89 | + BaseSalary = x.BaseSalary, | |
| 90 | + CommissionRate = x.CommissionRate, | |
| 91 | + CommissionAmount = x.CommissionAmount, | |
| 92 | + WorkingDays = x.WorkingDays, | |
| 93 | + LeaveDays = x.LeaveDays, | |
| 94 | + CalculatedGrossSalary = x.CalculatedGrossSalary, | |
| 95 | + FinalGrossSalary = x.FinalGrossSalary, | |
| 96 | + MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy, | |
| 97 | + MonthlyTransportSubsidy = x.MonthlyTransportSubsidy, | |
| 98 | + LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy, | |
| 99 | + LastMonthTransportSubsidy = x.LastMonthTransportSubsidy, | |
| 100 | + TotalSubsidy = x.TotalSubsidy, | |
| 101 | + MissingCard = x.MissingCard, | |
| 102 | + LateArrival = x.LateArrival, | |
| 103 | + LeaveDeduction = x.LeaveDeduction, | |
| 104 | + SocialInsuranceDeduction = x.SocialInsuranceDeduction, | |
| 105 | + RewardDeduction = x.RewardDeduction, | |
| 106 | + AccommodationDeduction = x.AccommodationDeduction, | |
| 107 | + StudyPeriodDeduction = x.StudyPeriodDeduction, | |
| 108 | + WorkClothesDeduction = x.WorkClothesDeduction, | |
| 109 | + TotalDeduction = x.TotalDeduction, | |
| 110 | + Bonus = x.Bonus, | |
| 111 | + ReturnPhoneDeposit = x.ReturnPhoneDeposit, | |
| 112 | + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit, | |
| 113 | + ActualSalary = x.ActualSalary, | |
| 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 | + }) | |
| 122 | + .ToPagedListAsync(input.currentPage, input.pageSize); | |
| 123 | + | |
| 124 | + return PageResult<MajorProjectDirectorSalaryOutput>.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/major-project-director")] | |
| 134 | + public async Task CalculateMajorProjectDirectorSalary(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 | + | |
| 140 | + // 1. 获取基础数据 | |
| 141 | + | |
| 142 | + // 1.1 先从BASE_ORGANIZE表查找组织名称包含"大项目一部"或"大项目二部"的组织 | |
| 143 | + var majorProjectOrganizeList = await _db.Queryable<OrganizeEntity>() | |
| 144 | + .Where(x => x.FullName != null && (x.FullName.Contains("大项目一部") || x.FullName.Contains("大项目二部")) | |
| 145 | + && x.DeleteMark == null && x.EnabledMark == 1) | |
| 146 | + .Select(x => new { x.Id, x.FullName }) | |
| 147 | + .ToListAsync(); | |
| 148 | + | |
| 149 | + if (!majorProjectOrganizeList.Any()) | |
| 150 | + { | |
| 151 | + // 如果没有找到大项目部组织,直接返回 | |
| 152 | + return; | |
| 153 | + } | |
| 154 | + | |
| 155 | + var majorProjectOrganizeIds = majorProjectOrganizeList.Select(x => x.Id).ToList(); | |
| 156 | + var majorProjectOrganizeDict = majorProjectOrganizeList.ToDictionary(x => x.Id, x => x.FullName); | |
| 157 | + | |
| 158 | + // 1.2 从BASE_USER表查询岗位为"主管"且组织ID在大项目一部或大项目二部的员工 | |
| 159 | + var majorProjectDirectorUserList = await _db.Queryable<UserEntity>() | |
| 160 | + .Where(x => x.Gw == "主管" | |
| 161 | + && majorProjectOrganizeIds.Contains(x.OrganizeId) | |
| 162 | + && x.DeleteMark == null && x.EnabledMark == 1) | |
| 163 | + .Select(x => new { x.Id, x.RealName, x.Account, x.Gw, x.OrganizeId, x.IsOnJob }) | |
| 164 | + .ToListAsync(); | |
| 165 | + | |
| 166 | + if (!majorProjectDirectorUserList.Any()) | |
| 167 | + { | |
| 168 | + // 如果没有大项目主管员工,直接返回 | |
| 169 | + return; | |
| 170 | + } | |
| 171 | + | |
| 172 | + // 1.3 从lq_md_target表获取管理的门店(按月份和大项目部组织ID) | |
| 173 | + var targetList = await _db.Queryable<LqMdTargetEntity>() | |
| 174 | + .Where(x => x.Month == monthStr | |
| 175 | + && majorProjectOrganizeIds.Contains(x.MajorProjectDepartment)) | |
| 176 | + .Select(x => new { x.StoreId, x.MajorProjectDepartment }) | |
| 177 | + .ToListAsync(); | |
| 178 | + | |
| 179 | + // 1.4 按大项目主管ID分组,获取每个大项目主管管理的门店 | |
| 180 | + var directorStoreDict = new Dictionary<string, List<string>>(); | |
| 181 | + foreach (var directorUser in majorProjectDirectorUserList) | |
| 182 | + { | |
| 183 | + var directorId = directorUser.Id; | |
| 184 | + var directorOrganizeId = directorUser.OrganizeId; | |
| 185 | + | |
| 186 | + // 从lq_md_target表中查找该大项目主管管理的门店 | |
| 187 | + var managedStores = targetList | |
| 188 | + .Where(x => x.MajorProjectDepartment == directorOrganizeId) | |
| 189 | + .Select(x => x.StoreId) | |
| 190 | + .Distinct() | |
| 191 | + .ToList(); | |
| 192 | + | |
| 193 | + directorStoreDict[directorId] = managedStores; | |
| 194 | + } | |
| 195 | + | |
| 196 | + // 1.5 门店信息 (lq_mdxx) | |
| 197 | + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync(); | |
| 198 | + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); | |
| 199 | + | |
| 200 | + // 1.6 考勤数据 (lq_attendance_summary) | |
| 201 | + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>() | |
| 202 | + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) | |
| 203 | + .ToListAsync(); | |
| 204 | + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x); | |
| 205 | + | |
| 206 | + // 1.7 获取所有管理的门店ID列表(用于后续查询) | |
| 207 | + var allManagedStoreIds = directorStoreDict.Values.SelectMany(x => x).Distinct().ToList(); | |
| 208 | + | |
| 209 | + // 1.8 按大项目主管和门店分组统计(用于生成门店明细JSON和汇总数据) | |
| 210 | + var storeDetailDict = new Dictionary<string, Dictionary<string, StoreDetailItem>>(); | |
| 211 | + | |
| 212 | + // 按门店统计开单和退卡金额(如果有管理的门店) | |
| 213 | + if (allManagedStoreIds.Any()) | |
| 214 | + { | |
| 215 | + foreach (var storeId in allManagedStoreIds) | |
| 216 | + { | |
| 217 | + // 该门店的开单金额 | |
| 218 | + var storeBillingAmount = await _db.Queryable<LqKdKdjlbEntity>() | |
| 219 | + .Where(x => x.IsEffective == 1 | |
| 220 | + && x.Djmd == storeId | |
| 221 | + && x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1)) | |
| 222 | + .SumAsync(x => (decimal?)x.Sfyj) ?? 0m; | |
| 223 | + | |
| 224 | + // 该门店的退卡金额(优先使用ActualRefundAmount,如果没有则使用Tkje) | |
| 225 | + var storeRefundAmount = await _db.Queryable<LqHytkHytkEntity>() | |
| 226 | + .Where(x => x.IsEffective == 1 | |
| 227 | + && x.Md == storeId | |
| 228 | + && x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1)) | |
| 229 | + .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m; | |
| 230 | + | |
| 231 | + // 该门店的净总业绩 | |
| 232 | + var storeTotalPerformance = storeBillingAmount - storeRefundAmount; | |
| 233 | + | |
| 234 | + // 找到该门店归属的大项目主管 | |
| 235 | + var storeTarget = targetList.FirstOrDefault(x => x.StoreId == storeId); | |
| 236 | + if (storeTarget != null) | |
| 237 | + { | |
| 238 | + var directorOrganizeId = storeTarget.MajorProjectDepartment; | |
| 239 | + var directorsOfStore = majorProjectDirectorUserList | |
| 240 | + .Where(x => x.OrganizeId == directorOrganizeId) | |
| 241 | + .Select(x => x.Id) | |
| 242 | + .ToList(); | |
| 243 | + | |
| 244 | + foreach (var directorId in directorsOfStore) | |
| 245 | + { | |
| 246 | + if (!storeDetailDict.ContainsKey(directorId)) | |
| 247 | + { | |
| 248 | + storeDetailDict[directorId] = new Dictionary<string, StoreDetailItem>(); | |
| 249 | + } | |
| 250 | + | |
| 251 | + var storeName = storeDict.ContainsKey(storeId) ? storeDict[storeId].Dm ?? "" : ""; | |
| 252 | + storeDetailDict[directorId][storeId] = new StoreDetailItem | |
| 253 | + { | |
| 254 | + StoreId = storeId, | |
| 255 | + StoreName = storeName, | |
| 256 | + BillingAmount = storeBillingAmount, | |
| 257 | + RefundAmount = storeRefundAmount, | |
| 258 | + TotalPerformance = storeTotalPerformance | |
| 259 | + }; | |
| 260 | + } | |
| 261 | + } | |
| 262 | + } | |
| 263 | + } | |
| 264 | + | |
| 265 | + // 2. 按大项目主管聚合数据 | |
| 266 | + var directorStats = new Dictionary<string, LqMajorProjectDirectorSalaryStatisticsEntity>(); | |
| 267 | + | |
| 268 | + foreach (var directorUser in majorProjectDirectorUserList) | |
| 269 | + { | |
| 270 | + var directorId = directorUser.Id; | |
| 271 | + | |
| 272 | + // 获取该大项目主管管理的门店列表 | |
| 273 | + var managedStores = directorStoreDict.ContainsKey(directorId) ? directorStoreDict[directorId] : new List<string>(); | |
| 274 | + | |
| 275 | + // 2.1 创建工资统计对象 | |
| 276 | + // 岗位使用组织名称(大项目一部/大项目二部) | |
| 277 | + var position = majorProjectOrganizeDict.ContainsKey(directorUser.OrganizeId) | |
| 278 | + ? majorProjectOrganizeDict[directorUser.OrganizeId] | |
| 279 | + : ""; | |
| 280 | + | |
| 281 | + var salary = new LqMajorProjectDirectorSalaryStatisticsEntity | |
| 282 | + { | |
| 283 | + Id = YitIdHelper.NextId().ToString(), | |
| 284 | + StatisticsMonth = monthStr, | |
| 285 | + EmployeeId = directorId, | |
| 286 | + Position = position, | |
| 287 | + EmployeeName = directorUser.RealName ?? "", | |
| 288 | + EmployeeAccount = directorUser.Account ?? "", | |
| 289 | + IsTerminated = directorUser.IsOnJob == 0 ? 1 : 0, | |
| 290 | + CreateTime = DateTime.Now, | |
| 291 | + UpdateTime = DateTime.Now, | |
| 292 | + IsLocked = 0 | |
| 293 | + }; | |
| 294 | + | |
| 295 | + // 2.2 考勤数据 | |
| 296 | + var attendance = attendanceDict.ContainsKey(directorId) ? attendanceDict[directorId] : null; | |
| 297 | + salary.WorkingDays = attendance?.WorkDays ?? 0; | |
| 298 | + salary.LeaveDays = attendance?.LeaveDays ?? 0; | |
| 299 | + | |
| 300 | + // 2.3 计算底薪(固定3500元) | |
| 301 | + salary.BaseSalary = 3500m; | |
| 302 | + | |
| 303 | + // 2.4 统计该大项目主管管理的所有门店的总业绩(开单-退卡)总和 | |
| 304 | + decimal totalBillingAmount = 0m; | |
| 305 | + decimal totalRefundAmount = 0m; | |
| 306 | + var storeDetails = new List<StoreDetailItem>(); | |
| 307 | + | |
| 308 | + if (managedStores.Any() && storeDetailDict.ContainsKey(directorId)) | |
| 309 | + { | |
| 310 | + foreach (var storeId in managedStores) | |
| 311 | + { | |
| 312 | + if (storeDetailDict[directorId].ContainsKey(storeId)) | |
| 313 | + { | |
| 314 | + var storeDetail = storeDetailDict[directorId][storeId]; | |
| 315 | + totalBillingAmount += storeDetail.BillingAmount; | |
| 316 | + totalRefundAmount += storeDetail.RefundAmount; | |
| 317 | + storeDetails.Add(storeDetail); | |
| 318 | + } | |
| 319 | + } | |
| 320 | + } | |
| 321 | + | |
| 322 | + salary.BillingAmount = totalBillingAmount; | |
| 323 | + salary.RefundAmount = totalRefundAmount; | |
| 324 | + salary.TotalPerformance = totalBillingAmount - totalRefundAmount; | |
| 325 | + | |
| 326 | + // 2.5 保存门店明细(JSON格式) | |
| 327 | + salary.StoreDetail = JsonConvert.SerializeObject(storeDetails); | |
| 328 | + | |
| 329 | + // 2.6 计算提成(分段方式) | |
| 330 | + var commission = CalculateCommission(salary.TotalPerformance); | |
| 331 | + salary.CommissionAmount = commission.Amount; | |
| 332 | + salary.CommissionRate = commission.Rate; | |
| 333 | + | |
| 334 | + // 2.7 计算应发工资 | |
| 335 | + salary.CalculatedGrossSalary = salary.BaseSalary + salary.CommissionAmount; | |
| 336 | + salary.FinalGrossSalary = salary.CalculatedGrossSalary; | |
| 337 | + | |
| 338 | + // 2.8 初始化其他字段 | |
| 339 | + salary.MonthlyPaymentStatus = "未发放"; | |
| 340 | + salary.ActualSalary = salary.FinalGrossSalary; // 默认实发工资等于应发工资 | |
| 341 | + | |
| 342 | + directorStats[directorId] = salary; | |
| 343 | + } | |
| 344 | + | |
| 345 | + // 3. 保存数据 | |
| 346 | + if (directorStats.Any()) | |
| 347 | + { | |
| 348 | + // 先删除当月旧数据 (防止重复) | |
| 349 | + await _db.Deleteable<LqMajorProjectDirectorSalaryStatisticsEntity>() | |
| 350 | + .Where(x => x.StatisticsMonth == monthStr) | |
| 351 | + .ExecuteCommandAsync(); | |
| 352 | + | |
| 353 | + await _db.Insertable(directorStats.Values.ToList()).ExecuteCommandAsync(); | |
| 354 | + } | |
| 355 | + } | |
| 356 | + | |
| 357 | + /// <summary> | |
| 358 | + /// 计算提成(分段方式) | |
| 359 | + /// </summary> | |
| 360 | + /// <param name="totalPerformance">总业绩</param> | |
| 361 | + /// <returns>提成金额和比例</returns> | |
| 362 | + private (decimal Amount, decimal? Rate) CalculateCommission(decimal totalPerformance) | |
| 363 | + { | |
| 364 | + if (totalPerformance <= 0) | |
| 365 | + { | |
| 366 | + return (0m, null); | |
| 367 | + } | |
| 368 | + | |
| 369 | + decimal commissionAmount = 0m; | |
| 370 | + decimal? rate = null; | |
| 371 | + | |
| 372 | + if (totalPerformance <= 500000m) | |
| 373 | + { | |
| 374 | + // ≤ 50万:无提成 | |
| 375 | + commissionAmount = 0m; | |
| 376 | + rate = null; | |
| 377 | + } | |
| 378 | + else if (totalPerformance <= 700000m) | |
| 379 | + { | |
| 380 | + // 50万 < 总业绩 ≤ 70万:1%提成 | |
| 381 | + commissionAmount = totalPerformance * 0.01m; | |
| 382 | + rate = 1.00m; | |
| 383 | + } | |
| 384 | + else | |
| 385 | + { | |
| 386 | + // > 70万:1.5%提成 | |
| 387 | + commissionAmount = totalPerformance * 0.015m; | |
| 388 | + rate = 1.50m; | |
| 389 | + } | |
| 390 | + | |
| 391 | + return (commissionAmount, rate); | |
| 392 | + } | |
| 393 | + | |
| 394 | + /// <summary> | |
| 395 | + /// 门店明细项(用于JSON序列化) | |
| 396 | + /// </summary> | |
| 397 | + private class StoreDetailItem | |
| 398 | + { | |
| 399 | + public string StoreId { get; set; } | |
| 400 | + public string StoreName { get; set; } | |
| 401 | + public decimal BillingAmount { get; set; } | |
| 402 | + public decimal RefundAmount { get; set; } | |
| 403 | + public decimal TotalPerformance { get; set; } | |
| 404 | + } | |
| 405 | + } | |
| 406 | +} | |
| 407 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs
| ... | ... | @@ -226,6 +226,7 @@ namespace NCC.Extend |
| 226 | 226 | id = x.Id, |
| 227 | 227 | productName = x.ProductName, |
| 228 | 228 | price = x.Price, |
| 229 | + averagePrice = x.AveragePrice, | |
| 229 | 230 | productCategory = x.ProductCategory, |
| 230 | 231 | departmentId = x.DepartmentId, |
| 231 | 232 | departmentName = SqlFunc.Subqueryable<OrganizeEntity>().Where(y => y.Id == x.DepartmentId).Select(y => y.FullName), |
| ... | ... | @@ -346,6 +347,7 @@ namespace NCC.Extend |
| 346 | 347 | id = product.Id, |
| 347 | 348 | productName = product.ProductName, |
| 348 | 349 | price = product.Price, |
| 350 | + averagePrice = product.AveragePrice, | |
| 349 | 351 | productCategory = product.ProductCategory, |
| 350 | 352 | departmentId = product.DepartmentId, |
| 351 | 353 | departmentName = "", | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
| 1 | -using NCC.Common.Core.Manager; | |
| 1 | +using NCC.Common.Core.Manager; | |
| 2 | 2 | using NCC.Common.Enum; |
| 3 | 3 | using NCC.Common.Extension; |
| 4 | 4 | using NCC.Common.Filter; |
| ... | ... | @@ -906,6 +906,7 @@ namespace NCC.Extend.LqReimbursementApplication |
| 906 | 906 | } |
| 907 | 907 | |
| 908 | 908 | // 更新现有记录(包括空字符串、"待审批"、"退回"的情况) |
| 909 | + // 注意:只更新审批结果和意见,不更新审批人信息(ApproverId和ApproverName在创建时已确定) | |
| 909 | 910 | existingRecord.ApprovalResult = result; |
| 910 | 911 | existingRecord.ApprovalOpinion = opinion; |
| 911 | 912 | existingRecord.ApprovalTime = DateTime.Now; |
| ... | ... | @@ -1050,6 +1051,8 @@ namespace NCC.Extend.LqReimbursementApplication |
| 1050 | 1051 | .Select(x => x.UserId) |
| 1051 | 1052 | .ToListAsync(); |
| 1052 | 1053 | |
| 1054 | + // 查询所有"通过"的审批记录,包括当前用户刚审批的记录 | |
| 1055 | + // 注意:由于在事务中,需要确保查询包含当前刚更新的记录 | |
| 1053 | 1056 | var approvedUsers = await _db.Queryable<LqReimbursementApprovalRecordEntity>() |
| 1054 | 1057 | .Where(x => x.ApplicationId == id |
| 1055 | 1058 | && x.NodeOrder == entity.CurrentNodeOrder |
| ... | ... | @@ -1058,7 +1061,13 @@ namespace NCC.Extend.LqReimbursementApplication |
| 1058 | 1061 | .Distinct() |
| 1059 | 1062 | .ToListAsync(); |
| 1060 | 1063 | |
| 1061 | - if (approvers.Count == approvedUsers.Count) | |
| 1064 | + // 如果当前用户刚审批通过,但查询结果中还没有包含,手动添加 | |
| 1065 | + if (result == "通过" && !approvedUsers.Contains(userInfo.userId)) | |
| 1066 | + { | |
| 1067 | + approvedUsers.Add(userInfo.userId); | |
| 1068 | + } | |
| 1069 | + | |
| 1070 | + if (approvers.Count == approvedUsers.Count && approvers.Count > 0) | |
| 1062 | 1071 | { |
| 1063 | 1072 | // 所有人都已通过 |
| 1064 | 1073 | shouldMoveToNext = true; |
| ... | ... | @@ -1506,8 +1515,8 @@ namespace NCC.Extend.LqReimbursementApplication |
| 1506 | 1515 | // 查询本月已审核通过的报销申请 |
| 1507 | 1516 | var applications = await _db.Queryable<LqReimbursementApplicationEntity>() |
| 1508 | 1517 | .Where(x => (x.ApprovalStatus ?? x.ApproveStatus) == "已通过") |
| 1509 | - .Where(x => x.ApplicationTime.HasValue && | |
| 1510 | - x.ApplicationTime.Value.Year == queryYear && | |
| 1518 | + .Where(x => x.ApplicationTime.HasValue && | |
| 1519 | + x.ApplicationTime.Value.Year == queryYear && | |
| 1511 | 1520 | x.ApplicationTime.Value.Month == int.Parse(queryMonth)) |
| 1512 | 1521 | .ToListAsync(); |
| 1513 | 1522 | |
| ... | ... | @@ -1543,7 +1552,7 @@ namespace NCC.Extend.LqReimbursementApplication |
| 1543 | 1552 | foreach (var app in applications) |
| 1544 | 1553 | { |
| 1545 | 1554 | var appPurchaseRecords = purchaseRecords.Where(x => x.ApplicationId == app.Id).ToList(); |
| 1546 | - | |
| 1555 | + | |
| 1547 | 1556 | if (appPurchaseRecords.Any()) |
| 1548 | 1557 | { |
| 1549 | 1558 | // 每个购买记录作为一行 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.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.LqTechGeneralManagerSalary; | |
| 8 | +using NCC.Extend.Entitys.lq_attendance_summary; | |
| 9 | +using NCC.Extend.Entitys.lq_hytk_hytk; | |
| 10 | +using NCC.Extend.Entitys.lq_hytk_mx; | |
| 11 | +using NCC.Extend.Entitys.lq_kd_kdjlb; | |
| 12 | +using NCC.Extend.Entitys.lq_kd_pxmx; | |
| 13 | +using NCC.Extend.Entitys.lq_md_general_manager_lifeline; | |
| 14 | +using NCC.Extend.Entitys.lq_mdxx; | |
| 15 | +using NCC.Extend.Entitys.lq_tech_general_manager_salary_statistics; | |
| 16 | +using NCC.Extend.Entitys.lq_xmzl; | |
| 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 | +using Newtonsoft.Json; | |
| 25 | + | |
| 26 | +namespace NCC.Extend | |
| 27 | +{ | |
| 28 | + /// <summary> | |
| 29 | + /// 科技部总经理薪酬服务 | |
| 30 | + /// </summary> | |
| 31 | + [ApiDescriptionSettings(Tag = "科技部总经理薪酬服务", Name = "LqTechGeneralManagerSalary", Order = 305)] | |
| 32 | + [Route("api/Extend/[controller]")] | |
| 33 | + public class LqTechGeneralManagerSalaryService : IDynamicApiController, ITransient | |
| 34 | + { | |
| 35 | + private readonly ISqlSugarClient _db; | |
| 36 | + | |
| 37 | + /// <summary> | |
| 38 | + /// 初始化一个<see cref="LqTechGeneralManagerSalaryService"/>类型的新实例 | |
| 39 | + /// </summary> | |
| 40 | + public LqTechGeneralManagerSalaryService(ISqlSugarClient db) | |
| 41 | + { | |
| 42 | + _db = db; | |
| 43 | + } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 获取科技部总经理工资列表 | |
| 47 | + /// </summary> | |
| 48 | + /// <param name="input">查询参数</param> | |
| 49 | + /// <returns>科技部总经理工资分页列表</returns> | |
| 50 | + [HttpGet("tech-general-manager")] | |
| 51 | + public async Task<dynamic> GetTechGeneralManagerSalaryList([FromQuery] TechGeneralManagerSalaryInput input) | |
| 52 | + { | |
| 53 | + var monthStr = $"{input.Year}{input.Month:D2}"; | |
| 54 | + | |
| 55 | + // 1. 检查当月是否已生成工资数据 | |
| 56 | + var exists = await _db.Queryable<LqTechGeneralManagerSalaryStatisticsEntity>() | |
| 57 | + .AnyAsync(x => x.StatisticsMonth == monthStr); | |
| 58 | + | |
| 59 | + // 2. 如果没有数据,则进行计算 | |
| 60 | + if (!exists) | |
| 61 | + { | |
| 62 | + await CalculateTechGeneralManagerSalary(input.Year, input.Month); | |
| 63 | + } | |
| 64 | + | |
| 65 | + // 3. 查询数据 | |
| 66 | + var query = _db.Queryable<LqTechGeneralManagerSalaryStatisticsEntity>() | |
| 67 | + .Where(x => x.StatisticsMonth == monthStr); | |
| 68 | + | |
| 69 | + if (!string.IsNullOrEmpty(input.Position)) | |
| 70 | + { | |
| 71 | + query = query.Where(x => x.Position == input.Position); | |
| 72 | + } | |
| 73 | + | |
| 74 | + if (!string.IsNullOrEmpty(input.Keyword)) | |
| 75 | + { | |
| 76 | + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeAccount.Contains(input.Keyword)); | |
| 77 | + } | |
| 78 | + | |
| 79 | + var list = await query.Select(x => new TechGeneralManagerSalaryOutput | |
| 80 | + { | |
| 81 | + Id = x.Id, | |
| 82 | + StatisticsMonth = x.StatisticsMonth, | |
| 83 | + Position = x.Position, | |
| 84 | + EmployeeName = x.EmployeeName, | |
| 85 | + EmployeeId = x.EmployeeId, | |
| 86 | + EmployeeAccount = x.EmployeeAccount, | |
| 87 | + IsTerminated = x.IsTerminated, | |
| 88 | + StoreDetail = x.StoreDetail, | |
| 89 | + TraceabilityAmount = x.TraceabilityAmount, | |
| 90 | + CellAmount = x.CellAmount, | |
| 91 | + BaseSalary = x.BaseSalary, | |
| 92 | + TraceabilityCommissionRate = x.TraceabilityCommissionRate, | |
| 93 | + TraceabilityCommissionAmount = x.TraceabilityCommissionAmount, | |
| 94 | + CellCommissionRate = x.CellCommissionRate, | |
| 95 | + CellCommissionAmount = x.CellCommissionAmount, | |
| 96 | + TotalCommission = x.TotalCommission, | |
| 97 | + WorkingDays = x.WorkingDays, | |
| 98 | + LeaveDays = x.LeaveDays, | |
| 99 | + CalculatedGrossSalary = x.CalculatedGrossSalary, | |
| 100 | + FinalGrossSalary = x.FinalGrossSalary, | |
| 101 | + MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy, | |
| 102 | + MonthlyTransportSubsidy = x.MonthlyTransportSubsidy, | |
| 103 | + LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy, | |
| 104 | + LastMonthTransportSubsidy = x.LastMonthTransportSubsidy, | |
| 105 | + TotalSubsidy = x.TotalSubsidy, | |
| 106 | + MissingCard = x.MissingCard, | |
| 107 | + LateArrival = x.LateArrival, | |
| 108 | + LeaveDeduction = x.LeaveDeduction, | |
| 109 | + SocialInsuranceDeduction = x.SocialInsuranceDeduction, | |
| 110 | + RewardDeduction = x.RewardDeduction, | |
| 111 | + AccommodationDeduction = x.AccommodationDeduction, | |
| 112 | + StudyPeriodDeduction = x.StudyPeriodDeduction, | |
| 113 | + WorkClothesDeduction = x.WorkClothesDeduction, | |
| 114 | + TotalDeduction = x.TotalDeduction, | |
| 115 | + Bonus = x.Bonus, | |
| 116 | + ReturnPhoneDeposit = x.ReturnPhoneDeposit, | |
| 117 | + ReturnAccommodationDeposit = x.ReturnAccommodationDeposit, | |
| 118 | + ActualSalary = x.ActualSalary, | |
| 119 | + MonthlyPaymentStatus = x.MonthlyPaymentStatus, | |
| 120 | + PaidAmount = x.PaidAmount, | |
| 121 | + PendingAmount = x.PendingAmount, | |
| 122 | + LastMonthSupplement = x.LastMonthSupplement, | |
| 123 | + MonthlyTotalPayment = x.MonthlyTotalPayment, | |
| 124 | + IsLocked = x.IsLocked, | |
| 125 | + UpdateTime = x.UpdateTime | |
| 126 | + }) | |
| 127 | + .ToPagedListAsync(input.currentPage, input.pageSize); | |
| 128 | + | |
| 129 | + return PageResult<TechGeneralManagerSalaryOutput>.SqlSugarPageResult(list); | |
| 130 | + } | |
| 131 | + | |
| 132 | + /// <summary> | |
| 133 | + /// 计算科技部总经理工资 | |
| 134 | + /// </summary> | |
| 135 | + /// <param name="year">年份</param> | |
| 136 | + /// <param name="month">月份</param> | |
| 137 | + /// <returns></returns> | |
| 138 | + [HttpPost("calculate/tech-general-manager")] | |
| 139 | + public async Task CalculateTechGeneralManagerSalary(int year, int month) | |
| 140 | + { | |
| 141 | + var startDate = new DateTime(year, month, 1); | |
| 142 | + var endDate = startDate.AddMonths(1).AddDays(-1); | |
| 143 | + var monthStr = $"{year}{month:D2}"; | |
| 144 | + | |
| 145 | + // 1. 获取基础数据 | |
| 146 | + | |
| 147 | + // 1.1 先从BASE_ORGANIZE表查找组织名称包含"科技一部"或"科技二部"的组织 | |
| 148 | + var techOrganizeList = await _db.Queryable<OrganizeEntity>() | |
| 149 | + .Where(x => x.FullName != null && (x.FullName.Contains("科技一部") || x.FullName.Contains("科技二部")) | |
| 150 | + && x.DeleteMark == null && x.EnabledMark == 1) | |
| 151 | + .Select(x => new { x.Id, x.FullName }) | |
| 152 | + .ToListAsync(); | |
| 153 | + | |
| 154 | + if (!techOrganizeList.Any()) | |
| 155 | + { | |
| 156 | + // 如果没有找到科技部组织,直接返回 | |
| 157 | + return; | |
| 158 | + } | |
| 159 | + | |
| 160 | + var techOrganizeIds = techOrganizeList.Select(x => x.Id).ToList(); | |
| 161 | + var techOrganizeDict = techOrganizeList.ToDictionary(x => x.Id, x => x.FullName); | |
| 162 | + | |
| 163 | + // 1.2 从BASE_USER表查询岗位为"总经理"且组织ID在科技一部或科技二部的员工 | |
| 164 | + var techGeneralManagerUserList = await _db.Queryable<UserEntity>() | |
| 165 | + .Where(x => x.Gw == "总经理" | |
| 166 | + && techOrganizeIds.Contains(x.OrganizeId) | |
| 167 | + && x.DeleteMark == null && x.EnabledMark == 1) | |
| 168 | + .Select(x => new { x.Id, x.RealName, x.Account, x.Gw, x.OrganizeId, x.IsOnJob }) | |
| 169 | + .ToListAsync(); | |
| 170 | + | |
| 171 | + if (!techGeneralManagerUserList.Any()) | |
| 172 | + { | |
| 173 | + // 如果没有科技部总经理员工,直接返回 | |
| 174 | + return; | |
| 175 | + } | |
| 176 | + | |
| 177 | + if (!techGeneralManagerUserList.Any()) | |
| 178 | + { | |
| 179 | + // 如果没有科技部总经理员工,直接返回 | |
| 180 | + return; | |
| 181 | + } | |
| 182 | + | |
| 183 | + // 1.3 获取科技部总经理归属信息(从lq_md_general_manager_lifeline表) | |
| 184 | + // 通过门店的科技部组织ID(kjb字段)找到科技一部或科技二部管理的门店 | |
| 185 | + // 然后在lifeline表中找到这些门店的记录,这些记录对应的总经理就是科技部总经理 | |
| 186 | + var lifelineList = await _db.Queryable<LqMdGeneralManagerLifelineEntity, LqMdxxEntity>( | |
| 187 | + (lifeline, store) => lifeline.StoreId == store.Id) | |
| 188 | + .Where((lifeline, store) => | |
| 189 | + lifeline.Month == monthStr | |
| 190 | + && techOrganizeIds.Contains(store.Kjb)) | |
| 191 | + .Select((lifeline, store) => lifeline) | |
| 192 | + .ToListAsync(); | |
| 193 | + | |
| 194 | + // 1.4 获取科技一部和科技二部管理的门店(通过门店的kjb字段) | |
| 195 | + var techManagedStoreIds = await _db.Queryable<LqMdxxEntity>() | |
| 196 | + .Where(x => techOrganizeIds.Contains(x.Kjb)) | |
| 197 | + .Select(x => x.Id) | |
| 198 | + .ToListAsync(); | |
| 199 | + | |
| 200 | + // 1.5 按科技部总经理ID分组,获取每个科技部总经理管理的门店 | |
| 201 | + // 科技部总经理管理的门店 = 科技一部/科技二部管理的所有门店(通过门店的kjb字段确定) | |
| 202 | + var managerStoreDict = new Dictionary<string, List<string>>(); | |
| 203 | + foreach (var managerUser in techGeneralManagerUserList) | |
| 204 | + { | |
| 205 | + var managerId = managerUser.Id; | |
| 206 | + var managerOrganizeId = managerUser.OrganizeId; | |
| 207 | + | |
| 208 | + // 如果该总经理属于科技一部,则管理所有科技一部的门店 | |
| 209 | + // 如果该总经理属于科技二部,则管理所有科技二部的门店 | |
| 210 | + var managedStores = await _db.Queryable<LqMdxxEntity>() | |
| 211 | + .Where(x => x.Kjb == managerOrganizeId) | |
| 212 | + .Select(x => x.Id) | |
| 213 | + .ToListAsync(); | |
| 214 | + | |
| 215 | + managerStoreDict[managerId] = managedStores; | |
| 216 | + } | |
| 217 | + | |
| 218 | + // 1.4 门店信息 (lq_mdxx) | |
| 219 | + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync(); | |
| 220 | + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); | |
| 221 | + | |
| 222 | + // 1.6 考勤数据 (lq_attendance_summary) | |
| 223 | + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>() | |
| 224 | + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) | |
| 225 | + .ToListAsync(); | |
| 226 | + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x); | |
| 227 | + | |
| 228 | + // 1.7 获取所有管理的门店ID列表(用于后续查询,如果没有管理的门店,则为空列表) | |
| 229 | + var allManagedStoreIds = managerStoreDict.Values.SelectMany(x => x).Distinct().ToList(); | |
| 230 | + | |
| 231 | + // 1.8 按科技部总经理和门店分组统计(用于生成门店明细JSON和汇总数据) | |
| 232 | + var storeDetailDict = new Dictionary<string, Dictionary<string, StoreDetailItem>>(); | |
| 233 | + | |
| 234 | + // 按门店统计溯源和Cell金额(如果有管理的门店) | |
| 235 | + if (allManagedStoreIds.Any()) | |
| 236 | + { | |
| 237 | + foreach (var storeId in allManagedStoreIds) | |
| 238 | + { | |
| 239 | + // 该门店的开单溯源金额 | |
| 240 | + var storeTraceabilityBilling = await _db.Queryable<LqKdPxmxEntity, LqKdKdjlbEntity, LqXmzlEntity>( | |
| 241 | + (pxmx, billing, item) => pxmx.Glkdbh == billing.Id && pxmx.Px == item.Id) | |
| 242 | + .Where((pxmx, billing, item) => | |
| 243 | + pxmx.IsEffective == 1 | |
| 244 | + && billing.IsEffective == 1 | |
| 245 | + && item.IsEffective == 1 | |
| 246 | + && (pxmx.BeautyType == "溯源系统" || pxmx.BeautyType == "溯源" | |
| 247 | + || item.BeautyType == "溯源系统" || item.BeautyType == "溯源") | |
| 248 | + && billing.Djmd == storeId | |
| 249 | + && billing.Kdrq >= startDate && billing.Kdrq <= endDate.AddDays(1)) | |
| 250 | + .SumAsync((pxmx, billing, item) => (decimal?)pxmx.ActualPrice) ?? 0m; | |
| 251 | + | |
| 252 | + // 该门店的退卡溯源金额 | |
| 253 | + var storeTraceabilityRefund = await _db.Queryable<LqHytkMxEntity, LqHytkHytkEntity, LqXmzlEntity>( | |
| 254 | + (tkmx, refund, item) => tkmx.RefundInfoId == refund.Id && tkmx.Px == item.Id) | |
| 255 | + .Where((tkmx, refund, item) => | |
| 256 | + tkmx.IsEffective == 1 | |
| 257 | + && refund.IsEffective == 1 | |
| 258 | + && item.IsEffective == 1 | |
| 259 | + && (tkmx.BeautyType == "溯源系统" || tkmx.BeautyType == "溯源" | |
| 260 | + || item.BeautyType == "溯源系统" || item.BeautyType == "溯源") | |
| 261 | + && refund.Md == storeId | |
| 262 | + && refund.Tksj >= startDate && refund.Tksj <= endDate.AddDays(1)) | |
| 263 | + .SumAsync((tkmx, refund, item) => (decimal?)tkmx.Tkje) ?? 0m; | |
| 264 | + | |
| 265 | + // 该门店的开单Cell金额 | |
| 266 | + var storeCellBilling = await _db.Queryable<LqKdPxmxEntity, LqKdKdjlbEntity, LqXmzlEntity>( | |
| 267 | + (pxmx, billing, item) => pxmx.Glkdbh == billing.Id && pxmx.Px == item.Id) | |
| 268 | + .Where((pxmx, billing, item) => | |
| 269 | + pxmx.IsEffective == 1 | |
| 270 | + && billing.IsEffective == 1 | |
| 271 | + && item.IsEffective == 1 | |
| 272 | + && (pxmx.BeautyType == "cell" || pxmx.BeautyType == "Cell" | |
| 273 | + || item.BeautyType == "cell" || item.BeautyType == "Cell") | |
| 274 | + && billing.Djmd == storeId | |
| 275 | + && billing.Kdrq >= startDate && billing.Kdrq <= endDate.AddDays(1)) | |
| 276 | + .SumAsync((pxmx, billing, item) => (decimal?)pxmx.ActualPrice) ?? 0m; | |
| 277 | + | |
| 278 | + // 该门店的退卡Cell金额 | |
| 279 | + var storeCellRefund = await _db.Queryable<LqHytkMxEntity, LqHytkHytkEntity, LqXmzlEntity>( | |
| 280 | + (tkmx, refund, item) => tkmx.RefundInfoId == refund.Id && tkmx.Px == item.Id) | |
| 281 | + .Where((tkmx, refund, item) => | |
| 282 | + tkmx.IsEffective == 1 | |
| 283 | + && refund.IsEffective == 1 | |
| 284 | + && item.IsEffective == 1 | |
| 285 | + && (tkmx.BeautyType == "cell" || tkmx.BeautyType == "Cell" | |
| 286 | + || item.BeautyType == "cell" || item.BeautyType == "Cell") | |
| 287 | + && refund.Md == storeId | |
| 288 | + && refund.Tksj >= startDate && refund.Tksj <= endDate.AddDays(1)) | |
| 289 | + .SumAsync((tkmx, refund, item) => (decimal?)tkmx.Tkje) ?? 0m; | |
| 290 | + | |
| 291 | + // 获取该门店属于哪些科技部总经理 | |
| 292 | + // 通过门店的kjb字段确定:如果门店的kjb等于科技一部的组织ID,则该门店属于科技一部总经理 | |
| 293 | + var store = storeDict.ContainsKey(storeId) ? storeDict[storeId] : null; | |
| 294 | + var managersOfStore = new List<string>(); | |
| 295 | + | |
| 296 | + if (store != null && !string.IsNullOrEmpty(store.Kjb)) | |
| 297 | + { | |
| 298 | + // 找到组织ID等于门店kjb的科技部总经理 | |
| 299 | + var managers = techGeneralManagerUserList | |
| 300 | + .Where(x => x.OrganizeId == store.Kjb) | |
| 301 | + .Select(x => x.Id) | |
| 302 | + .ToList(); | |
| 303 | + managersOfStore.AddRange(managers); | |
| 304 | + } | |
| 305 | + | |
| 306 | + foreach (var managerId in managersOfStore) | |
| 307 | + { | |
| 308 | + if (!storeDetailDict.ContainsKey(managerId)) | |
| 309 | + { | |
| 310 | + storeDetailDict[managerId] = new Dictionary<string, StoreDetailItem>(); | |
| 311 | + } | |
| 312 | + | |
| 313 | + var storeName = storeDict.ContainsKey(storeId) ? storeDict[storeId].Dm ?? "" : ""; | |
| 314 | + storeDetailDict[managerId][storeId] = new StoreDetailItem | |
| 315 | + { | |
| 316 | + StoreId = storeId, | |
| 317 | + StoreName = storeName, | |
| 318 | + TraceabilityBillingAmount = storeTraceabilityBilling, | |
| 319 | + TraceabilityRefundAmount = storeTraceabilityRefund, | |
| 320 | + TraceabilityAmount = storeTraceabilityBilling - storeTraceabilityRefund, | |
| 321 | + CellBillingAmount = storeCellBilling, | |
| 322 | + CellRefundAmount = storeCellRefund, | |
| 323 | + CellAmount = storeCellBilling - storeCellRefund | |
| 324 | + }; | |
| 325 | + } | |
| 326 | + } | |
| 327 | + } | |
| 328 | + | |
| 329 | + // 2. 按科技部总经理聚合数据 | |
| 330 | + var managerStats = new Dictionary<string, LqTechGeneralManagerSalaryStatisticsEntity>(); | |
| 331 | + | |
| 332 | + foreach (var managerUser in techGeneralManagerUserList) | |
| 333 | + { | |
| 334 | + var managerId = managerUser.Id; | |
| 335 | + | |
| 336 | + // 获取该科技部总经理管理的门店列表 | |
| 337 | + var managedStores = managerStoreDict.ContainsKey(managerId) ? managerStoreDict[managerId] : new List<string>(); | |
| 338 | + | |
| 339 | + // 2.1 创建工资统计对象 | |
| 340 | + // 岗位使用组织名称(科技一部/科技二部) | |
| 341 | + var position = techOrganizeDict.ContainsKey(managerUser.OrganizeId) | |
| 342 | + ? techOrganizeDict[managerUser.OrganizeId] | |
| 343 | + : ""; | |
| 344 | + | |
| 345 | + var salary = new LqTechGeneralManagerSalaryStatisticsEntity | |
| 346 | + { | |
| 347 | + Id = YitIdHelper.NextId().ToString(), | |
| 348 | + StatisticsMonth = monthStr, | |
| 349 | + EmployeeId = managerId, | |
| 350 | + Position = position, | |
| 351 | + EmployeeName = managerUser.RealName ?? "", | |
| 352 | + EmployeeAccount = managerUser.Account ?? "", | |
| 353 | + IsTerminated = managerUser.IsOnJob == 0 ? 1 : 0, | |
| 354 | + CreateTime = DateTime.Now, | |
| 355 | + UpdateTime = DateTime.Now, | |
| 356 | + IsLocked = 0 | |
| 357 | + }; | |
| 358 | + | |
| 359 | + // 2.2 考勤数据 | |
| 360 | + var attendance = attendanceDict.ContainsKey(managerId) ? attendanceDict[managerId] : null; | |
| 361 | + salary.WorkingDays = attendance?.WorkDays ?? 0; | |
| 362 | + salary.LeaveDays = attendance?.LeaveDays ?? 0; | |
| 363 | + | |
| 364 | + // 2.3 计算底薪(固定4000元) | |
| 365 | + salary.BaseSalary = 4000m; | |
| 366 | + | |
| 367 | + // 2.4 统计该科技部总经理管理的所有门店的溯源金额和Cell金额总和 | |
| 368 | + decimal totalTraceabilityAmount = 0m; | |
| 369 | + decimal totalCellAmount = 0m; | |
| 370 | + var storeDetails = new List<StoreDetailItem>(); | |
| 371 | + | |
| 372 | + if (managedStores.Any() && storeDetailDict.ContainsKey(managerId)) | |
| 373 | + { | |
| 374 | + foreach (var storeId in managedStores) | |
| 375 | + { | |
| 376 | + if (storeDetailDict[managerId].ContainsKey(storeId)) | |
| 377 | + { | |
| 378 | + var storeDetail = storeDetailDict[managerId][storeId]; | |
| 379 | + totalTraceabilityAmount += storeDetail.TraceabilityAmount; | |
| 380 | + totalCellAmount += storeDetail.CellAmount; | |
| 381 | + storeDetails.Add(storeDetail); | |
| 382 | + } | |
| 383 | + } | |
| 384 | + } | |
| 385 | + | |
| 386 | + salary.TraceabilityAmount = totalTraceabilityAmount; | |
| 387 | + salary.CellAmount = totalCellAmount; | |
| 388 | + | |
| 389 | + // 2.5 保存门店明细(JSON格式) | |
| 390 | + salary.StoreDetail = JsonConvert.SerializeObject(storeDetails); | |
| 391 | + | |
| 392 | + // 2.6 计算溯源金额提成(分段累进) | |
| 393 | + var traceabilityCommission = CalculateTraceabilityCommission(totalTraceabilityAmount); | |
| 394 | + salary.TraceabilityCommissionAmount = traceabilityCommission.Amount; | |
| 395 | + salary.TraceabilityCommissionRate = traceabilityCommission.Rate; | |
| 396 | + | |
| 397 | + // 2.7 计算Cell金额提成(分段累进) | |
| 398 | + var cellCommission = CalculateCellCommission(totalCellAmount); | |
| 399 | + salary.CellCommissionAmount = cellCommission.Amount; | |
| 400 | + salary.CellCommissionRate = cellCommission.Rate; | |
| 401 | + | |
| 402 | + // 2.8 提成合计 | |
| 403 | + salary.TotalCommission = salary.TraceabilityCommissionAmount + salary.CellCommissionAmount; | |
| 404 | + | |
| 405 | + // 2.9 计算应发工资 | |
| 406 | + salary.CalculatedGrossSalary = salary.BaseSalary + salary.TotalCommission; | |
| 407 | + salary.FinalGrossSalary = salary.CalculatedGrossSalary; | |
| 408 | + | |
| 409 | + // 2.10 初始化其他字段(默认值为0) | |
| 410 | + salary.MonthlyTrainingSubsidy = 0; | |
| 411 | + salary.MonthlyTransportSubsidy = 0; | |
| 412 | + salary.LastMonthTrainingSubsidy = 0; | |
| 413 | + salary.LastMonthTransportSubsidy = 0; | |
| 414 | + salary.TotalSubsidy = 0; | |
| 415 | + salary.MissingCard = 0; | |
| 416 | + salary.LateArrival = 0; | |
| 417 | + salary.LeaveDeduction = 0; | |
| 418 | + salary.SocialInsuranceDeduction = 0; | |
| 419 | + salary.RewardDeduction = 0; | |
| 420 | + salary.AccommodationDeduction = 0; | |
| 421 | + salary.StudyPeriodDeduction = 0; | |
| 422 | + salary.WorkClothesDeduction = 0; | |
| 423 | + salary.TotalDeduction = 0; | |
| 424 | + salary.Bonus = 0; | |
| 425 | + salary.ReturnPhoneDeposit = 0; | |
| 426 | + salary.ReturnAccommodationDeposit = 0; | |
| 427 | + salary.ActualSalary = salary.FinalGrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus; | |
| 428 | + salary.MonthlyPaymentStatus = "未发放"; | |
| 429 | + salary.PaidAmount = 0; | |
| 430 | + salary.PendingAmount = salary.ActualSalary; | |
| 431 | + salary.LastMonthSupplement = 0; | |
| 432 | + salary.MonthlyTotalPayment = 0; | |
| 433 | + | |
| 434 | + managerStats[managerId] = salary; | |
| 435 | + } | |
| 436 | + | |
| 437 | + // 3. 保存数据 | |
| 438 | + if (managerStats.Any()) | |
| 439 | + { | |
| 440 | + // 先删除当月旧数据 (防止重复) | |
| 441 | + await _db.Deleteable<LqTechGeneralManagerSalaryStatisticsEntity>() | |
| 442 | + .Where(x => x.StatisticsMonth == monthStr) | |
| 443 | + .ExecuteCommandAsync(); | |
| 444 | + | |
| 445 | + await _db.Insertable(managerStats.Values.ToList()).ExecuteCommandAsync(); | |
| 446 | + } | |
| 447 | + } | |
| 448 | + | |
| 449 | + /// <summary> | |
| 450 | + /// 计算溯源金额提成(分段累进) | |
| 451 | + /// </summary> | |
| 452 | + /// <param name="traceabilityAmount">溯源金额</param> | |
| 453 | + /// <returns>提成金额和平均比例</returns> | |
| 454 | + private (decimal Amount, decimal? Rate) CalculateTraceabilityCommission(decimal traceabilityAmount) | |
| 455 | + { | |
| 456 | + if (traceabilityAmount <= 0) | |
| 457 | + { | |
| 458 | + return (0m, null); | |
| 459 | + } | |
| 460 | + | |
| 461 | + decimal commissionAmount = 0m; | |
| 462 | + decimal? averageRate = null; | |
| 463 | + | |
| 464 | + if (traceabilityAmount < 200000m) | |
| 465 | + { | |
| 466 | + // < 200,000元:1% | |
| 467 | + commissionAmount = traceabilityAmount * 0.01m; | |
| 468 | + averageRate = 1.00m; | |
| 469 | + } | |
| 470 | + else if (traceabilityAmount < 300000m) | |
| 471 | + { | |
| 472 | + // 200,000-300,000元:1.5% | |
| 473 | + commissionAmount = 200000m * 0.01m + (traceabilityAmount - 200000m) * 0.015m; | |
| 474 | + averageRate = (commissionAmount / traceabilityAmount) * 100m; | |
| 475 | + } | |
| 476 | + else if (traceabilityAmount < 500000m) | |
| 477 | + { | |
| 478 | + // 300,000-500,000元:2% | |
| 479 | + commissionAmount = 200000m * 0.01m + 100000m * 0.015m + (traceabilityAmount - 300000m) * 0.02m; | |
| 480 | + averageRate = (commissionAmount / traceabilityAmount) * 100m; | |
| 481 | + } | |
| 482 | + else | |
| 483 | + { | |
| 484 | + // ≥ 500,000元:2.5% | |
| 485 | + commissionAmount = 200000m * 0.01m + 100000m * 0.015m + 200000m * 0.02m + (traceabilityAmount - 500000m) * 0.025m; | |
| 486 | + averageRate = (commissionAmount / traceabilityAmount) * 100m; | |
| 487 | + } | |
| 488 | + | |
| 489 | + return (commissionAmount, averageRate); | |
| 490 | + } | |
| 491 | + | |
| 492 | + /// <summary> | |
| 493 | + /// 计算Cell金额提成(分段累进) | |
| 494 | + /// </summary> | |
| 495 | + /// <param name="cellAmount">Cell金额</param> | |
| 496 | + /// <returns>提成金额和平均比例</returns> | |
| 497 | + private (decimal Amount, decimal? Rate) CalculateCellCommission(decimal cellAmount) | |
| 498 | + { | |
| 499 | + if (cellAmount <= 0) | |
| 500 | + { | |
| 501 | + return (0m, null); | |
| 502 | + } | |
| 503 | + | |
| 504 | + if (cellAmount < 50000m) | |
| 505 | + { | |
| 506 | + // < 50,000元:无提成 | |
| 507 | + return (0m, null); | |
| 508 | + } | |
| 509 | + | |
| 510 | + decimal commissionAmount = 0m; | |
| 511 | + decimal? averageRate = null; | |
| 512 | + | |
| 513 | + if (cellAmount < 400000m) | |
| 514 | + { | |
| 515 | + // 50,000-400,000元:1% | |
| 516 | + commissionAmount = (cellAmount - 50000m) * 0.01m; | |
| 517 | + averageRate = (commissionAmount / cellAmount) * 100m; | |
| 518 | + } | |
| 519 | + else | |
| 520 | + { | |
| 521 | + // ≥ 400,000元:1.5% | |
| 522 | + commissionAmount = 350000m * 0.01m + (cellAmount - 400000m) * 0.015m; | |
| 523 | + averageRate = (commissionAmount / cellAmount) * 100m; | |
| 524 | + } | |
| 525 | + | |
| 526 | + return (commissionAmount, averageRate); | |
| 527 | + } | |
| 528 | + | |
| 529 | + /// <summary> | |
| 530 | + /// 门店明细项(用于JSON序列化) | |
| 531 | + /// </summary> | |
| 532 | + private class StoreDetailItem | |
| 533 | + { | |
| 534 | + public string StoreId { get; set; } | |
| 535 | + public string StoreName { get; set; } | |
| 536 | + public decimal TraceabilityBillingAmount { get; set; } | |
| 537 | + public decimal TraceabilityRefundAmount { get; set; } | |
| 538 | + public decimal TraceabilityAmount { get; set; } | |
| 539 | + public decimal CellBillingAmount { get; set; } | |
| 540 | + public decimal CellRefundAmount { get; set; } | |
| 541 | + public decimal CellAmount { get; set; } | |
| 542 | + } | |
| 543 | + } | |
| 544 | +} | |
| 545 | + | ... | ... |
sql/主任工资表新增毛利相关字段.sql
0 → 100644
| 1 | +-- 主任工资表新增毛利相关字段 | |
| 2 | +-- 表名:lq_director_salary_statistics | |
| 3 | +-- 说明:为主任工资计算添加毛利相关字段,用于计算基于毛利的提成 | |
| 4 | + | |
| 5 | +-- 1. 销售业绩(开单业绩-退款业绩) | |
| 6 | +ALTER TABLE lq_director_salary_statistics | |
| 7 | +ADD COLUMN F_SalesPerformance DECIMAL(18,2) DEFAULT 0.00 COMMENT '销售业绩(开单业绩-退款业绩)' AFTER F_StoreRefundPerformance; | |
| 8 | + | |
| 9 | +-- 2. 产品物料(仓库领用金额) | |
| 10 | +ALTER TABLE lq_director_salary_statistics | |
| 11 | +ADD COLUMN F_ProductMaterial DECIMAL(18,2) DEFAULT 0.00 COMMENT '产品物料(仓库领用金额,注意11月特殊规则:11月工资算10月数据)' AFTER F_SalesPerformance; | |
| 12 | + | |
| 13 | +-- 3. 合作项目成本 | |
| 14 | +ALTER TABLE lq_director_salary_statistics | |
| 15 | +ADD COLUMN F_CooperationCost DECIMAL(18,2) DEFAULT 0.00 COMMENT '合作项目成本' AFTER F_ProductMaterial; | |
| 16 | + | |
| 17 | +-- 4. 店内支出 | |
| 18 | +ALTER TABLE lq_director_salary_statistics | |
| 19 | +ADD COLUMN F_StoreExpense DECIMAL(18,2) DEFAULT 0.00 COMMENT '店内支出' AFTER F_CooperationCost; | |
| 20 | + | |
| 21 | +-- 5. 洗毛巾费用 | |
| 22 | +ALTER TABLE lq_director_salary_statistics | |
| 23 | +ADD COLUMN F_LaundryCost DECIMAL(18,2) DEFAULT 0.00 COMMENT '洗毛巾费用(只统计送出的记录,F_FlowType = 0)' AFTER F_StoreExpense; | |
| 24 | + | |
| 25 | +-- 6. 毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾) | |
| 26 | +ALTER TABLE lq_director_salary_statistics | |
| 27 | +ADD COLUMN F_GrossProfit DECIMAL(18,2) DEFAULT 0.00 COMMENT '毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾)' AFTER F_LaundryCost; | |
| 28 | + | |
| 29 | +-- 7. 修改 F_StoreTotalPerformance 字段注释(说明该字段存储的是毛利,用于提成计算) | |
| 30 | +-- 注意:此字段的值将在代码中改为存储毛利,而不是开单-退卡 | |
| 31 | +ALTER TABLE lq_director_salary_statistics | |
| 32 | +MODIFY COLUMN F_StoreTotalPerformance DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店总业绩(毛利,用于提成计算)'; | |
| 33 | + | ... | ... |
sql/修复产品平均单价计算.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 修复产品平均单价计算 | |
| 3 | +-- ============================================ | |
| 4 | +-- 说明:重新计算所有产品的平均单价,基于当前所有有效库存 | |
| 5 | +-- 执行时间:2025年 | |
| 6 | +-- ============================================ | |
| 7 | + | |
| 8 | +-- 重新计算所有产品的平均单价(基于所有有效库存) | |
| 9 | +UPDATE `lq_product` p | |
| 10 | +SET p.`F_AveragePrice` = ( | |
| 11 | + SELECT | |
| 12 | + CASE | |
| 13 | + WHEN SUM(inv.`F_Quantity`) > 0 THEN | |
| 14 | + SUM( | |
| 15 | + CASE | |
| 16 | + WHEN inv.`F_FinalAmount` IS NOT NULL AND inv.`F_FinalAmount` > 0 THEN inv.`F_FinalAmount` | |
| 17 | + WHEN inv.`F_PurchaseUnitPrice` IS NOT NULL AND inv.`F_PurchaseUnitPrice` > 0 THEN inv.`F_PurchaseUnitPrice` * inv.`F_Quantity` | |
| 18 | + ELSE 0 | |
| 19 | + END | |
| 20 | + ) / SUM(inv.`F_Quantity`) | |
| 21 | + ELSE p.`F_Price` | |
| 22 | + END | |
| 23 | + FROM `lq_inventory` inv | |
| 24 | + WHERE inv.`F_ProductId` = p.`F_Id` | |
| 25 | + AND inv.`F_IsEffective` = 1 | |
| 26 | + AND inv.`F_Quantity` > 0 | |
| 27 | +) | |
| 28 | +WHERE EXISTS ( | |
| 29 | + SELECT 1 | |
| 30 | + FROM `lq_inventory` inv | |
| 31 | + WHERE inv.`F_ProductId` = p.`F_Id` | |
| 32 | + AND inv.`F_IsEffective` = 1 | |
| 33 | + AND inv.`F_Quantity` > 0 | |
| 34 | +); | |
| 35 | + | |
| 36 | +-- 对于没有库存的产品,将平均单价设置为产品价格 | |
| 37 | +UPDATE `lq_product` p | |
| 38 | +SET p.`F_AveragePrice` = p.`F_Price` | |
| 39 | +WHERE p.`F_AveragePrice` = 0 OR p.`F_AveragePrice` IS NULL | |
| 40 | + OR NOT EXISTS ( | |
| 41 | + SELECT 1 | |
| 42 | + FROM `lq_inventory` inv | |
| 43 | + WHERE inv.`F_ProductId` = p.`F_Id` | |
| 44 | + AND inv.`F_IsEffective` = 1 | |
| 45 | + AND inv.`F_Quantity` > 0 | |
| 46 | + ); | |
| 47 | + | |
| 48 | +-- 验证特定产品的平均单价(产品ID: 770163009904444677) | |
| 49 | +-- 应该显示:平均单价 = 200.00 (100+200+300)/3 = 600/3 = 200 | |
| 50 | +SELECT | |
| 51 | + p.`F_Id` as ProductId, | |
| 52 | + p.`F_ProductName` as ProductName, | |
| 53 | + p.`F_Price` as Price, | |
| 54 | + p.`F_AveragePrice` as AveragePrice, | |
| 55 | + SUM(inv.`F_Quantity`) as TotalQuantity, | |
| 56 | + SUM( | |
| 57 | + CASE | |
| 58 | + WHEN inv.`F_FinalAmount` IS NOT NULL AND inv.`F_FinalAmount` > 0 THEN inv.`F_FinalAmount` | |
| 59 | + WHEN inv.`F_PurchaseUnitPrice` IS NOT NULL AND inv.`F_PurchaseUnitPrice` > 0 THEN inv.`F_PurchaseUnitPrice` * inv.`F_Quantity` | |
| 60 | + ELSE 0 | |
| 61 | + END | |
| 62 | + ) as TotalAmount, | |
| 63 | + CASE | |
| 64 | + WHEN SUM(inv.`F_Quantity`) > 0 THEN | |
| 65 | + SUM( | |
| 66 | + CASE | |
| 67 | + WHEN inv.`F_FinalAmount` IS NOT NULL AND inv.`F_FinalAmount` > 0 THEN inv.`F_FinalAmount` | |
| 68 | + WHEN inv.`F_PurchaseUnitPrice` IS NOT NULL AND inv.`F_PurchaseUnitPrice` > 0 THEN inv.`F_PurchaseUnitPrice` * inv.`F_Quantity` | |
| 69 | + ELSE 0 | |
| 70 | + END | |
| 71 | + ) / SUM(inv.`F_Quantity`) | |
| 72 | + ELSE p.`F_Price` | |
| 73 | + END as CalculatedAveragePrice | |
| 74 | +FROM `lq_product` p | |
| 75 | +LEFT JOIN `lq_inventory` inv ON inv.`F_ProductId` = p.`F_Id` AND inv.`F_IsEffective` = 1 AND inv.`F_Quantity` > 0 | |
| 76 | +WHERE p.`F_Id` = '770163009904444677' | |
| 77 | +GROUP BY p.`F_Id`, p.`F_ProductName`, p.`F_Price`, p.`F_AveragePrice`; | ... | ... |
sql/创建大项目主管工资统计表.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 创建大项目主管工资统计表 | |
| 3 | +-- 功能:存储大项目主管每月的工资计算数据,包括底薪、业绩提成、扣款、补贴、奖金、支付等信息 | |
| 4 | +-- 创建时间:2025年 | |
| 5 | +-- ============================================ | |
| 6 | + | |
| 7 | +-- 删除表(如果存在) | |
| 8 | +DROP TABLE IF EXISTS lq_major_project_director_salary_statistics; | |
| 9 | + | |
| 10 | +-- ============================================ | |
| 11 | +-- 创建大项目主管工资统计表 | |
| 12 | +-- ============================================ | |
| 13 | +CREATE TABLE lq_major_project_director_salary_statistics ( | |
| 14 | + -- 主键 | |
| 15 | + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID', | |
| 16 | + | |
| 17 | + -- 一、基础信息字段 | |
| 18 | + F_StatisticsMonth VARCHAR(6) NOT NULL COMMENT '统计月份(YYYYMM格式)', | |
| 19 | + F_Position VARCHAR(50) NOT NULL COMMENT '核算岗位(大项目一部/大项目二部等)', | |
| 20 | + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名', | |
| 21 | + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID', | |
| 22 | + F_EmployeeAccount VARCHAR(100) NULL COMMENT '员工账号', | |
| 23 | + F_IsTerminated INT NOT NULL DEFAULT 0 COMMENT '是否离职(0=在职,1=离职)', | |
| 24 | + | |
| 25 | + -- 二、管理的门店信息(JSON格式) | |
| 26 | + F_StoreDetail TEXT NULL COMMENT '管理的门店明细(JSON格式,记录每个门店的业绩详情)', | |
| 27 | + | |
| 28 | + -- 三、业绩相关字段 | |
| 29 | + F_TotalPerformance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '总业绩(管理的所有门店的总业绩总和,开单-退卡)', | |
| 30 | + F_BillingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '开单金额(管理的所有门店的开单金额总和)', | |
| 31 | + F_RefundAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退卡金额(管理的所有门店的退卡金额总和)', | |
| 32 | + | |
| 33 | + -- 四、底薪相关字段 | |
| 34 | + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 3500.00 COMMENT '底薪金额(固定3500元)', | |
| 35 | + | |
| 36 | + -- 五、提成相关字段 | |
| 37 | + F_CommissionRate DECIMAL(18,4) DEFAULT NULL COMMENT '提成比例(根据总业绩分段:0%/1%/1.5%)', | |
| 38 | + F_CommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成金额(总业绩 × 提成比例)', | |
| 39 | + | |
| 40 | + -- 六、考勤相关字段 | |
| 41 | + F_WorkingDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '在店天数', | |
| 42 | + F_LeaveDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假天数', | |
| 43 | + | |
| 44 | + -- 七、工资计算字段 | |
| 45 | + F_CalculatedGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '核算应发工资(底薪 + 提成金额)', | |
| 46 | + F_FinalGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '最终应发工资(等于核算应发工资)', | |
| 47 | + | |
| 48 | + -- 八、补贴相关字段 | |
| 49 | + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴', | |
| 50 | + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴', | |
| 51 | + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴', | |
| 52 | + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴', | |
| 53 | + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计', | |
| 54 | + | |
| 55 | + -- 九、扣款相关字段 | |
| 56 | + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款', | |
| 57 | + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款', | |
| 58 | + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款', | |
| 59 | + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保', | |
| 60 | + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励', | |
| 61 | + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费', | |
| 62 | + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用', | |
| 63 | + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用', | |
| 64 | + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计', | |
| 65 | + | |
| 66 | + -- 十、奖金相关字段 | |
| 67 | + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金', | |
| 68 | + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金', | |
| 69 | + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金', | |
| 70 | + | |
| 71 | + -- 十一、支付相关字段 | |
| 72 | + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(最终应发工资 - 扣款合计 + 补贴合计 + 奖金)', | |
| 73 | + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)', | |
| 74 | + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额', | |
| 75 | + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额', | |
| 76 | + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月', | |
| 77 | + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额', | |
| 78 | + | |
| 79 | + -- 十二、系统字段 | |
| 80 | + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)', | |
| 81 | + F_CreateTime DATETIME NOT NULL COMMENT '创建时间', | |
| 82 | + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间', | |
| 83 | + F_CreateUser VARCHAR(50) NULL COMMENT '创建人', | |
| 84 | + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人', | |
| 85 | + | |
| 86 | + -- 主键约束 | |
| 87 | + PRIMARY KEY (F_Id), | |
| 88 | + | |
| 89 | + -- 唯一索引:确保同一员工同一月份只有一条记录 | |
| 90 | + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth), | |
| 91 | + | |
| 92 | + -- 普通索引 | |
| 93 | + KEY `idx_statistics_month` (F_StatisticsMonth), | |
| 94 | + KEY `idx_employee_id` (F_EmployeeId), | |
| 95 | + KEY `idx_employee_account` (F_EmployeeAccount), | |
| 96 | + KEY `idx_position` (F_Position), | |
| 97 | + KEY `idx_is_terminated` (F_IsTerminated), | |
| 98 | + KEY `idx_create_time` (F_CreateTime) | |
| 99 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='大项目主管工资统计表'; | |
| 100 | + | |
| 101 | +-- ============================================ | |
| 102 | +-- 表结构说明 | |
| 103 | +-- ============================================ | |
| 104 | +/* | |
| 105 | +表名:lq_major_project_director_salary_statistics(大项目主管工资统计表) | |
| 106 | + | |
| 107 | +功能说明: | |
| 108 | +1. 存储大项目主管每月的工资计算数据 | |
| 109 | +2. 包括底薪、业绩提成、扣款、补贴、奖金、支付等信息 | |
| 110 | +3. 支持按员工、月份查询 | |
| 111 | +4. 记录管理的门店汇总信息 | |
| 112 | + | |
| 113 | +主要字段说明: | |
| 114 | +- F_BaseSalary:底薪(固定3500元) | |
| 115 | +- F_TotalPerformance:总业绩(管理的所有门店的总业绩总和,开单-退卡) | |
| 116 | +- F_CommissionRate:提成比例(根据总业绩分段:0%/1%/1.5%) | |
| 117 | +- F_CommissionAmount:提成金额(总业绩 × 提成比例) | |
| 118 | +- F_StoreDetail:门店业绩明细(JSON格式,记录每个门店的业绩详情) | |
| 119 | + | |
| 120 | +数据来源: | |
| 121 | +- 大项目主管归属:BASE_USER 表(岗位为"主管",组织ID为大项目一部或大项目二部) | |
| 122 | +- 管理的门店:lq_md_target 表的 F_MajorProjectDepartment 字段(按月份筛选) | |
| 123 | +- 开单业绩:lq_kd_kdjlb 表的 sfyj 字段(按管理的门店统计) | |
| 124 | +- 退卡业绩:lq_hytk_hytk 表的 F_ActualRefundAmount 或 tkje 字段(按管理的门店统计) | |
| 125 | + | |
| 126 | +计算公式: | |
| 127 | +- 总业绩 = 管理的所有门店的开单金额 - 退卡金额 | |
| 128 | +- 提成计算逻辑: | |
| 129 | + 1. 总业绩 <= 50万:无提成(0%) | |
| 130 | + 2. 50万 < 总业绩 <= 70万:1%提成 | |
| 131 | + 3. 总业绩 > 70万:1.5%提成 | |
| 132 | +- 核算应发工资 = 底薪(3500) + 提成金额 | |
| 133 | +- 最终应发工资 = 核算应发工资 | |
| 134 | +- 实发工资 = 最终应发工资 - 扣款合计 + 补贴合计 + 奖金 | |
| 135 | + | |
| 136 | +门店业绩明细JSON格式示例: | |
| 137 | +[ | |
| 138 | + { | |
| 139 | + "storeId": "A001", | |
| 140 | + "storeName": "门店A", | |
| 141 | + "billingAmount": 160000.00, | |
| 142 | + "refundAmount": 10000.00, | |
| 143 | + "totalPerformance": 150000.00 | |
| 144 | + }, | |
| 145 | + { | |
| 146 | + "storeId": "B001", | |
| 147 | + "storeName": "门店B", | |
| 148 | + "billingAmount": 105000.00, | |
| 149 | + "refundAmount": 5000.00, | |
| 150 | + "totalPerformance": 100000.00 | |
| 151 | + } | |
| 152 | +] | |
| 153 | + | |
| 154 | +索引说明: | |
| 155 | +- 主键索引:F_Id | |
| 156 | +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录) | |
| 157 | +- 普通索引: | |
| 158 | + - F_StatisticsMonth:按月份查询 | |
| 159 | + - F_EmployeeId:按员工查询 | |
| 160 | + - F_EmployeeAccount:按员工账号查询 | |
| 161 | + - F_Position:按岗位查询 | |
| 162 | + - F_IsTerminated:按离职状态查询 | |
| 163 | + - F_CreateTime:按创建时间查询 | |
| 164 | + | |
| 165 | +数据校验要求: | |
| 166 | +1. 总业绩必须 >= 0(不能为负数) | |
| 167 | +2. 提成比例必须为 0、1 或 1.5(对应不同业绩区间) | |
| 168 | +3. 底薪固定为3500元,不允许修改 | |
| 169 | +*/ | |
| 170 | + | ... | ... |
sql/创建库存使用申请审批流程表.sql
sql/创建科技部总经理工资统计表.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 创建科技部总经理工资统计表 | |
| 3 | +-- 功能:存储科技部总经理每月的工资计算数据,包括底薪、溯源金额提成、Cell金额提成、扣款、补贴、奖金、支付等信息 | |
| 4 | +-- 创建时间:2025年 | |
| 5 | +-- ============================================ | |
| 6 | + | |
| 7 | +-- 删除表(如果存在) | |
| 8 | +DROP TABLE IF EXISTS lq_tech_general_manager_salary_statistics; | |
| 9 | + | |
| 10 | +-- ============================================ | |
| 11 | +-- 创建科技部总经理工资统计表 | |
| 12 | +-- ============================================ | |
| 13 | +CREATE TABLE lq_tech_general_manager_salary_statistics ( | |
| 14 | + -- 主键 | |
| 15 | + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID', | |
| 16 | + | |
| 17 | + -- 一、基础信息字段 | |
| 18 | + F_StatisticsMonth VARCHAR(6) NOT NULL COMMENT '统计月份(YYYYMM格式)', | |
| 19 | + F_Position VARCHAR(50) NOT NULL COMMENT '核算岗位(科技一部/科技二部等)', | |
| 20 | + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名', | |
| 21 | + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID', | |
| 22 | + F_EmployeeAccount VARCHAR(100) NULL COMMENT '员工账号', | |
| 23 | + F_IsTerminated INT NOT NULL DEFAULT 0 COMMENT '是否离职(0=在职,1=离职)', | |
| 24 | + | |
| 25 | + -- 二、管理的门店信息(JSON格式) | |
| 26 | + F_StoreDetail TEXT NULL COMMENT '管理的门店明细(JSON格式,记录每个门店的溯源金额和Cell金额详情)', | |
| 27 | + | |
| 28 | + -- 三、业绩相关字段 | |
| 29 | + F_TraceabilityAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '溯源金额(管理的所有门店的溯源金额总和,开单-退卡)', | |
| 30 | + F_CellAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT 'Cell金额(管理的所有门店的Cell金额总和,开单-退卡)', | |
| 31 | + | |
| 32 | + -- 四、底薪相关字段 | |
| 33 | + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 4000.00 COMMENT '底薪金额(固定4000元)', | |
| 34 | + | |
| 35 | + -- 五、提成相关字段 | |
| 36 | + F_TraceabilityCommissionRate DECIMAL(18,4) DEFAULT NULL COMMENT '溯源金额提成比例(分段计算,存储平均比例)', | |
| 37 | + F_TraceabilityCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '溯源金额提成金额', | |
| 38 | + F_CellCommissionRate DECIMAL(18,4) DEFAULT NULL COMMENT 'Cell金额提成比例(分段计算,存储平均比例)', | |
| 39 | + F_CellCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT 'Cell金额提成金额', | |
| 40 | + F_TotalCommission DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成合计(溯源提成+Cell提成)', | |
| 41 | + | |
| 42 | + -- 六、考勤相关字段 | |
| 43 | + F_WorkingDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '在店天数', | |
| 44 | + F_LeaveDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假天数', | |
| 45 | + | |
| 46 | + -- 七、工资计算字段 | |
| 47 | + F_CalculatedGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '核算应发工资(底薪 + 提成合计)', | |
| 48 | + F_FinalGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '最终应发工资(等于核算应发工资)', | |
| 49 | + | |
| 50 | + -- 八、补贴相关字段 | |
| 51 | + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴', | |
| 52 | + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴', | |
| 53 | + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴', | |
| 54 | + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴', | |
| 55 | + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计', | |
| 56 | + | |
| 57 | + -- 九、扣款相关字段 | |
| 58 | + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款', | |
| 59 | + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款', | |
| 60 | + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款', | |
| 61 | + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保', | |
| 62 | + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励', | |
| 63 | + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费', | |
| 64 | + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用', | |
| 65 | + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用', | |
| 66 | + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计', | |
| 67 | + | |
| 68 | + -- 十、奖金相关字段 | |
| 69 | + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金', | |
| 70 | + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金', | |
| 71 | + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金', | |
| 72 | + | |
| 73 | + -- 十一、支付相关字段 | |
| 74 | + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(最终应发工资 - 扣款合计 + 补贴合计 + 奖金)', | |
| 75 | + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)', | |
| 76 | + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额', | |
| 77 | + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额', | |
| 78 | + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月', | |
| 79 | + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额', | |
| 80 | + | |
| 81 | + -- 十二、系统字段 | |
| 82 | + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)', | |
| 83 | + F_CreateTime DATETIME NOT NULL COMMENT '创建时间', | |
| 84 | + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间', | |
| 85 | + F_CreateUser VARCHAR(50) NULL COMMENT '创建人', | |
| 86 | + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人', | |
| 87 | + | |
| 88 | + -- 主键约束 | |
| 89 | + PRIMARY KEY (F_Id), | |
| 90 | + | |
| 91 | + -- 唯一索引:确保同一员工同一月份只有一条记录 | |
| 92 | + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth), | |
| 93 | + | |
| 94 | + -- 普通索引 | |
| 95 | + KEY `idx_statistics_month` (F_StatisticsMonth), | |
| 96 | + KEY `idx_employee_id` (F_EmployeeId), | |
| 97 | + KEY `idx_employee_account` (F_EmployeeAccount), | |
| 98 | + KEY `idx_position` (F_Position), | |
| 99 | + KEY `idx_is_terminated` (F_IsTerminated), | |
| 100 | + KEY `idx_create_time` (F_CreateTime) | |
| 101 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='科技部总经理工资统计表'; | |
| 102 | + | |
| 103 | +-- ============================================ | |
| 104 | +-- 表结构说明 | |
| 105 | +-- ============================================ | |
| 106 | +/* | |
| 107 | +表名:lq_tech_general_manager_salary_statistics(科技部总经理工资统计表) | |
| 108 | + | |
| 109 | +功能说明: | |
| 110 | +1. 存储科技部总经理每月的工资计算数据 | |
| 111 | +2. 包括底薪、溯源金额提成、Cell金额提成、扣款、补贴、奖金、支付等信息 | |
| 112 | +3. 支持按员工、月份查询 | |
| 113 | +4. 记录管理的门店汇总信息 | |
| 114 | + | |
| 115 | +主要字段说明: | |
| 116 | +- F_BaseSalary:底薪(固定4000元) | |
| 117 | +- F_TraceabilityAmount:溯源金额(管理的所有门店的溯源金额总和,开单-退卡) | |
| 118 | +- F_CellAmount:Cell金额(管理的所有门店的Cell金额总和,开单-退卡) | |
| 119 | +- F_TraceabilityCommissionAmount:溯源金额提成金额 | |
| 120 | +- F_CellCommissionAmount:Cell金额提成金额 | |
| 121 | +- F_StoreDetail:管理的门店明细(JSON格式,记录每个门店的溯源金额和Cell金额详情) | |
| 122 | + | |
| 123 | +数据来源: | |
| 124 | +- 科技部总经理识别:BASE_USER 表(F_GW字段包含"科技一部"、"科技二部"等) | |
| 125 | +- 管理的门店归属:lq_md_general_manager_lifeline 表(通过F_GeneralManagerId和F_Month获取) | |
| 126 | +- 溯源金额:lq_kd_pxmx 表(F_BeautyType='溯源系统'或'溯源')和 lq_hytk_mx 表(退卡) | |
| 127 | +- Cell金额:lq_kd_pxmx 表(F_BeautyType='cell'或'Cell')和 lq_hytk_mx 表(退卡) | |
| 128 | + | |
| 129 | +计算公式: | |
| 130 | +- 溯源金额 = 管理的所有门店的溯源类型品项开单金额总和 - 退卡金额总和 | |
| 131 | +- Cell金额 = 管理的所有门店的Cell类型品项开单金额总和 - 退卡金额总和 | |
| 132 | +- 溯源金额提成计算(分段累进): | |
| 133 | + - < 200,000元:提成 = 溯源金额 × 1% | |
| 134 | + - 200,000-300,000元:提成 = 200,000 × 1% + (溯源金额 - 200,000) × 1.5% | |
| 135 | + - 300,000-500,000元:提成 = 200,000 × 1% + 100,000 × 1.5% + (溯源金额 - 300,000) × 2% | |
| 136 | + - ≥ 500,000元:提成 = 200,000 × 1% + 100,000 × 1.5% + 200,000 × 2% + (溯源金额 - 500,000) × 2.5% | |
| 137 | +- Cell金额提成计算(分段累进): | |
| 138 | + - < 50,000元:提成 = 0(无提成) | |
| 139 | + - 50,000-400,000元:提成 = (Cell金额 - 50,000) × 1% | |
| 140 | + - ≥ 400,000元:提成 = 350,000 × 1% + (Cell金额 - 400,000) × 1.5% | |
| 141 | +- 提成合计 = 溯源金额提成 + Cell金额提成 | |
| 142 | +- 核算应发工资 = 底薪(4000) + 提成合计 | |
| 143 | +- 最终应发工资 = 核算应发工资 | |
| 144 | +- 实发工资 = 最终应发工资 - 扣款合计 + 补贴合计 + 奖金 | |
| 145 | + | |
| 146 | +门店明细JSON格式示例: | |
| 147 | +[ | |
| 148 | + { | |
| 149 | + "storeId": "A001", | |
| 150 | + "storeName": "门店A", | |
| 151 | + "traceabilityBillingAmount": 160000.00, | |
| 152 | + "traceabilityRefundAmount": 10000.00, | |
| 153 | + "traceabilityAmount": 150000.00, | |
| 154 | + "cellBillingAmount": 85000.00, | |
| 155 | + "cellRefundAmount": 5000.00, | |
| 156 | + "cellAmount": 80000.00 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "storeId": "B001", | |
| 160 | + "storeName": "门店B", | |
| 161 | + "traceabilityBillingAmount": 105000.00, | |
| 162 | + "traceabilityRefundAmount": 5000.00, | |
| 163 | + "traceabilityAmount": 100000.00, | |
| 164 | + "cellBillingAmount": 125000.00, | |
| 165 | + "cellRefundAmount": 5000.00, | |
| 166 | + "cellAmount": 120000.00 | |
| 167 | + } | |
| 168 | +] | |
| 169 | + | |
| 170 | +JSON字段说明: | |
| 171 | +- storeId:门店ID | |
| 172 | +- storeName:门店名称 | |
| 173 | +- traceabilityBillingAmount:该门店的溯源类型品项开单金额 | |
| 174 | +- traceabilityRefundAmount:该门店的溯源类型品项退卡金额 | |
| 175 | +- traceabilityAmount:该门店的净溯源金额(开单-退卡) | |
| 176 | +- cellBillingAmount:该门店的Cell类型品项开单金额 | |
| 177 | +- cellRefundAmount:该门店的Cell类型品项退卡金额 | |
| 178 | +- cellAmount:该门店的净Cell金额(开单-退卡) | |
| 179 | + | |
| 180 | +索引说明: | |
| 181 | +- 主键索引:F_Id | |
| 182 | +- 唯一索引:F_EmployeeId + F_StatisticsMonth(确保同一员工同一月份只有一条记录) | |
| 183 | +- 普通索引: | |
| 184 | + - F_StatisticsMonth:按月份查询 | |
| 185 | + - F_EmployeeId:按员工查询 | |
| 186 | + - F_Position:按岗位查询(科技一部/科技二部等) | |
| 187 | + - F_CreateTime:按创建时间查询 | |
| 188 | + | |
| 189 | +数据校验要求: | |
| 190 | +1. 底薪固定为4000元 | |
| 191 | +2. 必须从lq_md_general_manager_lifeline表获取管理的门店 | |
| 192 | +3. 必须按管理的门店筛选,只统计归属范围内的门店 | |
| 193 | +4. 必须正确区分溯源和Cell类型(通过F_BeautyType字段) | |
| 194 | +5. 必须扣除退卡金额,确保数据准确性 | |
| 195 | +6. 提成必须按照分段累进方式计算 | |
| 196 | +7. 如果溯源金额或Cell金额为0或负数,对应提成为0 | |
| 197 | +*/ | |
| 198 | + | ... | ... |
sql/更新开单扣减信息表品项分类字段.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 更新开单扣减信息表(lq_kd_deductinfo)的品项分类字段 | |
| 3 | +-- ============================================ | |
| 4 | +-- 说明:此脚本用于更新 lq_kd_deductinfo 表的 F_ItemCategory 字段 | |
| 5 | +-- | |
| 6 | +-- 数据来源:从关联的项目资料表(lq_xmzl)的 qt2 字段获取品项分类 | |
| 7 | +-- | |
| 8 | +-- 关联关系: | |
| 9 | +-- - lq_kd_deductinfo.F_ItemId = lq_xmzl.F_Id | |
| 10 | +-- | |
| 11 | +-- 更新逻辑: | |
| 12 | +-- - 更新所有记录(不判断是否有效) | |
| 13 | +-- - 只更新关联的项目资料存在且qt2字段有值的记录 | |
| 14 | +-- - 从 lq_xmzl.qt2 字段获取品项分类(医美/科美/生美) | |
| 15 | +-- - 如果当前 F_ItemCategory 已有值但与 lq_xmzl.qt2 不一致,也会更新为最新值 | |
| 16 | + | |
| 17 | +-- ============================================ | |
| 18 | +-- 更新开单扣减信息表的品项分类字段 | |
| 19 | +-- ============================================ | |
| 20 | +UPDATE lq_kd_deductinfo deduct | |
| 21 | +INNER JOIN lq_xmzl xmzl ON deduct.F_ItemId = xmzl.F_Id | |
| 22 | +SET deduct.F_ItemCategory = xmzl.qt2 | |
| 23 | +WHERE xmzl.qt2 IS NOT NULL | |
| 24 | + AND xmzl.qt2 != '' | |
| 25 | + AND (deduct.F_ItemCategory IS NULL | |
| 26 | + OR deduct.F_ItemCategory = '' | |
| 27 | + OR deduct.F_ItemCategory != xmzl.qt2); | |
| 28 | + | |
| 29 | +-- ============================================ | |
| 30 | +-- 验证更新结果 | |
| 31 | +-- ============================================ | |
| 32 | +-- 查看更新后的统计信息 | |
| 33 | +-- SELECT | |
| 34 | +-- F_ItemCategory AS 品项分类, | |
| 35 | +-- COUNT(*) AS 记录数 | |
| 36 | +-- FROM lq_kd_deductinfo | |
| 37 | +-- GROUP BY F_ItemCategory | |
| 38 | +-- ORDER BY 记录数 DESC; | |
| 39 | + | |
| 40 | +-- 查看未更新的记录数(关联的项目资料不存在或qt2为空) | |
| 41 | +-- SELECT COUNT(*) AS 未更新记录数 | |
| 42 | +-- FROM lq_kd_deductinfo deduct | |
| 43 | +-- LEFT JOIN lq_xmzl xmzl ON deduct.F_ItemId = xmzl.F_Id | |
| 44 | +-- WHERE xmzl.F_Id IS NULL | |
| 45 | +-- OR xmzl.qt2 IS NULL | |
| 46 | +-- OR xmzl.qt2 = ''; | |
| 47 | + | |
| 48 | +-- 查看更新前后的对比(需要先备份数据) | |
| 49 | +-- SELECT | |
| 50 | +-- deduct.F_Id, | |
| 51 | +-- deduct.F_ItemId, | |
| 52 | +-- deduct.F_ItemName, | |
| 53 | +-- deduct.F_ItemCategory AS 更新前分类, | |
| 54 | +-- xmzl.qt2 AS 更新后分类 | |
| 55 | +-- FROM lq_kd_deductinfo deduct | |
| 56 | +-- INNER JOIN lq_xmzl xmzl ON deduct.F_ItemId = xmzl.F_Id | |
| 57 | +-- WHERE xmzl.qt2 IS NOT NULL | |
| 58 | +-- AND xmzl.qt2 != '' | |
| 59 | +-- AND (deduct.F_ItemCategory IS NULL | |
| 60 | +-- OR deduct.F_ItemCategory = '' | |
| 61 | +-- OR deduct.F_ItemCategory != xmzl.qt2) | |
| 62 | +-- LIMIT 100; | ... | ... |
sql/添加产品平均单价字段.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 添加产品平均单价字段 | |
| 3 | +-- ============================================ | |
| 4 | +-- 说明:为产品表添加平均单价字段,用于维护加权平均成本 | |
| 5 | +-- 执行时间:2025年 | |
| 6 | +-- ============================================ | |
| 7 | + | |
| 8 | +-- 1. 添加平均单价字段到产品表 | |
| 9 | +ALTER TABLE `lq_product` | |
| 10 | + ADD COLUMN `F_AveragePrice` decimal(18,2) DEFAULT 0.00 COMMENT '平均单价(加权平均成本,用于出库计价)' AFTER `F_Price`, | |
| 11 | + ADD INDEX `idx_average_price` (`F_AveragePrice`); | |
| 12 | + | |
| 13 | +-- 2. 初始化历史数据的平均单价 | |
| 14 | +-- 说明:对于已有库存的产品,根据当前库存计算初始平均单价 | |
| 15 | +-- 计算公式:平均单价 = SUM(库存金额) / SUM(库存数量) | |
| 16 | +-- 其中,库存金额优先使用 F_FinalAmount,其次使用 F_PurchaseUnitPrice * F_Quantity | |
| 17 | + | |
| 18 | +UPDATE `lq_product` p | |
| 19 | +SET p.`F_AveragePrice` = ( | |
| 20 | + SELECT | |
| 21 | + CASE | |
| 22 | + WHEN SUM(inv.`F_Quantity`) > 0 THEN | |
| 23 | + SUM( | |
| 24 | + CASE | |
| 25 | + WHEN inv.`F_FinalAmount` IS NOT NULL AND inv.`F_FinalAmount` > 0 THEN inv.`F_FinalAmount` | |
| 26 | + WHEN inv.`F_PurchaseUnitPrice` IS NOT NULL AND inv.`F_PurchaseUnitPrice` > 0 THEN inv.`F_PurchaseUnitPrice` * inv.`F_Quantity` | |
| 27 | + ELSE 0 | |
| 28 | + END | |
| 29 | + ) / SUM(inv.`F_Quantity`) | |
| 30 | + ELSE p.`F_Price` | |
| 31 | + END | |
| 32 | + FROM `lq_inventory` inv | |
| 33 | + WHERE inv.`F_ProductId` = p.`F_Id` | |
| 34 | + AND inv.`F_IsEffective` = 1 | |
| 35 | + AND inv.`F_Quantity` > 0 | |
| 36 | +) | |
| 37 | +WHERE EXISTS ( | |
| 38 | + SELECT 1 | |
| 39 | + FROM `lq_inventory` inv | |
| 40 | + WHERE inv.`F_ProductId` = p.`F_Id` | |
| 41 | + AND inv.`F_IsEffective` = 1 | |
| 42 | + AND inv.`F_Quantity` > 0 | |
| 43 | +); | |
| 44 | + | |
| 45 | +-- 3. 对于没有库存的产品,将平均单价设置为产品价格 | |
| 46 | +UPDATE `lq_product` p | |
| 47 | +SET p.`F_AveragePrice` = p.`F_Price` | |
| 48 | +WHERE p.`F_AveragePrice` = 0 OR p.`F_AveragePrice` IS NULL; | ... | ... |
主任工资毛利计算逻辑梳理.md
0 → 100644
| 1 | +# 主任工资毛利计算逻辑梳理 | |
| 2 | + | |
| 3 | +## 📋 概述 | |
| 4 | + | |
| 5 | +根据店长工资计算规则,主任工资也需要使用**毛利**来计算提成,而不是直接使用门店业绩(开单-退卡)。 | |
| 6 | + | |
| 7 | +## 💰 毛利计算公式 | |
| 8 | + | |
| 9 | +### 核心公式 | |
| 10 | + | |
| 11 | +``` | |
| 12 | +毛利 = 销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾 | |
| 13 | +``` | |
| 14 | + | |
| 15 | +其中: | |
| 16 | +- **销售业绩** = 开单业绩 - 退款业绩 | |
| 17 | +- **产品物料** = 仓库领用金额合计(注意11月特殊规则:11月工资算10月数据) | |
| 18 | +- **合作项目成本** = 合作成本表合计金额 | |
| 19 | +- **店内支出** = 店内支出表合计金额 | |
| 20 | +- **洗毛巾** = 送洗记录总费用(只统计送出的记录,F_FlowType = 0) | |
| 21 | + | |
| 22 | +--- | |
| 23 | + | |
| 24 | +## 📊 数据来源 | |
| 25 | + | |
| 26 | +### 1. 销售业绩 | |
| 27 | + | |
| 28 | +**计算公式**: | |
| 29 | +``` | |
| 30 | +销售业绩 = 开单业绩 - 退款业绩 | |
| 31 | +``` | |
| 32 | + | |
| 33 | +**数据来源**: | |
| 34 | + | |
| 35 | +1. **开单业绩**: | |
| 36 | + - 表:`lq_kd_kdjlb`(开单记录表) | |
| 37 | + - 字段:`sfyj`(实付业绩) | |
| 38 | + - 条件: | |
| 39 | + - `F_IsEffective = 1`(有效记录) | |
| 40 | + - `Djmd = @StoreId`(门店ID) | |
| 41 | + - `DATE_FORMAT(Kdrq, '%Y%m') = @Month`(月份,YYYYMM格式) | |
| 42 | + | |
| 43 | +2. **退款业绩**: | |
| 44 | + - 表:`lq_hytk_hytk`(退卡记录表) | |
| 45 | + - 字段:`F_ActualRefundAmount`(实际退款金额) | |
| 46 | + - 条件: | |
| 47 | + - `F_IsEffective = 1`(有效记录) | |
| 48 | + - `md = @StoreId`(门店ID) | |
| 49 | + - `DATE_FORMAT(tksj, '%Y%m') = @Month`(月份,YYYYMM格式) | |
| 50 | + | |
| 51 | +--- | |
| 52 | + | |
| 53 | +### 2. 产品物料 | |
| 54 | + | |
| 55 | +**计算公式**: | |
| 56 | +``` | |
| 57 | +产品物料 = 仓库领用金额合计 | |
| 58 | +``` | |
| 59 | + | |
| 60 | +**数据来源**: | |
| 61 | +- 表:`lq_inventory_usage`(库存使用记录表) | |
| 62 | +- 字段:`F_TotalAmount`(合计金额) | |
| 63 | +- 条件: | |
| 64 | + - `F_IsEffective = 1`(有效记录) | |
| 65 | + - `F_StoreId = @StoreId`(门店ID) | |
| 66 | + - **特殊规则**:核算11月工资时,算的是10月份的仓库领用 | |
| 67 | + - 如果计算月份是11月,则查询10月的数据 | |
| 68 | + - 其他月份正常查询当月数据 | |
| 69 | + | |
| 70 | +**SQL示例**: | |
| 71 | +```sql | |
| 72 | +-- 产品物料(特殊规则:11月工资算10月数据) | |
| 73 | +SET @QueryMonth = @Month; | |
| 74 | +IF @Month = '202411' THEN | |
| 75 | + SET @QueryMonth = '202410'; | |
| 76 | +END IF; | |
| 77 | + | |
| 78 | +SELECT COALESCE(SUM(F_TotalAmount), 0) as MaterialCost | |
| 79 | +FROM lq_inventory_usage | |
| 80 | +WHERE F_StoreId = @StoreId | |
| 81 | + AND DATE_FORMAT(F_UsageTime, '%Y%m') = @QueryMonth | |
| 82 | + AND F_IsEffective = 1 | |
| 83 | +``` | |
| 84 | + | |
| 85 | +--- | |
| 86 | + | |
| 87 | +### 3. 合作项目成本 | |
| 88 | + | |
| 89 | +**计算公式**: | |
| 90 | +``` | |
| 91 | +合作项目成本 = 合作成本表合计金额 | |
| 92 | +``` | |
| 93 | + | |
| 94 | +**数据来源**: | |
| 95 | +- 表:`lq_cooperation_cost`(合作成本表) | |
| 96 | +- 字段:`F_TotalAmount`(合计金额) | |
| 97 | +- 条件: | |
| 98 | + - `F_StoreId = @StoreId`(门店ID) | |
| 99 | + - `F_Year = @Year`(年份) | |
| 100 | + - `F_Month = @Month`(月份,YYYYMM格式) | |
| 101 | + - `F_IsEffective = 1`(有效记录) | |
| 102 | + | |
| 103 | +--- | |
| 104 | + | |
| 105 | +### 4. 店内支出 | |
| 106 | + | |
| 107 | +**计算公式**: | |
| 108 | +``` | |
| 109 | +店内支出 = 店内支出表合计金额 | |
| 110 | +``` | |
| 111 | + | |
| 112 | +**数据来源**: | |
| 113 | +- 表:`lq_store_expense`(店内支出表) | |
| 114 | +- 字段:`F_Amount`(金额) | |
| 115 | +- 条件: | |
| 116 | + - `F_StoreId = @StoreId`(门店ID) | |
| 117 | + - `DATE_FORMAT(F_ExpenseDate, '%Y%m') = @Month`(月份,YYYYMM格式) | |
| 118 | + - `F_IsEffective = 1`(有效记录) | |
| 119 | + | |
| 120 | +--- | |
| 121 | + | |
| 122 | +### 5. 洗毛巾费用 | |
| 123 | + | |
| 124 | +**计算公式**: | |
| 125 | +``` | |
| 126 | +洗毛巾 = 送洗记录总费用 | |
| 127 | +``` | |
| 128 | + | |
| 129 | +**数据来源**: | |
| 130 | +- 表:`lq_laundry_flow`(洗毛巾流水表) | |
| 131 | +- 字段:`F_TotalPrice`(总费用) | |
| 132 | +- 条件: | |
| 133 | + - `F_IsEffective = 1`(有效记录) | |
| 134 | + - `F_StoreId = @StoreId`(门店ID) | |
| 135 | + - `F_FlowType = 0`(只统计送出的记录) | |
| 136 | + - `DATE_FORMAT(F_CreateTime, '%Y%m') = @Month`(月份,YYYYMM格式) | |
| 137 | + | |
| 138 | +--- | |
| 139 | + | |
| 140 | +## 🔄 修改方案 | |
| 141 | + | |
| 142 | +### 1. 数据库字段调整 | |
| 143 | + | |
| 144 | +#### 需要新增的字段 | |
| 145 | + | |
| 146 | +在 `lq_director_salary_statistics` 表中新增以下字段: | |
| 147 | + | |
| 148 | +```sql | |
| 149 | +-- 销售业绩(开单业绩-退款业绩) | |
| 150 | +ALTER TABLE lq_director_salary_statistics | |
| 151 | +ADD COLUMN F_SalesPerformance DECIMAL(18,2) DEFAULT 0.00 COMMENT '销售业绩(开单业绩-退款业绩)' AFTER F_StoreRefundPerformance; | |
| 152 | + | |
| 153 | +-- 产品物料 | |
| 154 | +ALTER TABLE lq_director_salary_statistics | |
| 155 | +ADD COLUMN F_ProductMaterial DECIMAL(18,2) DEFAULT 0.00 COMMENT '产品物料(仓库领用金额)' AFTER F_SalesPerformance; | |
| 156 | + | |
| 157 | +-- 合作项目成本 | |
| 158 | +ALTER TABLE lq_director_salary_statistics | |
| 159 | +ADD COLUMN F_CooperationCost DECIMAL(18,2) DEFAULT 0.00 COMMENT '合作项目成本' AFTER F_ProductMaterial; | |
| 160 | + | |
| 161 | +-- 店内支出 | |
| 162 | +ALTER TABLE lq_director_salary_statistics | |
| 163 | +ADD COLUMN F_StoreExpense DECIMAL(18,2) DEFAULT 0.00 COMMENT '店内支出' AFTER F_CooperationCost; | |
| 164 | + | |
| 165 | +-- 洗毛巾费用 | |
| 166 | +ALTER TABLE lq_director_salary_statistics | |
| 167 | +ADD COLUMN F_LaundryCost DECIMAL(18,2) DEFAULT 0.00 COMMENT '洗毛巾费用' AFTER F_StoreExpense; | |
| 168 | + | |
| 169 | +-- 毛利 | |
| 170 | +ALTER TABLE lq_director_salary_statistics | |
| 171 | +ADD COLUMN F_GrossProfit DECIMAL(18,2) DEFAULT 0.00 COMMENT '毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾)' AFTER F_LaundryCost; | |
| 172 | +``` | |
| 173 | + | |
| 174 | +#### 字段说明调整 | |
| 175 | + | |
| 176 | +**F_StoreTotalPerformance** 字段的注释需要修改: | |
| 177 | +- **修改前**:门店总业绩(门店开单业绩-门店退卡业绩) | |
| 178 | +- **修改后**:门店总业绩(毛利,用于提成计算) | |
| 179 | + | |
| 180 | +**注意**:`F_StoreTotalPerformance` 字段的值应该存储**毛利**,而不是开单-退卡。 | |
| 181 | + | |
| 182 | +--- | |
| 183 | + | |
| 184 | +### 2. 代码逻辑调整 | |
| 185 | + | |
| 186 | +#### 计算流程 | |
| 187 | + | |
| 188 | +1. **计算销售业绩**(开单-退卡) | |
| 189 | + ```csharp | |
| 190 | + salary.SalesPerformance = billing - refund; | |
| 191 | + ``` | |
| 192 | + | |
| 193 | +2. **统计产品物料**(注意11月特殊规则) | |
| 194 | + ```csharp | |
| 195 | + var queryMonth = monthStr; | |
| 196 | + if (month == 11) | |
| 197 | + { | |
| 198 | + queryMonth = $"{year}10"; // 11月工资算10月数据 | |
| 199 | + } | |
| 200 | + salary.ProductMaterial = productMaterialDict.ContainsKey(storeId) ? productMaterialDict[storeId] : 0; | |
| 201 | + ``` | |
| 202 | + | |
| 203 | +3. **统计合作项目成本** | |
| 204 | + ```csharp | |
| 205 | + salary.CooperationCost = cooperationCostDict.ContainsKey(storeId) ? cooperationCostDict[storeId] : 0; | |
| 206 | + ``` | |
| 207 | + | |
| 208 | +4. **统计店内支出** | |
| 209 | + ```csharp | |
| 210 | + salary.StoreExpense = storeExpenseDict.ContainsKey(storeId) ? storeExpenseDict[storeId] : 0; | |
| 211 | + ``` | |
| 212 | + | |
| 213 | +5. **统计洗毛巾费用**(只统计送出的记录) | |
| 214 | + ```csharp | |
| 215 | + salary.LaundryCost = laundryCostDict.ContainsKey(storeId) ? laundryCostDict[storeId] : 0; | |
| 216 | + ``` | |
| 217 | + | |
| 218 | +6. **计算毛利** | |
| 219 | + ```csharp | |
| 220 | + salary.GrossProfit = salary.SalesPerformance - salary.ProductMaterial - salary.CooperationCost - salary.StoreExpense - salary.LaundryCost; | |
| 221 | + ``` | |
| 222 | + | |
| 223 | +7. **将毛利赋值给 F_StoreTotalPerformance**(用于提成计算) | |
| 224 | + ```csharp | |
| 225 | + salary.StoreTotalPerformance = salary.GrossProfit; | |
| 226 | + ``` | |
| 227 | + | |
| 228 | +8. **计算业绩完成率**(基于毛利与生命线比较) | |
| 229 | + ```csharp | |
| 230 | + if (salary.StoreLifeline > 0) | |
| 231 | + { | |
| 232 | + salary.PerformanceCompletionRate = salary.GrossProfit / salary.StoreLifeline; | |
| 233 | + } | |
| 234 | + ``` | |
| 235 | + | |
| 236 | +9. **判断业绩是否达标**(基于毛利) | |
| 237 | + ```csharp | |
| 238 | + bool performanceReached = salary.GrossProfit >= salary.StoreLifeline; | |
| 239 | + ``` | |
| 240 | + | |
| 241 | +10. **计算提成**(基于毛利,使用阶梯提成) | |
| 242 | + ```csharp | |
| 243 | + CalculateCommission(salary, isNewStore); // 提成计算基于毛利 | |
| 244 | + ``` | |
| 245 | + | |
| 246 | +--- | |
| 247 | + | |
| 248 | +### 3. 提成计算调整 | |
| 249 | + | |
| 250 | +**重要说明**:提成计算需要基于**毛利**,而不是销售业绩。 | |
| 251 | + | |
| 252 | +当前提成计算逻辑(阶梯提成): | |
| 253 | +- ≤生命线部分:根据门店分类使用不同比例(A类2%,B类2.5%,C类3%) | |
| 254 | +- >生命线部分:根据门店分类使用不同比例(A类2.5%,B类3%,C类3.5%) | |
| 255 | + | |
| 256 | +**修改后**: | |
| 257 | +- 提成计算基于**毛利**(`salary.GrossProfit`) | |
| 258 | +- 业绩完成率判断基于**毛利**与生命线比较 | |
| 259 | +- 业绩达标判断基于**毛利**是否≥生命线 | |
| 260 | + | |
| 261 | +--- | |
| 262 | + | |
| 263 | +## 📝 总结 | |
| 264 | + | |
| 265 | +### 关键变更点 | |
| 266 | + | |
| 267 | +1. **F_StoreTotalPerformance 字段含义变更**: | |
| 268 | + - 原来:开单业绩 - 退卡业绩 | |
| 269 | + - 现在:毛利(销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾) | |
| 270 | + | |
| 271 | +2. **需要新增字段**: | |
| 272 | + - `F_SalesPerformance`:销售业绩(开单-退卡) | |
| 273 | + - `F_ProductMaterial`:产品物料 | |
| 274 | + - `F_CooperationCost`:合作项目成本 | |
| 275 | + - `F_StoreExpense`:店内支出 | |
| 276 | + - `F_LaundryCost`:洗毛巾费用 | |
| 277 | + - `F_GrossProfit`:毛利 | |
| 278 | + | |
| 279 | +3. **计算逻辑调整**: | |
| 280 | + - 先计算销售业绩(开单-退卡) | |
| 281 | + - 统计各项成本(产品物料、合作项目成本、店内支出、洗毛巾) | |
| 282 | + - 计算毛利 | |
| 283 | + - 将毛利赋值给 `F_StoreTotalPerformance`(用于提成计算) | |
| 284 | + - 基于毛利计算业绩完成率和判断业绩是否达标 | |
| 285 | + - 基于毛利计算提成 | |
| 286 | + | |
| 287 | +4. **数据查询**: | |
| 288 | + - 需要查询 `lq_inventory_usage`(产品物料) | |
| 289 | + - 需要查询 `lq_cooperation_cost`(合作项目成本) | |
| 290 | + - 需要查询 `lq_store_expense`(店内支出) | |
| 291 | + - 需要查询 `lq_laundry_flow`(洗毛巾费用) | |
| 292 | + | |
| 293 | +--- | |
| 294 | + | |
| 295 | +## ⚠️ 注意事项 | |
| 296 | + | |
| 297 | +1. **11月特殊规则**:核算11月工资时,产品物料算的是10月份的数据 | |
| 298 | +2. **洗毛巾费用**:只统计送出的记录(`F_FlowType = 0`) | |
| 299 | +3. **业绩完成率**:基于毛利与生命线比较,不是基于销售业绩 | |
| 300 | +4. **业绩达标判断**:基于毛利是否≥生命线 | |
| 301 | +5. **提成计算**:基于毛利,不是基于销售业绩 | |
| 302 | + | ... | ... |
大项目主管工资计算规则梳理.md
0 → 100644
| 1 | +# 大项目主管工资计算规则梳理 | |
| 2 | + | |
| 3 | +## 📋 目录 | |
| 4 | +- [概述](#概述) | |
| 5 | +- [计算规则](#计算规则) | |
| 6 | +- [数据来源](#数据来源) | |
| 7 | +- [归属规则](#归属规则) | |
| 8 | +- [计算流程](#计算流程) | |
| 9 | +- [注意事项](#注意事项) | |
| 10 | + | |
| 11 | +--- | |
| 12 | + | |
| 13 | +## 📋 概述 | |
| 14 | + | |
| 15 | +大项目主管工资由以下几个部分组成: | |
| 16 | +1. **底薪**:固定3500元 | |
| 17 | +2. **业绩提成**:根据管理的所有门店的总业绩分段提成 | |
| 18 | + | |
| 19 | +**重要说明**: | |
| 20 | +- 底薪固定为3500元,不设档位,不设条件 | |
| 21 | +- 大项目主管从 `BASE_USER` 表获取,岗位字段(`F_GW`)为"主管",组织ID为大项目一部或大项目二部 | |
| 22 | +- 每个大项目主管管理的门店归属在 `lq_md_target` 表中(通过 `F_MajorProjectDepartment` 字段) | |
| 23 | +- 需要统计该大项目主管管理的**所有门店**的总业绩(开单-退卡) | |
| 24 | +- 提成采用分段方式计算(不是分段累进) | |
| 25 | + | |
| 26 | +--- | |
| 27 | + | |
| 28 | +## 💰 计算规则 | |
| 29 | + | |
| 30 | +### 1. 底薪规则 | |
| 31 | + | |
| 32 | +**固定底薪**:3500元 | |
| 33 | + | |
| 34 | +- 无论业绩多少,底薪固定为3500元 | |
| 35 | +- 不设档位,不设条件 | |
| 36 | +- 不设考核扣款 | |
| 37 | + | |
| 38 | +--- | |
| 39 | + | |
| 40 | +### 2. 业绩提成规则 | |
| 41 | + | |
| 42 | +**提成计算方式**:根据管理的所有门店的总业绩分段计算 | |
| 43 | + | |
| 44 | +| 总业绩范围 | 提成比例 | 说明 | | |
| 45 | +|-----------|---------|------| | |
| 46 | +| ≤ 50万 | 0% | 无提成 | | |
| 47 | +| > 50万 且 ≤ 70万 | 1% | 按1%计算提成 | | |
| 48 | +| > 70万 | 1.5% | 按1.5%计算提成 | | |
| 49 | + | |
| 50 | +**计算说明**: | |
| 51 | +- 提成金额 = 总业绩 × 对应提成比例 | |
| 52 | +- 采用分段方式计算,不同区间按不同比例计算 | |
| 53 | +- **注意**:不是分段累进,而是整个总业绩按对应比例计算 | |
| 54 | + | |
| 55 | +**示例**: | |
| 56 | +- 总业绩 = 40万 → 提成 = 0(无提成) | |
| 57 | +- 总业绩 = 60万 → 提成 = 60万 × 1% = 6000元 | |
| 58 | +- 总业绩 = 80万 → 提成 = 80万 × 1.5% = 12000元 | |
| 59 | + | |
| 60 | +--- | |
| 61 | + | |
| 62 | +## 📊 数据来源 | |
| 63 | + | |
| 64 | +### 大项目主管识别 | |
| 65 | + | |
| 66 | +**数据来源**:`BASE_USER` 表 | |
| 67 | + | |
| 68 | +**识别条件**: | |
| 69 | +- `F_GW`(岗位字段)为"主管" | |
| 70 | +- `F_OrganizeId`(组织ID)在大项目一部或大项目二部的组织ID列表中 | |
| 71 | +- `F_DeleteMark == null`(未删除) | |
| 72 | +- `F_EnabledMark == 1`(已启用) | |
| 73 | + | |
| 74 | +**获取步骤**: | |
| 75 | +1. 从 `BASE_ORGANIZE` 表查找组织名称包含"大项目一部"或"大项目二部"的组织 | |
| 76 | +2. 获取这些组织的ID列表 | |
| 77 | +3. 从 `BASE_USER` 表查询岗位为"主管"且组织ID在上述组织ID列表中的员工 | |
| 78 | + | |
| 79 | +**说明**: | |
| 80 | +- 目前有"大项目一部"和"大项目二部",未来可能还有更多大项目部 | |
| 81 | +- 岗位字段值必须是"主管"(完全匹配) | |
| 82 | + | |
| 83 | +--- | |
| 84 | + | |
| 85 | +### 管理的门店归属 | |
| 86 | + | |
| 87 | +**数据来源**:`lq_md_target` 表 | |
| 88 | + | |
| 89 | +**关联关系**: | |
| 90 | +- 通过 `F_MajorProjectDepartment` 字段关联到 `BASE_ORGANIZE.F_Id`(大项目一部或大项目二部的组织ID) | |
| 91 | +- 通过 `F_StoreId` 字段关联到 `lq_mdxx.F_Id` | |
| 92 | +- 通过 `F_Month` 字段(YYYYMM格式)关联到统计月份 | |
| 93 | + | |
| 94 | +**获取逻辑**: | |
| 95 | +1. 从 `lq_md_target` 表查询指定月份(`F_Month = @统计月份`)的记录 | |
| 96 | +2. 筛选出 `F_MajorProjectDepartment = @大项目一部组织ID` 或 `F_MajorProjectDepartment = @大项目二部组织ID` 的记录 | |
| 97 | +3. 获取这些记录的 `F_StoreId` 列表,即为该大项目主管管理的门店 | |
| 98 | + | |
| 99 | +**重要说明**: | |
| 100 | +- 每个大项目主管可能管理多个门店 | |
| 101 | +- 需要统计这些门店的总业绩(开单-退卡)总和 | |
| 102 | +- 如果某个门店在 `lq_md_target` 表中没有记录,则该门店的业绩不计入该大项目主管的统计 | |
| 103 | + | |
| 104 | +--- | |
| 105 | + | |
| 106 | +### 总业绩统计 | |
| 107 | + | |
| 108 | +**定义**:该大项目主管管理的所有门店中,开单金额总和减去退卡金额总和 | |
| 109 | + | |
| 110 | +**数据来源表及字段**: | |
| 111 | + | |
| 112 | +| 业绩类型 | 数据表 | 字段 | 说明 | | |
| 113 | +|---------|--------|------|------| | |
| 114 | +| **开单金额** | `lq_kd_kdjlb` | `sfyj` | 门店开单实付金额 | | |
| 115 | +| **退卡金额** | `lq_hytk_hytk` | `F_ActualRefundAmount` 或 `tkje` | 门店退卡金额 | | |
| 116 | + | |
| 117 | +**统计逻辑**: | |
| 118 | + | |
| 119 | +1. **统计开单金额**(按管理的门店筛选): | |
| 120 | + ```sql | |
| 121 | + SELECT COALESCE(SUM(billing.sfyj), 0) as BillingAmount | |
| 122 | + FROM lq_kd_kdjlb billing | |
| 123 | + WHERE billing.F_IsEffective = 1 | |
| 124 | + AND billing.djmd IN (@管理的门店ID列表) | |
| 125 | + AND DATE_FORMAT(billing.kdrq, '%Y%m') = @统计月份 | |
| 126 | + ``` | |
| 127 | + | |
| 128 | +2. **统计退卡金额**(按管理的门店筛选): | |
| 129 | + ```sql | |
| 130 | + SELECT COALESCE(SUM(refund.F_ActualRefundAmount), 0) as RefundAmount | |
| 131 | + FROM lq_hytk_hytk refund | |
| 132 | + WHERE refund.F_IsEffective = 1 | |
| 133 | + AND refund.djmd IN (@管理的门店ID列表) | |
| 134 | + AND DATE_FORMAT(refund.tkrq, '%Y%m') = @统计月份 | |
| 135 | + ``` | |
| 136 | + | |
| 137 | +3. **计算净总业绩**: | |
| 138 | + - 净总业绩 = 开单金额 - 退卡金额 | |
| 139 | + | |
| 140 | +--- | |
| 141 | + | |
| 142 | +## 🔗 归属规则 | |
| 143 | + | |
| 144 | +### 大项目主管与门店的归属关系 | |
| 145 | + | |
| 146 | +**数据来源**:`lq_md_target` 表 | |
| 147 | + | |
| 148 | +**表结构说明**: | |
| 149 | +- `F_Id`:主键ID | |
| 150 | +- `F_StoreId`:门店ID(关联 `lq_mdxx.F_Id`) | |
| 151 | +- `F_Month`:月份(YYYYMM格式) | |
| 152 | +- `F_MajorProjectDepartment`:归属大项目部(关联 `BASE_ORGANIZE.F_Id`) | |
| 153 | + | |
| 154 | +**获取管理的门店**: | |
| 155 | +1. 从 `lq_md_target` 表查询: | |
| 156 | + ```sql | |
| 157 | + SELECT DISTINCT F_StoreId | |
| 158 | + FROM lq_md_target | |
| 159 | + WHERE F_MajorProjectDepartment = @大项目一部或大项目二部组织ID | |
| 160 | + AND F_Month = @统计月份 | |
| 161 | + ``` | |
| 162 | + | |
| 163 | +2. 如果该大项目主管在指定月份没有管理的门店,则总业绩为0,提成为0 | |
| 164 | + | |
| 165 | +**重要说明**: | |
| 166 | +- 每个大项目主管可能管理多个门店 | |
| 167 | +- 需要统计这些门店的总业绩(开单-退卡)总和 | |
| 168 | +- 如果某个门店在 `lq_md_target` 表中没有记录,则该门店的业绩不计入该大项目主管的统计 | |
| 169 | + | |
| 170 | +--- | |
| 171 | + | |
| 172 | +## 🔄 计算流程 | |
| 173 | + | |
| 174 | +### 步骤1:识别大项目主管 | |
| 175 | + | |
| 176 | +1. 从 `BASE_ORGANIZE` 表中筛选: | |
| 177 | + - `F_FullName LIKE '%大项目一部%'` 或 `F_FullName LIKE '%大项目二部%'` | |
| 178 | + - `F_DeleteMark == null`(未删除) | |
| 179 | + - `F_EnabledMark == 1`(已启用) | |
| 180 | + | |
| 181 | +2. 获取这些组织的ID列表 | |
| 182 | + | |
| 183 | +3. 从 `BASE_USER` 表中筛选: | |
| 184 | + - `F_GW == "主管"`(岗位为"主管") | |
| 185 | + - `F_OrganizeId` 在上述组织ID列表中 | |
| 186 | + - `F_DeleteMark == null`(未删除) | |
| 187 | + - `F_EnabledMark == 1`(已启用) | |
| 188 | + | |
| 189 | +--- | |
| 190 | + | |
| 191 | +### 步骤2:获取管理的门店 | |
| 192 | + | |
| 193 | +1. 从 `lq_md_target` 表查询指定月份(`F_Month = @统计月份`)的记录 | |
| 194 | +2. 筛选出 `F_MajorProjectDepartment = @大项目一部组织ID` 或 `F_MajorProjectDepartment = @大项目二部组织ID` 的记录 | |
| 195 | +3. 获取这些记录的 `F_StoreId` 列表,即为该大项目主管管理的门店 | |
| 196 | + | |
| 197 | +--- | |
| 198 | + | |
| 199 | +### 步骤3:统计总业绩 | |
| 200 | + | |
| 201 | +1. **统计开单金额**: | |
| 202 | + - 从 `lq_kd_kdjlb` 表统计管理的门店在统计月份的开单金额(`sfyj`字段) | |
| 203 | + - 过滤条件:`F_IsEffective = 1`,`djmd IN (@管理的门店ID列表)`,`DATE_FORMAT(kdrq, '%Y%m') = @统计月份` | |
| 204 | + | |
| 205 | +2. **统计退卡金额**: | |
| 206 | + - 从 `lq_hytk_hytk` 表统计管理的门店在统计月份的退卡金额(`F_ActualRefundAmount` 或 `tkje`字段) | |
| 207 | + - 过滤条件:`F_IsEffective = 1`,`djmd IN (@管理的门店ID列表)`,`DATE_FORMAT(tkrq, '%Y%m') = @统计月份` | |
| 208 | + | |
| 209 | +3. **计算净总业绩**: | |
| 210 | + - 净总业绩 = 开单金额 - 退卡金额 | |
| 211 | + | |
| 212 | +--- | |
| 213 | + | |
| 214 | +### 步骤4:计算提成 | |
| 215 | + | |
| 216 | +根据总业绩分段计算提成: | |
| 217 | + | |
| 218 | +- 如果总业绩 ≤ 50万:提成 = 0(无提成) | |
| 219 | +- 如果 50万 < 总业绩 ≤ 70万:提成 = 总业绩 × 1% | |
| 220 | +- 如果总业绩 > 70万:提成 = 总业绩 × 1.5% | |
| 221 | + | |
| 222 | +--- | |
| 223 | + | |
| 224 | +### 步骤5:计算应发工资 | |
| 225 | + | |
| 226 | +- 应发工资 = 底薪(3500元)+ 提成金额 | |
| 227 | + | |
| 228 | +--- | |
| 229 | + | |
| 230 | +### 步骤6:保存数据 | |
| 231 | + | |
| 232 | +将计算结果保存到 `lq_major_project_director_salary_statistics` 表中: | |
| 233 | +- 如果已存在当月数据,则更新;否则插入新数据 | |
| 234 | +- 保存门店明细(JSON格式) | |
| 235 | + | |
| 236 | +--- | |
| 237 | + | |
| 238 | +## ✅ 总结 | |
| 239 | + | |
| 240 | +大项目主管工资计算规则相对简单明确: | |
| 241 | +1. **底薪固定**:3500元,无任何条件 | |
| 242 | +2. **业绩提成**:根据管理的所有门店的总业绩分段计算,最高1.5%,低于50万无提成 | |
| 243 | + | |
| 244 | +关键点: | |
| 245 | +- 必须从 `BASE_USER` 表识别大项目主管(岗位为"主管",组织ID为大项目一部或大项目二部) | |
| 246 | +- 必须从 `lq_md_target` 表获取管理的门店(通过 `F_MajorProjectDepartment` 字段) | |
| 247 | +- 必须正确统计总业绩(开单金额 - 退卡金额) | |
| 248 | +- 必须按管理的门店筛选,只统计该大项目主管管理的门店 | |
| 249 | +- 采用分段方式计算提成(不是分段累进),整个总业绩按对应比例计算 | |
| 250 | + | ... | ... |
库存平均单价计算逻辑说明.md
0 → 100644
| 1 | +# 库存平均单价计算逻辑说明 | |
| 2 | + | |
| 3 | +## 概述 | |
| 4 | + | |
| 5 | +采用**加权平均成本法(Weighted Average Cost Method)**来计算库存的平均单价,用于出库计价。 | |
| 6 | + | |
| 7 | +## 核心原则 | |
| 8 | + | |
| 9 | +1. **入库时**:根据当前平均单价和入库单价,计算新的平均单价 | |
| 10 | +2. **出库时**:使用当前平均单价,出库后平均单价不变 | |
| 11 | +3. **平均单价维护**:存储在 `lq_product.F_AveragePrice` 字段中 | |
| 12 | + | |
| 13 | +## 计算公式 | |
| 14 | + | |
| 15 | +### 入库时计算新平均单价 | |
| 16 | + | |
| 17 | +``` | |
| 18 | +新平均单价 = (当前平均单价 × 当前可用库存数量 + 入库单价 × 入库数量) / (当前可用库存数量 + 入库数量) | |
| 19 | +``` | |
| 20 | + | |
| 21 | +其中: | |
| 22 | +- **当前可用库存数量** = 总库存数量 - 已领取数量 | |
| 23 | +- **入库单价**: | |
| 24 | + - 采购入库:使用采购单价(`F_PurchaseUnitPrice`) | |
| 25 | + - 普通入库:使用产品价格(`F_Price`) | |
| 26 | + | |
| 27 | +### 出库时使用平均单价 | |
| 28 | + | |
| 29 | +``` | |
| 30 | +出库单价 = 产品平均单价(F_AveragePrice) | |
| 31 | +出库总金额 = 出库单价 × 出库数量 | |
| 32 | +``` | |
| 33 | + | |
| 34 | +**注意**:出库后,平均单价不变(因为已经领取的库存已经按照当时的平均单价计价了) | |
| 35 | + | |
| 36 | +## 示例说明 | |
| 37 | + | |
| 38 | +### 示例1:基本流程 | |
| 39 | + | |
| 40 | +**初始状态**: | |
| 41 | +- 商品A:当前平均单价 = 150 | |
| 42 | +- 库存A:数量1,单价100 | |
| 43 | +- 库存B:数量1,单价200 | |
| 44 | + | |
| 45 | +**步骤1:领取1个** | |
| 46 | +- 使用平均单价:150 | |
| 47 | +- 领取后剩余:1个库存 | |
| 48 | +- 平均单价不变:150 | |
| 49 | + | |
| 50 | +**步骤2:再次入库1个,单价50** | |
| 51 | +- 当前可用库存数量:1 | |
| 52 | +- 当前库存总金额:150 × 1 = 150 | |
| 53 | +- 入库金额:50 × 1 = 50 | |
| 54 | +- **新平均单价** = (150 + 50) / (1 + 1) = **100** | |
| 55 | + | |
| 56 | +### 示例2:多次入库 | |
| 57 | + | |
| 58 | +**初始状态**: | |
| 59 | +- 商品B:当前平均单价 = 100,当前可用库存 = 10 | |
| 60 | + | |
| 61 | +**步骤1:采购入库5个,单价120** | |
| 62 | +- 当前库存总金额:100 × 10 = 1000 | |
| 63 | +- 入库金额:120 × 5 = 600 | |
| 64 | +- **新平均单价** = (1000 + 600) / (10 + 5) = **106.67** | |
| 65 | + | |
| 66 | +**步骤2:领取3个** | |
| 67 | +- 使用平均单价:106.67 | |
| 68 | +- 领取后剩余:12个库存 | |
| 69 | +- 平均单价不变:106.67 | |
| 70 | + | |
| 71 | +**步骤3:再次采购入库8个,单价110** | |
| 72 | +- 当前可用库存数量:12 | |
| 73 | +- 当前库存总金额:106.67 × 12 = 1280.04 | |
| 74 | +- 入库金额:110 × 8 = 880 | |
| 75 | +- **新平均单价** = (1280.04 + 880) / (12 + 8) = **108.00** | |
| 76 | + | |
| 77 | +## 数据库字段 | |
| 78 | + | |
| 79 | +### 产品表(lq_product) | |
| 80 | + | |
| 81 | +- `F_AveragePrice`:平均单价(加权平均成本,用于出库计价) | |
| 82 | + | |
| 83 | +### 库存表(lq_inventory) | |
| 84 | + | |
| 85 | +- `F_PurchaseUnitPrice`:采购单价(采购入库时使用) | |
| 86 | +- `F_FinalAmount`:产品最终金额(优先用于计算单价) | |
| 87 | +- `F_Quantity`:库存数量 | |
| 88 | + | |
| 89 | +### 库存使用记录表(lq_inventory_usage) | |
| 90 | + | |
| 91 | +- `F_UnitPrice`:使用时的单价(从产品平均单价获取) | |
| 92 | +- `F_TotalAmount`:使用总金额(单价 × 数量) | |
| 93 | + | |
| 94 | +## 实现逻辑 | |
| 95 | + | |
| 96 | +### 1. 入库时(LqInventoryService.CreateAsync / UpdateAsync) | |
| 97 | + | |
| 98 | +```csharp | |
| 99 | +// 计算当前可用库存数量 | |
| 100 | +var currentAvailableQuantity = totalInventoryQuantity - totalUsageQuantity; | |
| 101 | + | |
| 102 | +// 确定入库单价 | |
| 103 | +decimal incomingPrice = stockInType == 2 && purchaseUnitPrice.HasValue | |
| 104 | + ? purchaseUnitPrice.Value // 采购入库 | |
| 105 | + : product.Price; // 普通入库 | |
| 106 | + | |
| 107 | +// 计算新平均单价 | |
| 108 | +if (currentAvailableQuantity <= 0) | |
| 109 | +{ | |
| 110 | + newAveragePrice = incomingPrice; // 没有库存时,直接使用入库单价 | |
| 111 | +} | |
| 112 | +else | |
| 113 | +{ | |
| 114 | + var currentTotalAmount = product.AveragePrice * currentAvailableQuantity; | |
| 115 | + var incomingAmount = incomingPrice * incomingQuantity; | |
| 116 | + newAveragePrice = (currentTotalAmount + incomingAmount) / (currentAvailableQuantity + incomingQuantity); | |
| 117 | +} | |
| 118 | + | |
| 119 | +// 更新产品平均单价 | |
| 120 | +product.AveragePrice = newAveragePrice; | |
| 121 | +``` | |
| 122 | + | |
| 123 | +### 2. 出库时(LqInventoryUsageService.CreateAsync / ConfirmReceiveAsync) | |
| 124 | + | |
| 125 | +```csharp | |
| 126 | +// 使用产品的平均单价 | |
| 127 | +var unitPrice = product.AveragePrice > 0 ? product.AveragePrice : product.Price; | |
| 128 | +var totalAmount = unitPrice * usageQuantity; | |
| 129 | + | |
| 130 | +// 创建使用记录(平均单价不变) | |
| 131 | +usageEntity.UnitPrice = unitPrice; | |
| 132 | +usageEntity.TotalAmount = totalAmount; | |
| 133 | +``` | |
| 134 | + | |
| 135 | +## 初始化历史数据 | |
| 136 | + | |
| 137 | +对于已有库存的产品,根据当前库存计算初始平均单价: | |
| 138 | + | |
| 139 | +```sql | |
| 140 | +平均单价 = SUM(库存金额) / SUM(库存数量) | |
| 141 | +``` | |
| 142 | + | |
| 143 | +其中,库存金额优先使用 `F_FinalAmount`,其次使用 `F_PurchaseUnitPrice × F_Quantity`。 | |
| 144 | + | |
| 145 | +## 注意事项 | |
| 146 | + | |
| 147 | +1. **平均单价维护**:每次入库时自动更新,出库时不变 | |
| 148 | +2. **可用库存计算**:需要考虑已领取的数量 | |
| 149 | +3. **普通入库**:如果没有采购单价,使用产品价格作为入库单价 | |
| 150 | +4. **历史数据初始化**:首次添加字段时,需要根据现有库存计算初始平均单价 | |
| 151 | +5. **数据一致性**:确保入库和出库操作在事务中执行,保证平均单价的一致性 | ... | ... |
科技部总经理工资计算规则梳理.md
0 → 100644
| 1 | +# 科技部总经理工资计算规则梳理 | |
| 2 | + | |
| 3 | +## 📋 目录 | |
| 4 | +- [概述](#概述) | |
| 5 | +- [计算规则](#计算规则) | |
| 6 | +- [数据来源](#数据来源) | |
| 7 | +- [归属规则](#归属规则) | |
| 8 | +- [计算流程](#计算流程) | |
| 9 | +- [注意事项](#注意事项) | |
| 10 | + | |
| 11 | +--- | |
| 12 | + | |
| 13 | +## 📋 概述 | |
| 14 | + | |
| 15 | +科技部总经理工资由以下几个部分组成: | |
| 16 | +1. **底薪**:固定4000元 | |
| 17 | +2. **溯源金额提成**:根据管理的所有门店的溯源金额总和分段提成 | |
| 18 | +3. **Cell金额提成**:根据管理的所有门店的Cell金额总和分段提成 | |
| 19 | + | |
| 20 | +**重要说明**: | |
| 21 | +- 底薪固定为4000元,不设档位,不设条件 | |
| 22 | +- 科技部总经理从 `BASE_USER` 表获取,岗位字段(`F_GW`)为"科技一部"、"科技二部"等(未来可能还有更多) | |
| 23 | +- 每个科技部总经理管理的门店归属在 `lq_md_general_manager_lifeline` 表中 | |
| 24 | +- 需要统计该科技部总经理管理的**所有门店**的溯源金额和Cell金额总和 | |
| 25 | +- 溯源金额和Cell金额分别计算提成,互不影响 | |
| 26 | +- 提成采用分段累进方式计算 | |
| 27 | + | |
| 28 | +--- | |
| 29 | + | |
| 30 | +## 💰 计算规则 | |
| 31 | + | |
| 32 | +### 1. 底薪规则 | |
| 33 | + | |
| 34 | +**固定底薪**:4000元 | |
| 35 | + | |
| 36 | +- 无论业绩多少,底薪固定为4000元 | |
| 37 | +- 不设档位,不设条件 | |
| 38 | +- 不设考核扣款 | |
| 39 | + | |
| 40 | +--- | |
| 41 | + | |
| 42 | +### 2. 溯源金额提成规则 | |
| 43 | + | |
| 44 | +**提成计算方式**:根据管理的所有门店的溯源金额总和分段累进计算 | |
| 45 | + | |
| 46 | +| 溯源金额范围 | 提成比例 | | |
| 47 | +|------------|---------| | |
| 48 | +| < 200,000元 | 1% | | |
| 49 | +| 200,000元 - 300,000元 | 1.5% | | |
| 50 | +| 300,000元 - 500,000元 | 2% | | |
| 51 | +| ≥ 500,000元 | 2.5% | | |
| 52 | + | |
| 53 | +**计算说明**: | |
| 54 | +- 提成金额 = 溯源金额 × 对应提成比例 | |
| 55 | +- 采用分段累进方式计算,不同区间按不同比例计算 | |
| 56 | +- 例如:溯源金额为350,000元 | |
| 57 | + - 0-200,000元部分:200,000 × 1% = 2,000元 | |
| 58 | + - 200,000-300,000元部分:100,000 × 1.5% = 1,500元 | |
| 59 | + - 300,000-350,000元部分:50,000 × 2% = 1,000元 | |
| 60 | + - 总提成 = 2,000 + 1,500 + 1,000 = 4,500元 | |
| 61 | + | |
| 62 | +--- | |
| 63 | + | |
| 64 | +### 3. Cell金额提成规则 | |
| 65 | + | |
| 66 | +**提成计算方式**:根据管理的所有门店的Cell金额总和分段累进计算 | |
| 67 | + | |
| 68 | +| Cell金额范围 | 提成比例 | | |
| 69 | +|------------|---------| | |
| 70 | +| < 50,000元 | 0% (无提成) | | |
| 71 | +| 50,000元 - 400,000元 | 1% | | |
| 72 | +| ≥ 400,000元 | 1.5% | | |
| 73 | + | |
| 74 | +**计算说明**: | |
| 75 | +- 如果Cell金额 < 50,000元,无提成 | |
| 76 | +- 如果Cell金额 ≥ 50,000元,按分段累进方式计算 | |
| 77 | +- 例如:Cell金额为450,000元 | |
| 78 | + - 0-50,000元部分:无提成 | |
| 79 | + - 50,000-400,000元部分:350,000 × 1% = 3,500元 | |
| 80 | + - 400,000-450,000元部分:50,000 × 1.5% = 750元 | |
| 81 | + - 总提成 = 3,500 + 750 = 4,250元 | |
| 82 | + | |
| 83 | +--- | |
| 84 | + | |
| 85 | +## 📊 数据来源 | |
| 86 | + | |
| 87 | +### 科技部总经理识别 | |
| 88 | + | |
| 89 | +**数据来源**:`BASE_USER` 表 | |
| 90 | + | |
| 91 | +**识别条件**: | |
| 92 | +- `F_GW`(岗位字段)包含"科技一部"、"科技二部"等(如:`F_GW LIKE '%科技一部%'` 或 `F_GW LIKE '%科技二部%'`) | |
| 93 | +- `F_DeleteMark == null`(未删除) | |
| 94 | +- `F_EnabledMark == 1`(已启用) | |
| 95 | + | |
| 96 | +**说明**: | |
| 97 | +- 目前有"科技一部"和"科技二部",未来可能还有更多科技部 | |
| 98 | +- 岗位字段值可能是"科技一部"、"科技二部"等完整名称 | |
| 99 | + | |
| 100 | +--- | |
| 101 | + | |
| 102 | +### 管理的门店归属 | |
| 103 | + | |
| 104 | +**数据来源**:`lq_md_general_manager_lifeline` 表 | |
| 105 | + | |
| 106 | +**关联关系**: | |
| 107 | +- 通过 `F_GeneralManagerId` 字段关联到 `BASE_USER.F_Id` | |
| 108 | +- 通过 `F_StoreId` 字段关联到 `lq_mdxx.F_Id` | |
| 109 | +- 通过 `F_Month` 字段(YYYYMM格式)关联到统计月份 | |
| 110 | + | |
| 111 | +**获取逻辑**: | |
| 112 | +1. 从 `lq_md_general_manager_lifeline` 表查询指定月份(`F_Month = @统计月份`)的记录 | |
| 113 | +2. 筛选出 `F_GeneralManagerId = @科技部总经理ID` 的记录 | |
| 114 | +3. 获取这些记录的 `F_StoreId` 列表,即为该科技部总经理管理的门店 | |
| 115 | + | |
| 116 | +**重要说明**: | |
| 117 | +- 每个科技部总经理可能管理多个门店 | |
| 118 | +- 需要统计这些门店的溯源金额和Cell金额总和 | |
| 119 | + | |
| 120 | +--- | |
| 121 | + | |
| 122 | +### 溯源金额统计 | |
| 123 | + | |
| 124 | +**定义**:该科技部总经理管理的所有门店中,品项的 `F_BeautyType` 为 "溯源系统" 或 "溯源" 的品项明细的实付金额总和(开单金额 - 退卡金额) | |
| 125 | + | |
| 126 | +**数据来源表及字段**: | |
| 127 | + | |
| 128 | +| 数据表 | 字段 | 说明 | | |
| 129 | +|--------|------|------| | |
| 130 | +| `lq_kd_pxmx` | `F_ActualPrice` | 品项明细实付金额 | | |
| 131 | +| `lq_kd_pxmx` | `F_BeautyType` | 科美类型(用于区分溯源和Cell) | | |
| 132 | +| `lq_kd_kdjlb` | `kdrq` | 开单日期(用于按月统计) | | |
| 133 | +| `lq_kd_kdjlb` | `djmd` | 单据门店ID(用于筛选管理的门店) | | |
| 134 | +| `lq_xmzl` | `F_BeautyType` | 品项的科美类型(如果明细表没有,从品项表获取) | | |
| 135 | +| `lq_hytk_mx` | `tkje` | 退卡明细退款金额 | | |
| 136 | +| `lq_hytk_hytk` | `tkrq` | 退卡日期(用于按月统计) | | |
| 137 | +| `lq_hytk_hytk` | `djmd` | 单据门店ID(用于筛选管理的门店) | | |
| 138 | + | |
| 139 | +**统计逻辑**: | |
| 140 | + | |
| 141 | +1. **统计开单溯源金额**(按管理的门店筛选): | |
| 142 | + ```sql | |
| 143 | + SELECT COALESCE(SUM(pxmx.F_ActualPrice), 0) as TraceabilityAmount | |
| 144 | + FROM lq_kd_pxmx pxmx | |
| 145 | + INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id | |
| 146 | + INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id | |
| 147 | + WHERE pxmx.F_IsEffective = 1 | |
| 148 | + AND billing.F_IsEffective = 1 | |
| 149 | + AND item.F_IsEffective = 1 | |
| 150 | + AND (pxmx.F_BeautyType = '溯源系统' OR pxmx.F_BeautyType = '溯源' | |
| 151 | + OR item.F_BeautyType = '溯源系统' OR item.F_BeautyType = '溯源') | |
| 152 | + AND billing.djmd IN (@管理的门店ID列表) | |
| 153 | + AND DATE_FORMAT(billing.kdrq, '%Y%m') = @统计月份 | |
| 154 | + ``` | |
| 155 | + | |
| 156 | +2. **统计退卡溯源金额**(按管理的门店筛选): | |
| 157 | + ```sql | |
| 158 | + SELECT COALESCE(SUM(tkmx.tkje), 0) as RefundTraceabilityAmount | |
| 159 | + FROM lq_hytk_mx tkmx | |
| 160 | + INNER JOIN lq_hytk_hytk refund ON tkmx.glhytkbh = refund.F_Id | |
| 161 | + INNER JOIN lq_xmzl item ON tkmx.px = item.F_Id | |
| 162 | + WHERE tkmx.F_IsEffective = 1 | |
| 163 | + AND refund.F_IsEffective = 1 | |
| 164 | + AND item.F_IsEffective = 1 | |
| 165 | + AND (tkmx.F_BeautyType = '溯源系统' OR tkmx.F_BeautyType = '溯源' | |
| 166 | + OR item.F_BeautyType = '溯源系统' OR item.F_BeautyType = '溯源') | |
| 167 | + AND refund.djmd IN (@管理的门店ID列表) | |
| 168 | + AND DATE_FORMAT(refund.tkrq, '%Y%m') = @统计月份 | |
| 169 | + ``` | |
| 170 | + | |
| 171 | +3. **计算净溯源金额**: | |
| 172 | + - 净溯源金额 = 开单溯源金额 - 退卡溯源金额 | |
| 173 | + | |
| 174 | +--- | |
| 175 | + | |
| 176 | +### Cell金额统计 | |
| 177 | + | |
| 178 | +**定义**:该科技部总经理管理的所有门店中,品项的 `F_BeautyType` 为 "cell" 或 "Cell" 的品项明细的实付金额总和(开单金额 - 退卡金额) | |
| 179 | + | |
| 180 | +**数据来源表及字段**:同溯源金额统计 | |
| 181 | + | |
| 182 | +**统计逻辑**: | |
| 183 | + | |
| 184 | +1. **统计开单Cell金额**(按管理的门店筛选): | |
| 185 | + ```sql | |
| 186 | + SELECT COALESCE(SUM(pxmx.F_ActualPrice), 0) as CellAmount | |
| 187 | + FROM lq_kd_pxmx pxmx | |
| 188 | + INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id | |
| 189 | + INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id | |
| 190 | + WHERE pxmx.F_IsEffective = 1 | |
| 191 | + AND billing.F_IsEffective = 1 | |
| 192 | + AND item.F_IsEffective = 1 | |
| 193 | + AND (pxmx.F_BeautyType = 'cell' OR pxmx.F_BeautyType = 'Cell' | |
| 194 | + OR item.F_BeautyType = 'cell' OR item.F_BeautyType = 'Cell') | |
| 195 | + AND billing.djmd IN (@管理的门店ID列表) | |
| 196 | + AND DATE_FORMAT(billing.kdrq, '%Y%m') = @统计月份 | |
| 197 | + ``` | |
| 198 | + | |
| 199 | +2. **统计退卡Cell金额**(按管理的门店筛选): | |
| 200 | + ```sql | |
| 201 | + SELECT COALESCE(SUM(tkmx.tkje), 0) as RefundCellAmount | |
| 202 | + FROM lq_hytk_mx tkmx | |
| 203 | + INNER JOIN lq_hytk_hytk refund ON tkmx.glhytkbh = refund.F_Id | |
| 204 | + INNER JOIN lq_xmzl item ON tkmx.px = item.F_Id | |
| 205 | + WHERE tkmx.F_IsEffective = 1 | |
| 206 | + AND refund.F_IsEffective = 1 | |
| 207 | + AND item.F_IsEffective = 1 | |
| 208 | + AND (tkmx.F_BeautyType = 'cell' OR tkmx.F_BeautyType = 'Cell' | |
| 209 | + OR item.F_BeautyType = 'cell' OR item.F_BeautyType = 'Cell') | |
| 210 | + AND refund.djmd IN (@管理的门店ID列表) | |
| 211 | + AND DATE_FORMAT(refund.tkrq, '%Y%m') = @统计月份 | |
| 212 | + ``` | |
| 213 | + | |
| 214 | +3. **计算净Cell金额**: | |
| 215 | + - 净Cell金额 = 开单Cell金额 - 退卡Cell金额 | |
| 216 | + | |
| 217 | +--- | |
| 218 | + | |
| 219 | +## 🔗 归属规则 | |
| 220 | + | |
| 221 | +### 科技部总经理与门店的归属关系 | |
| 222 | + | |
| 223 | +**数据来源**:`lq_md_general_manager_lifeline` 表 | |
| 224 | + | |
| 225 | +**表结构说明**: | |
| 226 | +- `F_Id`:主键ID | |
| 227 | +- `F_StoreId`:门店ID(关联 `lq_mdxx.F_Id`) | |
| 228 | +- `F_Month`:月份(YYYYMM格式) | |
| 229 | +- `F_GeneralManagerId`:总经理用户ID(关联 `BASE_USER.F_Id`) | |
| 230 | +- `F_ManagerType`:经理类型(0=经理,1=总经理) | |
| 231 | + | |
| 232 | +**获取管理的门店**: | |
| 233 | +1. 从 `lq_md_general_manager_lifeline` 表查询: | |
| 234 | + ```sql | |
| 235 | + SELECT DISTINCT F_StoreId | |
| 236 | + FROM lq_md_general_manager_lifeline | |
| 237 | + WHERE F_GeneralManagerId = @科技部总经理ID | |
| 238 | + AND F_Month = @统计月份 | |
| 239 | + ``` | |
| 240 | + | |
| 241 | +2. 如果该科技部总经理在指定月份没有管理的门店,则溯源金额和Cell金额为0,提成为0 | |
| 242 | + | |
| 243 | +**重要说明**: | |
| 244 | +- 每个科技部总经理可能管理多个门店 | |
| 245 | +- 需要统计这些门店的溯源金额和Cell金额总和 | |
| 246 | +- 如果某个门店在 `lq_md_general_manager_lifeline` 表中没有记录,则该门店的业绩不计入该科技部总经理的统计 | |
| 247 | + | |
| 248 | +--- | |
| 249 | + | |
| 250 | +## 🔄 计算流程 | |
| 251 | + | |
| 252 | +### 步骤1:识别科技部总经理 | |
| 253 | + | |
| 254 | +从 `BASE_USER` 表中筛选: | |
| 255 | +- `F_GW LIKE '%科技一部%'` 或 `F_GW LIKE '%科技二部%'` 等(根据实际岗位名称) | |
| 256 | +- `F_DeleteMark == null`(未删除) | |
| 257 | +- `F_EnabledMark == 1`(已启用) | |
| 258 | + | |
| 259 | +**注意**:未来可能还有"科技三部"等,需要灵活处理岗位识别逻辑 | |
| 260 | + | |
| 261 | +--- | |
| 262 | + | |
| 263 | +### 步骤2:获取管理的门店 | |
| 264 | + | |
| 265 | +1. 从 `lq_md_general_manager_lifeline` 表查询该科技部总经理在指定月份管理的门店: | |
| 266 | + ```sql | |
| 267 | + SELECT DISTINCT F_StoreId | |
| 268 | + FROM lq_md_general_manager_lifeline | |
| 269 | + WHERE F_GeneralManagerId = @科技部总经理ID | |
| 270 | + AND F_Month = @统计月份 | |
| 271 | + ``` | |
| 272 | + | |
| 273 | +2. 如果查询结果为空,说明该科技部总经理在该月份没有管理的门店,溯源金额和Cell金额为0,提成为0 | |
| 274 | + | |
| 275 | +--- | |
| 276 | + | |
| 277 | +### 步骤3:统计溯源金额(所有管理的门店总和) | |
| 278 | + | |
| 279 | +1. **统计开单溯源金额**(按管理的门店筛选): | |
| 280 | + - 从 `lq_kd_pxmx` 表统计 | |
| 281 | + - 关联 `lq_kd_kdjlb` 表获取开单日期和门店ID | |
| 282 | + - 关联 `lq_xmzl` 表获取品项的 `F_BeautyType` | |
| 283 | + - 筛选条件: | |
| 284 | + - `lq_kd_pxmx.F_IsEffective = 1`(有效记录) | |
| 285 | + - `lq_kd_kdjlb.F_IsEffective = 1`(有效开单) | |
| 286 | + - `lq_xmzl.F_IsEffective = 1`(有效品项) | |
| 287 | + - `F_BeautyType = '溯源系统'` 或 `'溯源'` | |
| 288 | + - `lq_kd_kdjlb.djmd IN (@管理的门店ID列表)` | |
| 289 | + - 开单日期在统计月份范围内 | |
| 290 | + - 汇总:`SUM(lq_kd_pxmx.F_ActualPrice)` | |
| 291 | + | |
| 292 | +2. **统计退卡溯源金额**(按管理的门店筛选): | |
| 293 | + - 从 `lq_hytk_mx` 表统计 | |
| 294 | + - 关联 `lq_hytk_hytk` 表获取退卡日期和门店ID | |
| 295 | + - 关联 `lq_xmzl` 表获取品项的 `F_BeautyType` | |
| 296 | + - 筛选条件: | |
| 297 | + - `lq_hytk_mx.F_IsEffective = 1`(有效记录) | |
| 298 | + - `lq_hytk_hytk.F_IsEffective = 1`(有效退卡) | |
| 299 | + - `lq_xmzl.F_IsEffective = 1`(有效品项) | |
| 300 | + - `F_BeautyType = '溯源系统'` 或 `'溯源'` | |
| 301 | + - `lq_hytk_hytk.djmd IN (@管理的门店ID列表)` | |
| 302 | + - 退卡日期在统计月份范围内 | |
| 303 | + - 汇总:`SUM(lq_hytk_mx.tkje)` | |
| 304 | + | |
| 305 | +3. **计算净溯源金额**: | |
| 306 | + - 净溯源金额 = 开单溯源金额 - 退卡溯源金额 | |
| 307 | + | |
| 308 | +--- | |
| 309 | + | |
| 310 | +### 步骤4:统计Cell金额(所有管理的门店总和) | |
| 311 | + | |
| 312 | +1. **统计开单Cell金额**(按管理的门店筛选): | |
| 313 | + - 从 `lq_kd_pxmx` 表统计 | |
| 314 | + - 关联 `lq_kd_kdjlb` 表获取开单日期和门店ID | |
| 315 | + - 关联 `lq_xmzl` 表获取品项的 `F_BeautyType` | |
| 316 | + - 筛选条件: | |
| 317 | + - `lq_kd_pxmx.F_IsEffective = 1`(有效记录) | |
| 318 | + - `lq_kd_kdjlb.F_IsEffective = 1`(有效开单) | |
| 319 | + - `lq_xmzl.F_IsEffective = 1`(有效品项) | |
| 320 | + - `F_BeautyType = 'cell'` 或 `'Cell'` | |
| 321 | + - `lq_kd_kdjlb.djmd IN (@管理的门店ID列表)` | |
| 322 | + - 开单日期在统计月份范围内 | |
| 323 | + - 汇总:`SUM(lq_kd_pxmx.F_ActualPrice)` | |
| 324 | + | |
| 325 | +2. **统计退卡Cell金额**(按管理的门店筛选): | |
| 326 | + - 从 `lq_hytk_mx` 表统计 | |
| 327 | + - 关联 `lq_hytk_hytk` 表获取退卡日期和门店ID | |
| 328 | + - 关联 `lq_xmzl` 表获取品项的 `F_BeautyType` | |
| 329 | + - 筛选条件: | |
| 330 | + - `lq_hytk_mx.F_IsEffective = 1`(有效记录) | |
| 331 | + - `lq_hytk_hytk.F_IsEffective = 1`(有效退卡) | |
| 332 | + - `lq_xmzl.F_IsEffective = 1`(有效品项) | |
| 333 | + - `F_BeautyType = 'cell'` 或 `'Cell'` | |
| 334 | + - `lq_hytk_hytk.djmd IN (@管理的门店ID列表)` | |
| 335 | + - 退卡日期在统计月份范围内 | |
| 336 | + - 汇总:`SUM(lq_hytk_mx.tkje)` | |
| 337 | + | |
| 338 | +3. **计算净Cell金额**: | |
| 339 | + - 净Cell金额 = 开单Cell金额 - 退卡Cell金额 | |
| 340 | + | |
| 341 | +--- | |
| 342 | + | |
| 343 | +### 步骤5:工资计算 | |
| 344 | + | |
| 345 | +#### 5.1 计算底薪 | |
| 346 | +- 底薪 = 4000元(固定) | |
| 347 | + | |
| 348 | +#### 5.2 计算溯源金额提成 | |
| 349 | + | |
| 350 | +根据净溯源金额范围确定提成比例,采用分段累进方式: | |
| 351 | + | |
| 352 | +- **如果净溯源金额 < 200,000元**: | |
| 353 | + - 提成金额 = 净溯源金额 × 1% | |
| 354 | + | |
| 355 | +- **如果 200,000元 ≤ 净溯源金额 < 300,000元**: | |
| 356 | + - 0-200,000元部分:200,000 × 1% = 2,000元 | |
| 357 | + - 200,000元以上部分:(净溯源金额 - 200,000) × 1.5% | |
| 358 | + - 总提成 = 2,000 + (净溯源金额 - 200,000) × 1.5% | |
| 359 | + | |
| 360 | +- **如果 300,000元 ≤ 净溯源金额 < 500,000元**: | |
| 361 | + - 0-200,000元部分:200,000 × 1% = 2,000元 | |
| 362 | + - 200,000-300,000元部分:100,000 × 1.5% = 1,500元 | |
| 363 | + - 300,000元以上部分:(净溯源金额 - 300,000) × 2% | |
| 364 | + - 总提成 = 2,000 + 1,500 + (净溯源金额 - 300,000) × 2% | |
| 365 | + | |
| 366 | +- **如果净溯源金额 ≥ 500,000元**: | |
| 367 | + - 0-200,000元部分:200,000 × 1% = 2,000元 | |
| 368 | + - 200,000-300,000元部分:100,000 × 1.5% = 1,500元 | |
| 369 | + - 300,000-500,000元部分:200,000 × 2% = 4,000元 | |
| 370 | + - 500,000元以上部分:(净溯源金额 - 500,000) × 2.5% | |
| 371 | + - 总提成 = 2,000 + 1,500 + 4,000 + (净溯源金额 - 500,000) × 2.5% | |
| 372 | + | |
| 373 | +#### 5.3 计算Cell金额提成 | |
| 374 | + | |
| 375 | +根据净Cell金额范围确定提成比例,采用分段累进方式: | |
| 376 | + | |
| 377 | +- **如果净Cell金额 < 50,000元**: | |
| 378 | + - 提成金额 = 0(无提成) | |
| 379 | + | |
| 380 | +- **如果 50,000元 ≤ 净Cell金额 < 400,000元**: | |
| 381 | + - 0-50,000元部分:无提成 | |
| 382 | + - 50,000元以上部分:(净Cell金额 - 50,000) × 1% | |
| 383 | + - 总提成 = (净Cell金额 - 50,000) × 1% | |
| 384 | + | |
| 385 | +- **如果净Cell金额 ≥ 400,000元**: | |
| 386 | + - 0-50,000元部分:无提成 | |
| 387 | + - 50,000-400,000元部分:350,000 × 1% = 3,500元 | |
| 388 | + - 400,000元以上部分:(净Cell金额 - 400,000) × 1.5% | |
| 389 | + - 总提成 = 3,500 + (净Cell金额 - 400,000) × 1.5% | |
| 390 | + | |
| 391 | +#### 5.4 计算最终工资 | |
| 392 | + | |
| 393 | +- **应发工资** = 底薪 + 溯源金额提成 + Cell金额提成 | |
| 394 | + | |
| 395 | +--- | |
| 396 | + | |
| 397 | +## 📝 注意事项 | |
| 398 | + | |
| 399 | +1. **数据一致性**: | |
| 400 | + - 溯源金额和Cell金额的统计逻辑必须与其他统计接口保持一致 | |
| 401 | + - 必须扣除退卡金额,确保数据准确性 | |
| 402 | + - 必须按管理的门店筛选,只统计该科技部总经理管理的门店 | |
| 403 | + | |
| 404 | +2. **数据校验**: | |
| 405 | + - `F_BeautyType` 字段值可能不统一("溯源系统"、"溯源"、"cell"、"Cell"),需要兼容处理 | |
| 406 | + - 优先使用明细表的 `F_BeautyType`,如果为空则使用品项表的 `F_BeautyType` | |
| 407 | + - 如果科技部总经理在指定月份没有管理的门店,溯源金额和Cell金额为0,提成为0 | |
| 408 | + | |
| 409 | +3. **归属关系**: | |
| 410 | + - 必须从 `lq_md_general_manager_lifeline` 表获取管理的门店 | |
| 411 | + - 如果某个门店不在该科技部总经理的管理范围内,该门店的业绩不计入统计 | |
| 412 | + - 需要确保 `lq_md_general_manager_lifeline` 表的数据准确性 | |
| 413 | + | |
| 414 | +4. **边界情况**: | |
| 415 | + - 如果溯源金额或Cell金额为0或负数,对应提成为0 | |
| 416 | + - 如果溯源金额或Cell金额计算后为负数(退卡金额大于开单金额),对应提成为0 | |
| 417 | + - 如果科技部总经理在指定月份没有管理的门店,所有金额为0,提成为0 | |
| 418 | + | |
| 419 | +5. **计算精度**: | |
| 420 | + - 涉及金额计算时,建议保留2位小数 | |
| 421 | + - 提成金额四舍五入到分 | |
| 422 | + | |
| 423 | +6. **性能优化**: | |
| 424 | + - 建议使用数据库聚合查询,避免在应用层进行大量数据计算 | |
| 425 | + - 可以先获取管理的门店列表,然后在SQL中使用 `IN` 子句筛选 | |
| 426 | + - 可以考虑创建统计视图或物化视图,提高查询性能 | |
| 427 | + | |
| 428 | +7. **岗位识别**: | |
| 429 | + - 目前有"科技一部"和"科技二部",未来可能还有更多 | |
| 430 | + - 建议使用模糊匹配(`LIKE '%科技一部%'`)或维护一个岗位列表 | |
| 431 | + - 需要确保岗位识别逻辑能够适应未来的扩展 | |
| 432 | + | |
| 433 | +--- | |
| 434 | + | |
| 435 | +## 📊 计算示例 | |
| 436 | + | |
| 437 | +### 示例1:基础计算 | |
| 438 | + | |
| 439 | +**假设数据**: | |
| 440 | +- 科技部总经理:科技一部(ID: 123456) | |
| 441 | +- 管理的门店:门店A(ID: A001)、门店B(ID: B001) | |
| 442 | +- 底薪:4000元 | |
| 443 | +- 门店A溯源金额:150,000元,Cell金额:80,000元 | |
| 444 | +- 门店B溯源金额:100,000元,Cell金额:120,000元 | |
| 445 | +- 总溯源金额:250,000元 | |
| 446 | +- 总Cell金额:200,000元 | |
| 447 | + | |
| 448 | +**计算过程**: | |
| 449 | + | |
| 450 | +1. **溯源金额提成**: | |
| 451 | + - 0-200,000元部分:200,000 × 1% = 2,000元 | |
| 452 | + - 200,000-250,000元部分:50,000 × 1.5% = 750元 | |
| 453 | + - 溯源金额提成 = 2,000 + 750 = 2,750元 | |
| 454 | + | |
| 455 | +2. **Cell金额提成**: | |
| 456 | + - 0-50,000元部分:无提成 | |
| 457 | + - 50,000-200,000元部分:150,000 × 1% = 1,500元 | |
| 458 | + - Cell金额提成 = 1,500元 | |
| 459 | + | |
| 460 | +3. **应发工资**: | |
| 461 | + - 应发工资 = 4,000 + 2,750 + 1,500 = 8,250元 | |
| 462 | + | |
| 463 | +--- | |
| 464 | + | |
| 465 | +### 示例2:高业绩计算 | |
| 466 | + | |
| 467 | +**假设数据**: | |
| 468 | +- 科技部总经理:科技二部(ID: 789012) | |
| 469 | +- 管理的门店:门店C(ID: C001)、门店D(ID: D001)、门店E(ID: E001) | |
| 470 | +- 底薪:4000元 | |
| 471 | +- 门店C溯源金额:200,000元,Cell金额:150,000元 | |
| 472 | +- 门店D溯源金额:250,000元,Cell金额:200,000元 | |
| 473 | +- 门店E溯源金额:150,000元,Cell金额:100,000元 | |
| 474 | +- 总溯源金额:600,000元 | |
| 475 | +- 总Cell金额:450,000元 | |
| 476 | + | |
| 477 | +**计算过程**: | |
| 478 | + | |
| 479 | +1. **溯源金额提成**: | |
| 480 | + - 0-200,000元部分:200,000 × 1% = 2,000元 | |
| 481 | + - 200,000-300,000元部分:100,000 × 1.5% = 1,500元 | |
| 482 | + - 300,000-500,000元部分:200,000 × 2% = 4,000元 | |
| 483 | + - 500,000-600,000元部分:100,000 × 2.5% = 2,500元 | |
| 484 | + - 溯源金额提成 = 2,000 + 1,500 + 4,000 + 2,500 = 10,000元 | |
| 485 | + | |
| 486 | +2. **Cell金额提成**: | |
| 487 | + - 0-50,000元部分:无提成 | |
| 488 | + - 50,000-400,000元部分:350,000 × 1% = 3,500元 | |
| 489 | + - 400,000-450,000元部分:50,000 × 1.5% = 750元 | |
| 490 | + - Cell金额提成 = 3,500 + 750 = 4,250元 | |
| 491 | + | |
| 492 | +3. **应发工资**: | |
| 493 | + - 应发工资 = 4,000 + 10,000 + 4,250 = 18,250元 | |
| 494 | + | |
| 495 | +--- | |
| 496 | + | |
| 497 | +## 🔍 数据表结构参考 | |
| 498 | + | |
| 499 | +### 科技部总经理工资统计表 | |
| 500 | + | |
| 501 | +参考其他岗位的工资表结构(如:`lq_business_unit_manager_salary_statistics`、`lq_tech_teacher_salary_statistics`),建议创建如下表结构: | |
| 502 | + | |
| 503 | +```sql | |
| 504 | +-- ============================================ | |
| 505 | +-- 创建科技部总经理工资统计表 | |
| 506 | +-- 功能:存储科技部总经理每月的工资计算数据,包括底薪、溯源金额提成、Cell金额提成、扣款、补贴、奖金、支付等信息 | |
| 507 | +-- 创建时间:2025年 | |
| 508 | +-- ============================================ | |
| 509 | + | |
| 510 | +DROP TABLE IF EXISTS lq_tech_general_manager_salary_statistics; | |
| 511 | + | |
| 512 | +CREATE TABLE lq_tech_general_manager_salary_statistics ( | |
| 513 | + -- 主键 | |
| 514 | + F_Id VARCHAR(50) NOT NULL COMMENT '主键ID', | |
| 515 | + | |
| 516 | + -- 一、基础信息字段 | |
| 517 | + F_StatisticsMonth VARCHAR(6) NOT NULL COMMENT '统计月份(YYYYMM格式)', | |
| 518 | + F_Position VARCHAR(50) NOT NULL COMMENT '核算岗位(科技一部/科技二部等)', | |
| 519 | + F_EmployeeName VARCHAR(100) NOT NULL COMMENT '员工姓名', | |
| 520 | + F_EmployeeId VARCHAR(50) NOT NULL COMMENT '员工ID', | |
| 521 | + F_EmployeeAccount VARCHAR(100) NULL COMMENT '员工账号', | |
| 522 | + F_IsTerminated INT NOT NULL DEFAULT 0 COMMENT '是否离职(0=在职,1=离职)', | |
| 523 | + | |
| 524 | + -- 二、管理的门店信息(JSON格式) | |
| 525 | + F_StoreDetail TEXT NULL COMMENT '管理的门店明细(JSON格式,记录每个门店的溯源金额和Cell金额详情)', | |
| 526 | + | |
| 527 | + -- 三、业绩相关字段 | |
| 528 | + F_TraceabilityAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '溯源金额(管理的所有门店的溯源金额总和,开单-退卡)', | |
| 529 | + F_CellAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT 'Cell金额(管理的所有门店的Cell金额总和,开单-退卡)', | |
| 530 | + | |
| 531 | + -- 四、底薪相关字段 | |
| 532 | + F_BaseSalary DECIMAL(18,2) NOT NULL DEFAULT 4000.00 COMMENT '底薪金额(固定4000元)', | |
| 533 | + | |
| 534 | + -- 五、提成相关字段 | |
| 535 | + F_TraceabilityCommissionRate DECIMAL(18,4) DEFAULT NULL COMMENT '溯源金额提成比例(分段计算,存储平均比例)', | |
| 536 | + F_TraceabilityCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '溯源金额提成金额', | |
| 537 | + F_CellCommissionRate DECIMAL(18,4) DEFAULT NULL COMMENT 'Cell金额提成比例(分段计算,存储平均比例)', | |
| 538 | + F_CellCommissionAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT 'Cell金额提成金额', | |
| 539 | + F_TotalCommission DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '提成合计(溯源提成+Cell提成)', | |
| 540 | + | |
| 541 | + -- 六、考勤相关字段 | |
| 542 | + F_WorkingDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '在店天数', | |
| 543 | + F_LeaveDays DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假天数', | |
| 544 | + | |
| 545 | + -- 七、工资计算字段 | |
| 546 | + F_CalculatedGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '核算应发工资(底薪 + 提成合计)', | |
| 547 | + F_FinalGrossSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '最终应发工资(等于核算应发工资)', | |
| 548 | + | |
| 549 | + -- 八、补贴相关字段 | |
| 550 | + F_MonthlyTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月培训补贴', | |
| 551 | + F_MonthlyTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月交通补贴', | |
| 552 | + F_LastMonthTrainingSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月培训补贴', | |
| 553 | + F_LastMonthTransportSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '上月交通补贴', | |
| 554 | + F_TotalSubsidy DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补贴合计', | |
| 555 | + | |
| 556 | + -- 九、扣款相关字段 | |
| 557 | + F_MissingCard DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '缺卡扣款', | |
| 558 | + F_LateArrival DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '迟到扣款', | |
| 559 | + F_LeaveDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '请假扣款', | |
| 560 | + F_SocialInsuranceDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣社保', | |
| 561 | + F_RewardDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣除奖励', | |
| 562 | + F_AccommodationDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣住宿费', | |
| 563 | + F_StudyPeriodDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣学习期费用', | |
| 564 | + F_WorkClothesDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣工作服费用', | |
| 565 | + F_TotalDeduction DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '扣款合计', | |
| 566 | + | |
| 567 | + -- 十、奖金相关字段 | |
| 568 | + F_Bonus DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '发奖金', | |
| 569 | + F_ReturnPhoneDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退手机押金', | |
| 570 | + F_ReturnAccommodationDeposit DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '退住宿押金', | |
| 571 | + | |
| 572 | + -- 十一、支付相关字段 | |
| 573 | + F_ActualSalary DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '实发工资(最终应发工资 - 扣款合计 + 补贴合计 + 奖金)', | |
| 574 | + F_MonthlyPaymentStatus VARCHAR(20) NOT NULL DEFAULT '未发放' COMMENT '当月是否发放(已发放/未发放/部分发放)', | |
| 575 | + F_PaidAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额', | |
| 576 | + F_PendingAmount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待支付金额', | |
| 577 | + F_LastMonthSupplement DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '补发上月', | |
| 578 | + F_MonthlyTotalPayment DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '当月支付总额', | |
| 579 | + | |
| 580 | + -- 十二、系统字段 | |
| 581 | + F_IsLocked INT NOT NULL DEFAULT 0 COMMENT '是否锁定(0=未锁定,1=已锁定)', | |
| 582 | + F_CreateTime DATETIME NOT NULL COMMENT '创建时间', | |
| 583 | + F_UpdateTime DATETIME NOT NULL COMMENT '更新时间', | |
| 584 | + F_CreateUser VARCHAR(50) NULL COMMENT '创建人', | |
| 585 | + F_UpdateUser VARCHAR(50) NULL COMMENT '更新人', | |
| 586 | + | |
| 587 | + -- 主键约束 | |
| 588 | + PRIMARY KEY (F_Id), | |
| 589 | + | |
| 590 | + -- 唯一索引:确保同一员工同一月份只有一条记录 | |
| 591 | + UNIQUE KEY `uk_employee_month` (F_EmployeeId, F_StatisticsMonth), | |
| 592 | + | |
| 593 | + -- 普通索引 | |
| 594 | + KEY `idx_statistics_month` (F_StatisticsMonth), | |
| 595 | + KEY `idx_employee_id` (F_EmployeeId), | |
| 596 | + KEY `idx_employee_account` (F_EmployeeAccount), | |
| 597 | + KEY `idx_position` (F_Position), | |
| 598 | + KEY `idx_is_terminated` (F_IsTerminated), | |
| 599 | + KEY `idx_create_time` (F_CreateTime) | |
| 600 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='科技部总经理工资统计表'; | |
| 601 | +``` | |
| 602 | + | |
| 603 | +**门店明细JSON格式示例**: | |
| 604 | +```json | |
| 605 | +[ | |
| 606 | + { | |
| 607 | + "storeId": "A001", | |
| 608 | + "storeName": "门店A", | |
| 609 | + "traceabilityBillingAmount": 160000.00, | |
| 610 | + "traceabilityRefundAmount": 10000.00, | |
| 611 | + "traceabilityAmount": 150000.00, | |
| 612 | + "cellBillingAmount": 85000.00, | |
| 613 | + "cellRefundAmount": 5000.00, | |
| 614 | + "cellAmount": 80000.00 | |
| 615 | + }, | |
| 616 | + { | |
| 617 | + "storeId": "B001", | |
| 618 | + "storeName": "门店B", | |
| 619 | + "traceabilityBillingAmount": 105000.00, | |
| 620 | + "traceabilityRefundAmount": 5000.00, | |
| 621 | + "traceabilityAmount": 100000.00, | |
| 622 | + "cellBillingAmount": 125000.00, | |
| 623 | + "cellRefundAmount": 5000.00, | |
| 624 | + "cellAmount": 120000.00 | |
| 625 | + } | |
| 626 | +] | |
| 627 | +``` | |
| 628 | + | |
| 629 | +--- | |
| 630 | + | |
| 631 | +## ✅ 总结 | |
| 632 | + | |
| 633 | +科技部总经理工资计算规则相对简单明确: | |
| 634 | +1. **底薪固定**:4000元,无任何条件 | |
| 635 | +2. **溯源提成**:根据管理的所有门店的溯源金额总和分段累进,最高2.5% | |
| 636 | +3. **Cell提成**:根据管理的所有门店的Cell金额总和分段累进,最高1.5%,低于5万无提成 | |
| 637 | + | |
| 638 | +关键点: | |
| 639 | +- 必须从 `BASE_USER` 表识别科技部总经理(岗位为"科技一部"、"科技二部"等) | |
| 640 | +- 必须从 `lq_md_general_manager_lifeline` 表获取管理的门店 | |
| 641 | +- 必须正确区分溯源和Cell类型(通过 `F_BeautyType` 字段) | |
| 642 | +- 必须扣除退卡金额,确保数据准确性 | |
| 643 | +- 必须按管理的门店筛选,只统计该科技部总经理管理的门店 | |
| 644 | +- 采用分段累进方式计算提成,确保计算准确 | ... | ... |