Commit 6af31905e774562f0a0ee16861bdce45d3b448d0
1 parent
cea16511
feat: 优化多个接口功能
1. 健康师薪酬计算优化: - 修复升单提点计算规则(>=10:12%, >=7:10%, >=4:7%, <4:0%) - 修复升单业绩提成计算(需要乘以0.95) - 修复顾问提成规则(按战队人数区分:3人及以上按3人规则,2人按2人规则,1人无顾问) - 修复健康师筛选逻辑(只统计岗位为'健康师'的员工) - 修复新店健康师底薪计算说明 2. 店助薪酬计算优化: - 修复门店目标缺失时的处理逻辑(默认值为0,不抛异常) 3. 合同成本统计接口优化: - 年份和月份参数改为可选 - 优化时间范围过滤逻辑 4. 合作成本表功能增强: - 添加成本类型字段(F_CostType) - 导入功能优化(支持通过门店名称查找门店ID) - 更新Excel模板 5. 清洗流水管理接口优化: - 创建送出记录接口:支持传入sendTime参数(可选) - 创建送回记录接口:支持传入returnTime参数(可选) - 修改接口:支持修改sendTime和returnTime 6. 开单品项明细列表接口优化: - 添加paymentMethod字段(付款方式,来源于开单记录表的fkfs字段) 7. 其他优化: - 修复开单记录表PerformanceType字段同步更新逻辑 - 更新相关文档说明
Showing
30 changed files
with
734 additions
and
199 deletions
excel/合作成本表.xlsx
No preview for this file type
excel/合作成本表_测试导入.xlsx
0 → 100644
No preview for this file type
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/ContractExpenseStatisticsInput.cs
| ... | ... | @@ -16,20 +16,16 @@ namespace NCC.Extend.Entitys.Dto.LqContract |
| 16 | 16 | public string StoreId { get; set; } |
| 17 | 17 | |
| 18 | 18 | /// <summary> |
| 19 | - /// 统计年份(必填) | |
| 19 | + /// 统计年份(可选) | |
| 20 | 20 | /// </summary> |
| 21 | - [Required(ErrorMessage = "年份不能为空")] | |
| 22 | - [Range(2020, 2100, ErrorMessage = "年份必须在2020-2100之间")] | |
| 23 | 21 | [Display(Name = "年份")] |
| 24 | - public int Year { get; set; } | |
| 22 | + public int? Year { get; set; } | |
| 25 | 23 | |
| 26 | 24 | /// <summary> |
| 27 | - /// 统计月份(必填,1-12) | |
| 25 | + /// 统计月份(可选,1-12) | |
| 28 | 26 | /// </summary> |
| 29 | - [Required(ErrorMessage = "月份不能为空")] | |
| 30 | - [Range(1, 12, ErrorMessage = "月份必须在1-12之间")] | |
| 31 | 27 | [Display(Name = "月份")] |
| 32 | - public int Month { get; set; } | |
| 28 | + public int? Month { get; set; } | |
| 33 | 29 | |
| 34 | 30 | /// <summary> |
| 35 | 31 | /// 分类列表(可选,不传则统计所有分类) | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs
| ... | ... | @@ -43,6 +43,11 @@ namespace NCC.Extend.Entitys.Dto.LqCooperationCost |
| 43 | 43 | public string remarks { get; set; } |
| 44 | 44 | |
| 45 | 45 | /// <summary> |
| 46 | + /// 成本类型 | |
| 47 | + /// </summary> | |
| 48 | + public string costType { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 46 | 51 | /// 创建人ID |
| 47 | 52 | /// </summary> |
| 48 | 53 | public string createUser { get; set; } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs
| ... | ... | @@ -43,6 +43,11 @@ namespace NCC.Extend.Entitys.Dto.LqCooperationCost |
| 43 | 43 | public string remarks { get; set; } |
| 44 | 44 | |
| 45 | 45 | /// <summary> |
| 46 | + /// 成本类型 | |
| 47 | + /// </summary> | |
| 48 | + public string costType { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 46 | 51 | /// 创建人ID |
| 47 | 52 | /// </summary> |
| 48 | 53 | public string createUser { get; set; } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowReturnInput.cs
| 1 | +using System; | |
| 1 | 2 | using System.ComponentModel.DataAnnotations; |
| 2 | 3 | |
| 3 | 4 | namespace NCC.Extend.Entitys.Dto.LqLaundryFlow |
| ... | ... | @@ -37,6 +38,12 @@ namespace NCC.Extend.Entitys.Dto.LqLaundryFlow |
| 37 | 38 | [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")] |
| 38 | 39 | [Display(Name = "备注")] |
| 39 | 40 | public string Remark { get; set; } |
| 41 | + | |
| 42 | + /// <summary> | |
| 43 | + /// 送回时间(可选,如果不传则使用当前时间) | |
| 44 | + /// </summary> | |
| 45 | + [Display(Name = "送回时间")] | |
| 46 | + public DateTime? ReturnTime { get; set; } | |
| 40 | 47 | } |
| 41 | 48 | } |
| 42 | 49 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowSendInput.cs
| 1 | +using System; | |
| 1 | 2 | using System.ComponentModel.DataAnnotations; |
| 2 | 3 | |
| 3 | 4 | namespace NCC.Extend.Entitys.Dto.LqLaundryFlow |
| ... | ... | @@ -45,6 +46,12 @@ namespace NCC.Extend.Entitys.Dto.LqLaundryFlow |
| 45 | 46 | [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")] |
| 46 | 47 | [Display(Name = "备注")] |
| 47 | 48 | public string Remark { get; set; } |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 送出时间(可选,如果不传则使用当前时间) | |
| 52 | + /// </summary> | |
| 53 | + [Display(Name = "送出时间")] | |
| 54 | + public DateTime? SendTime { get; set; } | |
| 48 | 55 | } |
| 49 | 56 | } |
| 50 | 57 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs
| ... | ... | @@ -54,6 +54,12 @@ namespace NCC.Extend.Entitys.lq_cooperation_cost |
| 54 | 54 | public string Remarks { get; set; } |
| 55 | 55 | |
| 56 | 56 | /// <summary> |
| 57 | + /// 成本类型 | |
| 58 | + /// </summary> | |
| 59 | + [SugarColumn(ColumnName = "F_CostType")] | |
| 60 | + public string CostType { get; set; } | |
| 61 | + | |
| 62 | + /// <summary> | |
| 57 | 63 | /// 是否有效(1:有效 0:无效) |
| 58 | 64 | /// </summary> |
| 59 | 65 | [SugarColumn(ColumnName = "F_IsEffective")] | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_store/LqShareStatisticsStoreEntity.cs
| ... | ... | @@ -183,12 +183,18 @@ namespace NCC.Extend.Entitys.lq_share_statistics_store |
| 183 | 183 | public decimal SalaryBaseStoreManager { get; set; } |
| 184 | 184 | |
| 185 | 185 | /// <summary> |
| 186 | - /// 人工工资-总经理/经理底薪 | |
| 186 | + /// 人工工资-总经理底薪 | |
| 187 | 187 | /// </summary> |
| 188 | 188 | [SugarColumn(ColumnName = "F_SalaryBaseGeneralManager")] |
| 189 | 189 | public decimal SalaryBaseGeneralManager { get; set; } |
| 190 | 190 | |
| 191 | 191 | /// <summary> |
| 192 | + /// 人工工资-经理底薪 | |
| 193 | + /// </summary> | |
| 194 | + [SugarColumn(ColumnName = "F_SalaryBaseManager")] | |
| 195 | + public decimal SalaryBaseManager { get; set; } | |
| 196 | + | |
| 197 | + /// <summary> | |
| 192 | 198 | /// 人工工资-健康师提成 |
| 193 | 199 | /// </summary> |
| 194 | 200 | [SugarColumn(ColumnName = "F_SalaryCommissionHealthCoach")] |
| ... | ... | @@ -213,12 +219,18 @@ namespace NCC.Extend.Entitys.lq_share_statistics_store |
| 213 | 219 | public decimal SalaryCommissionStoreManager { get; set; } |
| 214 | 220 | |
| 215 | 221 | /// <summary> |
| 216 | - /// 人工工资-总经理/经理提成 | |
| 222 | + /// 人工工资-总经理提成 | |
| 217 | 223 | /// </summary> |
| 218 | 224 | [SugarColumn(ColumnName = "F_SalaryCommissionGeneralManager")] |
| 219 | 225 | public decimal SalaryCommissionGeneralManager { get; set; } |
| 220 | 226 | |
| 221 | 227 | /// <summary> |
| 228 | + /// 人工工资-经理提成 | |
| 229 | + /// </summary> | |
| 230 | + [SugarColumn(ColumnName = "F_SalaryCommissionManager")] | |
| 231 | + public decimal SalaryCommissionManager { get; set; } | |
| 232 | + | |
| 233 | + /// <summary> | |
| 222 | 234 | /// 人工工资-手工 |
| 223 | 235 | /// </summary> |
| 224 | 236 | [SugarColumn(ColumnName = "F_SalaryManual")] | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs
| ... | ... | @@ -278,21 +278,27 @@ namespace NCC.Extend |
| 278 | 278 | } |
| 279 | 279 | |
| 280 | 280 | // 2.3 获取门店目标信息(门店生命线和阶段目标) |
| 281 | + // 如果没有设置门店目标,则相关字段设为0(适用于新店未开张等情况) | |
| 281 | 282 | if (!storeTargetDict.ContainsKey(storeId)) |
| 282 | 283 | { |
| 283 | - throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算店助工资"); | |
| 284 | + // 门店目标未设置时,所有目标相关字段设为0 | |
| 285 | + salary.StoreLifeline = 0; | |
| 286 | + salary.Stage1TargetHeadCount = 0; | |
| 287 | + salary.Stage2TargetHeadCount = 0; | |
| 288 | + } | |
| 289 | + else | |
| 290 | + { | |
| 291 | + var storeTarget = storeTargetDict[storeId]; | |
| 292 | + salary.StoreLifeline = storeTarget.StoreLifeline; | |
| 293 | + | |
| 294 | + // 阶段目标设置(如果未设置,则奖励金额为0) | |
| 295 | + salary.Stage1TargetHeadCount = storeTarget.AssistantHeadcountTargetStage1 > 0 | |
| 296 | + ? (int)storeTarget.AssistantHeadcountTargetStage1 | |
| 297 | + : 0; | |
| 298 | + salary.Stage2TargetHeadCount = storeTarget.AssistantHeadcountTargetStage2 > 0 | |
| 299 | + ? (int)storeTarget.AssistantHeadcountTargetStage2 | |
| 300 | + : 0; | |
| 284 | 301 | } |
| 285 | - | |
| 286 | - var storeTarget = storeTargetDict[storeId]; | |
| 287 | - salary.StoreLifeline = storeTarget.StoreLifeline; | |
| 288 | - | |
| 289 | - // 阶段目标设置(如果未设置,则奖励金额为0) | |
| 290 | - salary.Stage1TargetHeadCount = storeTarget.AssistantHeadcountTargetStage1 > 0 | |
| 291 | - ? (int)storeTarget.AssistantHeadcountTargetStage1 | |
| 292 | - : 0; | |
| 293 | - salary.Stage2TargetHeadCount = storeTarget.AssistantHeadcountTargetStage2 > 0 | |
| 294 | - ? (int)storeTarget.AssistantHeadcountTargetStage2 | |
| 295 | - : 0; | |
| 296 | 302 | |
| 297 | 303 | // 2.4 计算门店业绩 |
| 298 | 304 | decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs
| ... | ... | @@ -1177,10 +1177,15 @@ namespace NCC.Extend |
| 1177 | 1177 | /// |
| 1178 | 1178 | /// 参数说明: |
| 1179 | 1179 | /// - storeId: 门店ID(必填) |
| 1180 | - /// - year: 统计年份(必填) | |
| 1181 | - /// - month: 统计月份(必填,1-12) | |
| 1180 | + /// - year: 统计年份(可选,不传则统计所有年份) | |
| 1181 | + /// - month: 统计月份(可选,1-12,不传则统计所有月份;如果只传月份不传年份,则使用当前年份) | |
| 1182 | 1182 | /// - categories: 分类列表(可选,不传则统计所有分类) |
| 1183 | 1183 | /// |
| 1184 | + /// 时间范围说明: | |
| 1185 | + /// - 如果同时提供年份和月份:统计指定月份的数据 | |
| 1186 | + /// - 如果只提供年份:统计整年的数据 | |
| 1187 | + /// - 如果都不提供:统计所有时间的数据 | |
| 1188 | + /// | |
| 1184 | 1189 | /// 返回数据说明: |
| 1185 | 1190 | /// - storeId: 门店ID |
| 1186 | 1191 | /// - storeName: 门店名称 |
| ... | ... | @@ -1203,16 +1208,42 @@ namespace NCC.Extend |
| 1203 | 1208 | { |
| 1204 | 1209 | try |
| 1205 | 1210 | { |
| 1206 | - // 验证月份 | |
| 1207 | - if (input.Month < 1 || input.Month > 12) | |
| 1211 | + // 验证年份(如果提供了年份) | |
| 1212 | + if (input.Year.HasValue && (input.Year < 2020 || input.Year > 2100)) | |
| 1213 | + { | |
| 1214 | + throw NCCException.Oh("年份必须在2020-2100之间"); | |
| 1215 | + } | |
| 1216 | + | |
| 1217 | + // 验证月份(如果提供了月份) | |
| 1218 | + if (input.Month.HasValue && (input.Month < 1 || input.Month > 12)) | |
| 1208 | 1219 | { |
| 1209 | 1220 | throw NCCException.Oh("月份必须在1-12之间"); |
| 1210 | 1221 | } |
| 1211 | 1222 | |
| 1212 | - // 构建统计月份的开始和结束时间 | |
| 1213 | - var statisticsMonth = new DateTime(input.Year, input.Month, 1); | |
| 1214 | - var monthStart = statisticsMonth; | |
| 1215 | - var monthEnd = statisticsMonth.AddMonths(1).AddDays(-1); | |
| 1223 | + // 如果提供了月份但没有提供年份,使用当前年份 | |
| 1224 | + if (input.Month.HasValue && !input.Year.HasValue) | |
| 1225 | + { | |
| 1226 | + input.Year = DateTime.Now.Year; | |
| 1227 | + } | |
| 1228 | + | |
| 1229 | + // 构建时间范围 | |
| 1230 | + DateTime? monthStart = null; | |
| 1231 | + DateTime? monthEnd = null; | |
| 1232 | + | |
| 1233 | + if (input.Year.HasValue && input.Month.HasValue) | |
| 1234 | + { | |
| 1235 | + // 统计指定月份 | |
| 1236 | + var statisticsMonth = new DateTime(input.Year.Value, input.Month.Value, 1); | |
| 1237 | + monthStart = statisticsMonth; | |
| 1238 | + monthEnd = statisticsMonth.AddMonths(1).AddDays(-1); | |
| 1239 | + } | |
| 1240 | + else if (input.Year.HasValue) | |
| 1241 | + { | |
| 1242 | + // 只提供了年份,统计整年 | |
| 1243 | + monthStart = new DateTime(input.Year.Value, 1, 1); | |
| 1244 | + monthEnd = new DateTime(input.Year.Value, 12, 31, 23, 59, 59); | |
| 1245 | + } | |
| 1246 | + // 如果年份和月份都没提供,monthStart 和 monthEnd 保持为 null,表示不按时间过滤 | |
| 1216 | 1247 | |
| 1217 | 1248 | // 查询门店信息 |
| 1218 | 1249 | var store = await _db.Queryable<LqMdxxEntity>() |
| ... | ... | @@ -1225,16 +1256,25 @@ namespace NCC.Extend |
| 1225 | 1256 | throw NCCException.Oh("门店不存在"); |
| 1226 | 1257 | } |
| 1227 | 1258 | |
| 1228 | - // 查询该门店在指定月份的月租明细 | |
| 1229 | - var details = await _db.Queryable<LqContractRentDetailEntity, LqContractEntity>( | |
| 1259 | + // 查询该门店在指定时间范围的月租明细 | |
| 1260 | + var detailsQuery = _db.Queryable<LqContractRentDetailEntity, LqContractEntity>( | |
| 1230 | 1261 | (detail, contract) => new JoinQueryInfos( |
| 1231 | 1262 | JoinType.Inner, detail.ContractId == contract.Id)) |
| 1232 | 1263 | .Where((detail, contract) => |
| 1233 | 1264 | contract.StoreId == input.StoreId && |
| 1234 | 1265 | contract.IsEffective == StatusEnum.有效.GetHashCode() && |
| 1235 | - detail.IsEffective == StatusEnum.有效.GetHashCode() && | |
| 1236 | - detail.PaymentMonth >= monthStart && | |
| 1237 | - detail.PaymentMonth <= monthEnd) | |
| 1266 | + detail.IsEffective == StatusEnum.有效.GetHashCode()); | |
| 1267 | + | |
| 1268 | + // 如果提供了时间范围,则添加时间过滤条件 | |
| 1269 | + if (monthStart.HasValue && monthEnd.HasValue) | |
| 1270 | + { | |
| 1271 | + detailsQuery = detailsQuery.Where((detail, contract) => | |
| 1272 | + detail.PaymentMonth >= monthStart.Value && | |
| 1273 | + detail.PaymentMonth <= monthEnd.Value); | |
| 1274 | + } | |
| 1275 | + | |
| 1276 | + // 如果指定了分类,则添加分类过滤条件 | |
| 1277 | + var details = await detailsQuery | |
| 1238 | 1278 | .WhereIF(input.Categories != null && input.Categories.Length > 0, |
| 1239 | 1279 | (detail, contract) => contract.Category != null && input.Categories.Contains(contract.Category)) |
| 1240 | 1280 | .Select((detail, contract) => new |
| ... | ... | @@ -1289,8 +1329,8 @@ namespace NCC.Extend |
| 1289 | 1329 | { |
| 1290 | 1330 | storeId = input.StoreId, |
| 1291 | 1331 | storeName = store.Dm ?? "", |
| 1292 | - year = input.Year, | |
| 1293 | - month = input.Month, | |
| 1332 | + year = input.Year ?? 0, | |
| 1333 | + month = input.Month ?? 0, | |
| 1294 | 1334 | totalAmount = totalAmount, |
| 1295 | 1335 | categoryDetails = categoryDetails |
| 1296 | 1336 | }; | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs
| ... | ... | @@ -121,6 +121,7 @@ namespace NCC.Extend.LqCooperationCost |
| 121 | 121 | month = x.Month, |
| 122 | 122 | totalAmount = x.TotalAmount, |
| 123 | 123 | remarks = x.Remarks, |
| 124 | + costType = x.CostType, | |
| 124 | 125 | createUser = x.CreateUser, |
| 125 | 126 | createTime = x.CreateTime, |
| 126 | 127 | updateUser = x.UpdateUser, |
| ... | ... | @@ -185,6 +186,7 @@ namespace NCC.Extend.LqCooperationCost |
| 185 | 186 | month = x.Month, |
| 186 | 187 | totalAmount = x.TotalAmount, |
| 187 | 188 | remarks = x.Remarks, |
| 189 | + costType = x.CostType, | |
| 188 | 190 | createUser = x.CreateUser, |
| 189 | 191 | createTime = x.CreateTime, |
| 190 | 192 | updateUser = x.UpdateUser, |
| ... | ... | @@ -243,6 +245,7 @@ namespace NCC.Extend.LqCooperationCost |
| 243 | 245 | entity.Month = input.month; |
| 244 | 246 | entity.TotalAmount = input.totalAmount; |
| 245 | 247 | entity.Remarks = input.remarks; |
| 248 | + entity.CostType = input.costType; | |
| 246 | 249 | entity.UpdateUser = _userManager.UserId; |
| 247 | 250 | entity.UpdateTime = DateTime.Now; |
| 248 | 251 | |
| ... | ... | @@ -301,7 +304,7 @@ namespace NCC.Extend.LqCooperationCost |
| 301 | 304 | { |
| 302 | 305 | exportData = await this.GetNoPagingList(input); |
| 303 | 306 | } |
| 304 | - List<ParamsModel> paramList = "[{\"value\":\"门店ID\",\"field\":\"storeId\"},{\"value\":\"门店名称\",\"field\":\"storeName\"},{\"value\":\"年份\",\"field\":\"year\"},{\"value\":\"月份\",\"field\":\"month\"},{\"value\":\"合计金额\",\"field\":\"totalAmount\"},{\"value\":\"备注说明\",\"field\":\"remarks\"},{\"value\":\"创建人\",\"field\":\"createUser\"},{\"value\":\"创建时间\",\"field\":\"createTime\"},]".ToList<ParamsModel>(); | |
| 307 | + List<ParamsModel> paramList = "[{\"value\":\"门店名称\",\"field\":\"storeName\"},{\"value\":\"年份\",\"field\":\"year\"},{\"value\":\"月份\",\"field\":\"month\"},{\"value\":\"合计金额\",\"field\":\"totalAmount\"},{\"value\":\"成本类型\",\"field\":\"costType\"},{\"value\":\"备注说明\",\"field\":\"remarks\"},{\"value\":\"创建人\",\"field\":\"createUser\"},{\"value\":\"创建时间\",\"field\":\"createTime\"},]".ToList<ParamsModel>(); | |
| 305 | 308 | ExcelConfig excelconfig = new ExcelConfig(); |
| 306 | 309 | excelconfig.FileName = "合作成本表.xls"; |
| 307 | 310 | excelconfig.HeadFont = "微软雅黑"; |
| ... | ... | @@ -329,15 +332,78 @@ namespace NCC.Extend.LqCooperationCost |
| 329 | 332 | } |
| 330 | 333 | |
| 331 | 334 | /// <summary> |
| 335 | + /// 下载导入模板 | |
| 336 | + /// </summary> | |
| 337 | + /// <remarks> | |
| 338 | + /// 下载合作成本表导入模板Excel文件 | |
| 339 | + /// | |
| 340 | + /// Excel格式: | |
| 341 | + /// 第一行为标题行:门店名称、年份、月份、合计金额、成本类型、备注说明 | |
| 342 | + /// </remarks> | |
| 343 | + /// <returns>模板文件下载信息</returns> | |
| 344 | + [HttpGet("Actions/TemplateDownload")] | |
| 345 | + public dynamic TemplateDownload() | |
| 346 | + { | |
| 347 | + try | |
| 348 | + { | |
| 349 | + // 创建模板数据(只有标题行) | |
| 350 | + var templateData = new List<Dictionary<string, object>> | |
| 351 | + { | |
| 352 | + new Dictionary<string, object> | |
| 353 | + { | |
| 354 | + { "storeName", "" }, | |
| 355 | + { "year", "" }, | |
| 356 | + { "month", "" }, | |
| 357 | + { "totalAmount", "" }, | |
| 358 | + { "costType", "" }, | |
| 359 | + { "remarks", "" } | |
| 360 | + } | |
| 361 | + }; | |
| 362 | + | |
| 363 | + ExcelConfig excelconfig = new ExcelConfig(); | |
| 364 | + excelconfig.FileName = "合作成本表模板.xlsx"; | |
| 365 | + excelconfig.HeadFont = "微软雅黑"; | |
| 366 | + excelconfig.HeadPoint = 10; | |
| 367 | + excelconfig.IsAllSizeColumn = true; | |
| 368 | + excelconfig.ColumnModel = new List<ExcelColumnModel> | |
| 369 | + { | |
| 370 | + new ExcelColumnModel { Column = "storeName", ExcelColumn = "门店名称" }, | |
| 371 | + new ExcelColumnModel { Column = "year", ExcelColumn = "年份" }, | |
| 372 | + new ExcelColumnModel { Column = "month", ExcelColumn = "月份" }, | |
| 373 | + new ExcelColumnModel { Column = "totalAmount", ExcelColumn = "合计金额" }, | |
| 374 | + new ExcelColumnModel { Column = "costType", ExcelColumn = "成本类型" }, | |
| 375 | + new ExcelColumnModel { Column = "remarks", ExcelColumn = "备注说明" } | |
| 376 | + }; | |
| 377 | + | |
| 378 | + var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName; | |
| 379 | + var columnList = new List<string> { "门店名称", "年份", "月份", "合计金额", "成本类型", "备注说明" }; | |
| 380 | + ExcelExportHelper<Dictionary<string, object>>.Export(templateData, excelconfig, addPath, columnList); | |
| 381 | + | |
| 382 | + var fileName = _userManager.UserId + "|" + addPath + "|xlsx"; | |
| 383 | + return new | |
| 384 | + { | |
| 385 | + name = excelconfig.FileName, | |
| 386 | + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") | |
| 387 | + }; | |
| 388 | + } | |
| 389 | + catch (Exception ex) | |
| 390 | + { | |
| 391 | + throw NCCException.Oh($"生成模板失败:{ex.Message}"); | |
| 392 | + } | |
| 393 | + } | |
| 394 | + | |
| 395 | + /// <summary> | |
| 332 | 396 | /// 导入合作成本数据 |
| 333 | 397 | /// </summary> |
| 334 | 398 | /// <remarks> |
| 335 | 399 | /// 从Excel文件导入合作成本数据 |
| 336 | 400 | /// |
| 337 | 401 | /// Excel格式要求: |
| 338 | - /// 第一行为标题行:门店ID、门店名称、年份、月份、合计金额、备注说明 | |
| 402 | + /// 第一行为标题行:门店名称、年份、月份、合计金额、成本类型、备注说明 | |
| 339 | 403 | /// 从第二行开始为数据行 |
| 340 | 404 | /// |
| 405 | + /// 注意:导入时通过门店名称查找门店ID,不需要填写门店ID | |
| 406 | + /// | |
| 341 | 407 | /// 示例请求: |
| 342 | 408 | /// POST /api/Extend/LqCooperationCost/Actions/Import |
| 343 | 409 | /// Content-Type: multipart/form-data |
| ... | ... | @@ -391,27 +457,43 @@ namespace NCC.Extend.LqCooperationCost |
| 391 | 457 | try |
| 392 | 458 | { |
| 393 | 459 | var row = dataTable.Rows[i]; |
| 394 | - var storeId = row[0]?.ToString()?.Trim(); | |
| 395 | - var storeName = row[1]?.ToString()?.Trim(); | |
| 396 | - var yearText = row[2]?.ToString()?.Trim(); | |
| 397 | - var monthText = row[3]?.ToString()?.Trim(); | |
| 398 | - var totalAmountText = row[4]?.ToString()?.Trim(); | |
| 460 | + // Excel列顺序:门店名称、年份、月份、合计金额、成本类型、备注说明 | |
| 461 | + var storeName = row[0]?.ToString()?.Trim(); | |
| 462 | + var yearText = row[1]?.ToString()?.Trim(); | |
| 463 | + var monthText = row[2]?.ToString()?.Trim(); | |
| 464 | + var totalAmountText = row[3]?.ToString()?.Trim(); | |
| 465 | + var costType = row[4]?.ToString()?.Trim(); | |
| 399 | 466 | var remarks = row[5]?.ToString()?.Trim(); |
| 400 | 467 | |
| 401 | 468 | // 跳过空行 |
| 402 | - if (string.IsNullOrEmpty(storeId) && string.IsNullOrEmpty(storeName)) | |
| 469 | + if (string.IsNullOrEmpty(storeName)) | |
| 470 | + { | |
| 471 | + continue; | |
| 472 | + } | |
| 473 | + | |
| 474 | + // 验证必填字段:门店名称 | |
| 475 | + if (string.IsNullOrEmpty(storeName)) | |
| 403 | 476 | { |
| 477 | + failMessages.Add($"第{i + 1}行:门店名称不能为空"); | |
| 478 | + failCount++; | |
| 404 | 479 | continue; |
| 405 | 480 | } |
| 406 | 481 | |
| 407 | - // 验证必填字段 | |
| 408 | - if (string.IsNullOrEmpty(storeId)) | |
| 482 | + // 根据门店名称查找门店ID | |
| 483 | + var store = await _db.Queryable<LqMdxxEntity>() | |
| 484 | + .Where(x => x.Dm == storeName) | |
| 485 | + .FirstAsync(); | |
| 486 | + | |
| 487 | + if (store == null) | |
| 409 | 488 | { |
| 410 | - failMessages.Add($"第{i + 1}行:门店ID不能为空"); | |
| 489 | + failMessages.Add($"第{i + 1}行:找不到门店名称'{storeName}'对应的门店"); | |
| 411 | 490 | failCount++; |
| 412 | 491 | continue; |
| 413 | 492 | } |
| 414 | 493 | |
| 494 | + var storeId = store.Id; | |
| 495 | + | |
| 496 | + // 验证年份 | |
| 415 | 497 | if (string.IsNullOrEmpty(yearText) || !int.TryParse(yearText, out int year)) |
| 416 | 498 | { |
| 417 | 499 | failMessages.Add($"第{i + 1}行:年份格式错误(应为数字)"); |
| ... | ... | @@ -419,6 +501,7 @@ namespace NCC.Extend.LqCooperationCost |
| 419 | 501 | continue; |
| 420 | 502 | } |
| 421 | 503 | |
| 504 | + // 验证月份 | |
| 422 | 505 | if (string.IsNullOrEmpty(monthText) || monthText.Length != 6) |
| 423 | 506 | { |
| 424 | 507 | failMessages.Add($"第{i + 1}行:月份格式错误(应为YYYYMM格式,如:202501)"); |
| ... | ... | @@ -426,6 +509,7 @@ namespace NCC.Extend.LqCooperationCost |
| 426 | 509 | continue; |
| 427 | 510 | } |
| 428 | 511 | |
| 512 | + // 验证合计金额 | |
| 429 | 513 | if (string.IsNullOrEmpty(totalAmountText) || !decimal.TryParse(totalAmountText, out decimal totalAmount)) |
| 430 | 514 | { |
| 431 | 515 | failMessages.Add($"第{i + 1}行:合计金额格式错误(应为数字)"); |
| ... | ... | @@ -433,16 +517,6 @@ namespace NCC.Extend.LqCooperationCost |
| 433 | 517 | continue; |
| 434 | 518 | } |
| 435 | 519 | |
| 436 | - // 如果未提供门店名称,根据门店ID查询 | |
| 437 | - if (string.IsNullOrEmpty(storeName)) | |
| 438 | - { | |
| 439 | - var store = await _db.Queryable<LqMdxxEntity>() | |
| 440 | - .Where(x => x.Id == storeId) | |
| 441 | - .Select(x => x.Dm) | |
| 442 | - .FirstAsync(); | |
| 443 | - storeName = store ?? ""; | |
| 444 | - } | |
| 445 | - | |
| 446 | 520 | // 检查是否已存在相同门店、年份、月份的记录 |
| 447 | 521 | var exists = await _db.Queryable<LqCooperationCostEntity>() |
| 448 | 522 | .Where(x => x.StoreId == storeId && x.Year == year && x.Month == monthText && x.IsEffective == StatusEnum.有效.GetHashCode()) |
| ... | ... | @@ -464,6 +538,7 @@ namespace NCC.Extend.LqCooperationCost |
| 464 | 538 | Year = year, |
| 465 | 539 | Month = monthText, |
| 466 | 540 | TotalAmount = totalAmount, |
| 541 | + CostType = costType, | |
| 467 | 542 | Remarks = remarks, |
| 468 | 543 | IsEffective = StatusEnum.有效.GetHashCode(), |
| 469 | 544 | CreateUser = _userManager.UserId, |
| ... | ... | @@ -514,3 +589,4 @@ namespace NCC.Extend.LqCooperationCost |
| 514 | 589 | } |
| 515 | 590 | } |
| 516 | 591 | |
| 592 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
| ... | ... | @@ -3802,11 +3802,11 @@ namespace NCC.Extend.LqKdKdjlb |
| 3802 | 3802 | { |
| 3803 | 3803 | var billings = await _db.Queryable<LqKdKdjlbEntity>() |
| 3804 | 3804 | .Where(x => billingIds.Contains(x.Id)) |
| 3805 | - .Select(x => new { x.Id, x.Djmd, x.Hgjg, x.Fkyy }) | |
| 3805 | + .Select(x => new { x.Id, x.Djmd, x.Hgjg, x.Fkyy, x.Fkfs }) | |
| 3806 | 3806 | .ToListAsync(); |
| 3807 | 3807 | |
| 3808 | 3808 | billingStoreDict = billings.ToDictionary(x => x.Id, x => x.Djmd ?? ""); |
| 3809 | - billingExtraInfoDict = billings.ToDictionary(x => x.Id, x => (dynamic)new { Hgjg = x.Hgjg, Fkyy = x.Fkyy }); | |
| 3809 | + billingExtraInfoDict = billings.ToDictionary(x => x.Id, x => (dynamic)new { Hgjg = x.Hgjg, Fkyy = x.Fkyy, Fkfs = x.Fkfs }); | |
| 3810 | 3810 | } |
| 3811 | 3811 | |
| 3812 | 3812 | // 批量查询门店信息 |
| ... | ... | @@ -3836,7 +3836,8 @@ namespace NCC.Extend.LqKdKdjlb |
| 3836 | 3836 | storeId = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) ? billingStoreDict[pxmx.Glkdbh] : "", |
| 3837 | 3837 | storeName = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) && !string.IsNullOrEmpty(billingStoreDict[pxmx.Glkdbh]) && storeDict.ContainsKey(billingStoreDict[pxmx.Glkdbh]) ? storeDict[billingStoreDict[pxmx.Glkdbh]] : "", |
| 3838 | 3838 | hgjg = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Hgjg : "", |
| 3839 | - fkyy = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Fkyy : "" | |
| 3839 | + fkyy = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Fkyy : "", | |
| 3840 | + paymentMethod = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Fkfs : "" | |
| 3840 | 3841 | }).ToList(); |
| 3841 | 3842 | |
| 3842 | 3843 | // 6. 返回分页结果 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs
| ... | ... | @@ -57,9 +57,18 @@ namespace NCC.Extend |
| 57 | 57 | /// "productType": "毛巾", |
| 58 | 58 | /// "laundrySupplierId": "清洗商ID", |
| 59 | 59 | /// "quantity": 100, |
| 60 | - /// "remark": "备注" | |
| 60 | + /// "remark": "备注", | |
| 61 | + /// "sendTime": "2025-01-15T10:30:00" // 可选,如果不传则使用当前时间 | |
| 61 | 62 | /// } |
| 62 | 63 | /// ``` |
| 64 | + /// | |
| 65 | + /// 参数说明: | |
| 66 | + /// - storeId: 门店ID(必填) | |
| 67 | + /// - productType: 产品类型(必填) | |
| 68 | + /// - laundrySupplierId: 清洗商ID(必填) | |
| 69 | + /// - quantity: 送出数量(必填) | |
| 70 | + /// - remark: 备注(可选) | |
| 71 | + /// - sendTime: 送出时间(可选,如果不传则使用当前时间) | |
| 63 | 72 | /// </remarks> |
| 64 | 73 | /// <param name="input">送出输入</param> |
| 65 | 74 | /// <returns>创建结果(包含批次号)</returns> |
| ... | ... | @@ -116,7 +125,7 @@ namespace NCC.Extend |
| 116 | 125 | IsEffective = StatusEnum.有效.GetHashCode(), |
| 117 | 126 | CreateUser = _userManager.UserId, |
| 118 | 127 | CreateTime = DateTime.Now, |
| 119 | - SendTime = DateTime.Now // 设置送出时间 | |
| 128 | + SendTime = input.SendTime ?? DateTime.Now // 如果传入了送出时间则使用,否则使用当前时间 | |
| 120 | 129 | }; |
| 121 | 130 | |
| 122 | 131 | var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); |
| ... | ... | @@ -145,9 +154,17 @@ namespace NCC.Extend |
| 145 | 154 | /// "batchNumber": "批次号(对应的送出记录的ID)", |
| 146 | 155 | /// "laundrySupplierId": "清洗商ID", |
| 147 | 156 | /// "quantity": 95, |
| 148 | - /// "remark": "5条损坏" | |
| 157 | + /// "remark": "5条损坏", | |
| 158 | + /// "returnTime": "2025-01-20T14:30:00" // 可选,如果不传则使用当前时间 | |
| 149 | 159 | /// } |
| 150 | 160 | /// ``` |
| 161 | + /// | |
| 162 | + /// 参数说明: | |
| 163 | + /// - batchNumber: 批次号(必填) | |
| 164 | + /// - laundrySupplierId: 清洗商ID(必填) | |
| 165 | + /// - quantity: 送回数量(必填) | |
| 166 | + /// - remark: 备注(可选) | |
| 167 | + /// - returnTime: 送回时间(可选,如果不传则使用当前时间) | |
| 151 | 168 | /// </remarks> |
| 152 | 169 | /// <param name="input">送回输入</param> |
| 153 | 170 | /// <returns>创建结果</returns> |
| ... | ... | @@ -214,7 +231,7 @@ namespace NCC.Extend |
| 214 | 231 | IsEffective = StatusEnum.有效.GetHashCode(), |
| 215 | 232 | CreateUser = _userManager.UserId, |
| 216 | 233 | CreateTime = DateTime.Now, |
| 217 | - ReturnTime = DateTime.Now // 设置送回时间 | |
| 234 | + ReturnTime = input.ReturnTime ?? DateTime.Now // 如果传入了送回时间则使用,否则使用当前时间 | |
| 218 | 235 | }; |
| 219 | 236 | |
| 220 | 237 | var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs
| ... | ... | @@ -327,18 +327,26 @@ namespace NCC.Extend |
| 327 | 327 | |
| 328 | 328 | // 1.8 批量获取员工信息 (BASE_USER + BASE_POSITION) |
| 329 | 329 | // 使用 allEmployeeIds 作为驱动,查询 BASE_USER |
| 330 | + // 重要:只统计岗位为"健康师"的员工 | |
| 330 | 331 | var userList = await _db.Queryable<UserEntity>() |
| 331 | - .Where(x => allEmployeeIds.Contains(x.Id)) | |
| 332 | + .Where(x => allEmployeeIds.Contains(x.Id) | |
| 333 | + && x.Gw == "健康师" // 只统计岗位为"健康师"的员工 | |
| 334 | + && x.DeleteMark == null | |
| 335 | + && x.EnabledMark == 1) | |
| 332 | 336 | .Select(x => new { x.Id, x.RealName, x.PositionId, x.Mdid }) |
| 333 | 337 | .ToListAsync(); |
| 334 | 338 | |
| 339 | + // 过滤出健康师ID列表 | |
| 340 | + var healthCoachIds = userList.Select(x => x.Id).ToList(); | |
| 341 | + | |
| 335 | 342 | var userDict = userList.ToDictionary(x => x.Id, x => x); |
| 336 | 343 | |
| 337 | 344 | var positionIds = userList.Select(x => x.PositionId).Distinct().ToList(); |
| 338 | 345 | var positionList = await _db.Queryable<PositionEntity>().Where(x => positionIds.Contains(x.Id)).ToListAsync(); |
| 339 | 346 | var positionLookup = positionList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x.FullName); |
| 340 | 347 | |
| 341 | - foreach (var empId in allEmployeeIds) | |
| 348 | + // 只处理健康师员工 | |
| 349 | + foreach (var empId in allEmployeeIds.Where(x => healthCoachIds.Contains(x))) | |
| 342 | 350 | { |
| 343 | 351 | var salary = new LqSalaryStatisticsEntity |
| 344 | 352 | { |
| ... | ... | @@ -671,6 +679,10 @@ namespace NCC.Extend |
| 671 | 679 | // 计算新客转化率提成和升单人头提成(根据新店阶段) |
| 672 | 680 | // isNewStore 和 newStoreStage 已在上面定义 |
| 673 | 681 | |
| 682 | + // 升单提点总是赋值(根据升单人头数) | |
| 683 | + decimal upgradeCommissionRate = GetUpgradeCommissionRate(salary.UpgradeCustomerCount); | |
| 684 | + salary.UpgradePoint = upgradeCommissionRate; | |
| 685 | + | |
| 674 | 686 | if (isNewStore) |
| 675 | 687 | { |
| 676 | 688 | if (newStoreStage == 1) |
| ... | ... | @@ -695,10 +707,23 @@ namespace NCC.Extend |
| 695 | 707 | // 注意:这里需要重新判断是否是顾问,因为可能被降级了 |
| 696 | 708 | if (salary.Position == "顾问" && !string.IsNullOrEmpty(salary.GoldTriangleId)) |
| 697 | 709 | { |
| 698 | - salary.ConsultantCommission = CalculateConsultantCommission( | |
| 699 | - salary.TeamPerformance, | |
| 700 | - employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList(), | |
| 701 | - isNewStore); | |
| 710 | + // 获取战队人数 | |
| 711 | + var teamMemberList = employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList(); | |
| 712 | + var teamMemberCount = teamMemberList.Count; | |
| 713 | + | |
| 714 | + // 单人或者一个人的战队就没有顾问 | |
| 715 | + if (teamMemberCount > 1) | |
| 716 | + { | |
| 717 | + salary.ConsultantCommission = CalculateConsultantCommission( | |
| 718 | + salary.TeamPerformance, | |
| 719 | + teamMemberList, | |
| 720 | + teamMemberCount, | |
| 721 | + isNewStore); | |
| 722 | + } | |
| 723 | + else | |
| 724 | + { | |
| 725 | + salary.ConsultantCommission = 0; | |
| 726 | + } | |
| 702 | 727 | } |
| 703 | 728 | |
| 704 | 729 | // 计算门店T区提成 |
| ... | ... | @@ -865,18 +890,25 @@ namespace NCC.Extend |
| 865 | 890 | /// <summary> |
| 866 | 891 | /// 计算顾问提成 |
| 867 | 892 | /// </summary> |
| 868 | - private decimal CalculateConsultantCommission(decimal teamPerformance, List<LqSalaryStatisticsEntity> teamMembers, bool isNewStore) | |
| 893 | + /// <param name="teamPerformance">战队总业绩</param> | |
| 894 | + /// <param name="teamMembers">战队成员列表</param> | |
| 895 | + /// <param name="teamMemberCount">战队人数</param> | |
| 896 | + /// <param name="isNewStore">是否为新店</param> | |
| 897 | + /// <returns>顾问提成金额</returns> | |
| 898 | + private decimal CalculateConsultantCommission(decimal teamPerformance, List<LqSalaryStatisticsEntity> teamMembers, int teamMemberCount, bool isNewStore) | |
| 869 | 899 | { |
| 870 | 900 | // 顾问提成规则: |
| 871 | - // 高级顾问:战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8% | |
| 872 | - // 普通顾问:战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3% | |
| 901 | + // 战队人数3人:战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8% | |
| 902 | + // 战队人数2人:战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3% | |
| 903 | + // 如果3人以上,就按照3人的规则来 | |
| 904 | + // 单人或者一个人的战队就没有顾问(已在调用处处理) | |
| 873 | 905 | |
| 874 | 906 | // 注意: |
| 875 | 907 | // 1. "组员业绩"指除顾问外的其他成员业绩总和 |
| 876 | 908 | // 2. 只统计有效战队成员(考勤≥20天,未被剔除的成员) |
| 877 | 909 | // 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X% |
| 878 | 910 | // 4. 新店顾问不考核消耗 |
| 879 | - // 5. 消耗达标:高级顾问整组消耗>=6万,普通顾问整组消耗>=4万 | |
| 911 | + // 5. 消耗达标:3人战队整组消耗>=6万,2人战队整组消耗>=4万 | |
| 880 | 912 | |
| 881 | 913 | // 使用传入的 teamMembers 计算总消耗 |
| 882 | 914 | var teamConsumption = teamMembers.Sum(x => x.Consumption); |
| ... | ... | @@ -884,28 +916,29 @@ namespace NCC.Extend |
| 884 | 916 | // 计算组员(非顾问)业绩总和 |
| 885 | 917 | var memberPerformance = teamMembers.Where(x => x.Position != "顾问").Sum(x => x.TotalPerformance); |
| 886 | 918 | |
| 887 | - // 高级顾问:业绩≥6万 且 组员业绩≥40% 且 (新店(第1,2阶段) 或 消耗≥6万) | |
| 888 | - // 注意:isNewStore 仅代表是否为新店,具体免考核阶段需确认。假设新店前两个阶段免考核,第三阶段需考核。 | |
| 889 | - // 这里暂且沿用 isNewStore 逻辑,如果需要更细粒度控制,应传入 NewStoreProtectionStage | |
| 890 | - // 如果 isNewStore 为 true,则默认免考核消耗(根据原需求描述:新店第3个阶段时,有金三角,但是不考核消耗) | |
| 891 | - // 用户最新指示:新店第3个阶段时,有金三角,但是不考核消耗,默认达标 -> 意味着只要是新店,不管阶段,都不考核消耗 | |
| 892 | - | |
| 893 | - if (teamPerformance >= 60000 && memberPerformance >= teamPerformance * 0.4m) | |
| 919 | + // 3人及以上战队:业绩≥6万 且 组员业绩≥40% 且 (新店 或 消耗≥6万) → 0.8% | |
| 920 | + if (teamMemberCount >= 3) | |
| 894 | 921 | { |
| 895 | - if (isNewStore || teamConsumption >= 60000) | |
| 922 | + if (teamPerformance >= 60000 && memberPerformance >= teamPerformance * 0.4m) | |
| 896 | 923 | { |
| 897 | - return teamPerformance * 0.008m; | |
| 924 | + if (isNewStore || teamConsumption >= 60000) | |
| 925 | + { | |
| 926 | + return teamPerformance * 0.008m; | |
| 927 | + } | |
| 898 | 928 | } |
| 899 | 929 | } |
| 900 | - | |
| 901 | - // 普通顾问:业绩≥4万 且 组员业绩≥30% 且 (新店 或 消耗≥4万) | |
| 902 | - if (teamPerformance >= 40000 && memberPerformance >= teamPerformance * 0.3m) | |
| 930 | + // 2人战队:业绩≥4万 且 组员业绩≥30% 且 (新店 或 消耗≥4万) → 0.3% | |
| 931 | + else if (teamMemberCount == 2) | |
| 903 | 932 | { |
| 904 | - if (isNewStore || teamConsumption >= 40000) | |
| 933 | + if (teamPerformance >= 40000 && memberPerformance >= teamPerformance * 0.3m) | |
| 905 | 934 | { |
| 906 | - return teamPerformance * 0.003m; | |
| 935 | + if (isNewStore || teamConsumption >= 40000) | |
| 936 | + { | |
| 937 | + return teamPerformance * 0.003m; | |
| 938 | + } | |
| 907 | 939 | } |
| 908 | 940 | } |
| 941 | + // 1人战队:没有顾问,返回0(已在调用处处理,这里不会执行到) | |
| 909 | 942 | |
| 910 | 943 | return 0; |
| 911 | 944 | } |
| ... | ... | @@ -926,16 +959,23 @@ namespace NCC.Extend |
| 926 | 959 | } |
| 927 | 960 | |
| 928 | 961 | /// <summary> |
| 962 | + /// 获取升单提成比例 | |
| 963 | + /// </summary> | |
| 964 | + private decimal GetUpgradeCommissionRate(decimal upgradeCustomerCount) | |
| 965 | + { | |
| 966 | + if (upgradeCustomerCount >= 10) return 0.12m; // 大于等于10:12% | |
| 967 | + else if (upgradeCustomerCount >= 7 && upgradeCustomerCount < 10) return 0.10m; // 大于等于7且小于10:10% | |
| 968 | + else if (upgradeCustomerCount >= 4 && upgradeCustomerCount < 7) return 0.07m; // 大于等于4且小于7:7% | |
| 969 | + // 小于4:0% | |
| 970 | + return 0m; | |
| 971 | + } | |
| 972 | + | |
| 973 | + /// <summary> | |
| 929 | 974 | /// 计算升单人头提成 |
| 930 | 975 | /// </summary> |
| 931 | 976 | private decimal CalculateUpgradeCustomerCommission(decimal upgradePerformance, decimal upgradeCustomerCount) |
| 932 | 977 | { |
| 933 | - decimal commissionRate = 0; | |
| 934 | - | |
| 935 | - if (upgradeCustomerCount >= 10) commissionRate = 0.20m; | |
| 936 | - else if (upgradeCustomerCount >= 4) commissionRate = 0.10m; | |
| 937 | - // 0-4个: 0% | |
| 938 | - | |
| 978 | + decimal commissionRate = GetUpgradeCommissionRate(upgradeCustomerCount); | |
| 939 | 979 | return upgradePerformance * commissionRate; |
| 940 | 980 | } |
| 941 | 981 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs
| 1 | 1 | using Microsoft.AspNetCore.Mvc; |
| 2 | +using Microsoft.Extensions.Logging; | |
| 2 | 3 | using NCC.Common.Filter; |
| 3 | 4 | using NCC.Dependency; |
| 4 | 5 | using NCC.DynamicApiController; |
| ... | ... | @@ -16,6 +17,7 @@ using NCC.Extend.Entitys.lq_director_salary_statistics; |
| 16 | 17 | using NCC.Extend.Entitys.lq_store_manager_salary_statistics; |
| 17 | 18 | using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics; |
| 18 | 19 | using NCC.Extend.Entitys.lq_contract_rent_detail; |
| 20 | +using NCC.Extend.Entitys.lq_contract_monthly_cost; | |
| 19 | 21 | using NCC.Extend.Entitys.lq_mdxx; |
| 20 | 22 | using NCC.Extend.Entitys.lq_kd_pxmx; |
| 21 | 23 | using NCC.Extend.Entitys.lq_product; |
| ... | ... | @@ -38,10 +40,12 @@ namespace NCC.Extend |
| 38 | 40 | public class LqShareStatisticsStoreService : IDynamicApiController, ITransient |
| 39 | 41 | { |
| 40 | 42 | private readonly ISqlSugarClient _db; |
| 43 | + private readonly Microsoft.Extensions.Logging.ILogger<LqShareStatisticsStoreService> _logger; | |
| 41 | 44 | |
| 42 | - public LqShareStatisticsStoreService(ISqlSugarClient db) | |
| 45 | + public LqShareStatisticsStoreService(ISqlSugarClient db, Microsoft.Extensions.Logging.ILogger<LqShareStatisticsStoreService> logger) | |
| 43 | 46 | { |
| 44 | 47 | _db = db; |
| 48 | + _logger = logger; | |
| 45 | 49 | } |
| 46 | 50 | |
| 47 | 51 | /// <summary> |
| ... | ... | @@ -60,7 +64,7 @@ namespace NCC.Extend |
| 60 | 64 | var year = int.Parse(input.StatisticsMonth.Substring(0, 4)); |
| 61 | 65 | var month = int.Parse(input.StatisticsMonth.Substring(4, 2)); |
| 62 | 66 | var startDate = new DateTime(year, month, 1); |
| 63 | - var endDate = startDate.AddMonths(1).AddDays(-1); | |
| 67 | + var endDate = startDate.AddMonths(1).AddDays(-1).Date.AddHours(23).AddMinutes(59).AddSeconds(59); | |
| 64 | 68 | |
| 65 | 69 | // 获取门店列表 |
| 66 | 70 | var storeQuery = _db.Queryable<LqMdxxEntity>(); |
| ... | ... | @@ -346,13 +350,26 @@ namespace NCC.Extend |
| 346 | 350 | && x.IsEffective == 1) |
| 347 | 351 | .SumAsync(x => x.TotalPrice); |
| 348 | 352 | |
| 349 | - // 4. 科技部成本 = 科美业绩 * 30% | |
| 350 | - var kemeiPerformance = await _db.Queryable<LqKdPxmxEntity>() | |
| 351 | - .Where(x => x.Glkdbh.StartsWith(entity.StoreId) && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1) | |
| 352 | - .Where(x => x.ItemCategory == "科美") | |
| 353 | - .SumAsync(x => x.TotalPrice); | |
| 353 | + // 4. 科技部成本 = (科美开单业绩 - 科美退款业绩) * 30% | |
| 354 | + // 4.1 统计科美开单业绩(通过关联开单表获取门店信息) | |
| 355 | + var kemeiBilling = await _db.Queryable<LqKdPxmxEntity, LqKdKdjlbEntity>((px, kd) => new JoinQueryInfos( | |
| 356 | + JoinType.Inner, px.Glkdbh == kd.Id)) | |
| 357 | + .Where((px, kd) => kd.Djmd == entity.StoreId | |
| 358 | + && px.Yjsj >= startDate && px.Yjsj <= endDate | |
| 359 | + && px.IsEffective == 1 | |
| 360 | + && px.ItemCategory == "科美") | |
| 361 | + .SumAsync((px, kd) => (decimal?)px.TotalPrice); | |
| 362 | + | |
| 363 | + // 4.2 统计科美退款业绩(从退卡健康师业绩表统计) | |
| 364 | + var kemeiRefund = await _db.Queryable<LqHytkJksyjEntity>() | |
| 365 | + .Where(x => x.StoreId == entity.StoreId | |
| 366 | + && x.Tksj >= startDate && x.Tksj <= endDate | |
| 367 | + && x.IsEffective == 1 | |
| 368 | + && x.ItemCategory == "科美") | |
| 369 | + .SumAsync(x => (decimal?)(x.Jksyj ?? 0)); | |
| 354 | 370 | |
| 355 | - entity.CostTechDept = kemeiPerformance * 0.3m; | |
| 371 | + // 4.3 计算科技部成本 = (开单业绩 - 退款业绩) * 30% | |
| 372 | + entity.CostTechDept = ((kemeiBilling ?? 0m) - (kemeiRefund ?? 0m)) * 0.3m; | |
| 356 | 373 | |
| 357 | 374 | // 5. 管理费 = 总业绩 * 9% |
| 358 | 375 | var totalPerformance = await _db.Queryable<LqKdKdjlbEntity>() |
| ... | ... | @@ -444,42 +461,131 @@ namespace NCC.Extend |
| 444 | 461 | |
| 445 | 462 | // 5. 总经理/经理底薪和提成 |
| 446 | 463 | // 底薪按门店数平均分摊,提成需要从 F_StorePerformanceDetail JSON 中提取该门店的提成 |
| 447 | - var gmSalary = await _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>() | |
| 464 | + var businessUnitSalaries = await _db.Queryable<LqBusinessUnitManagerSalaryStatisticsEntity>() | |
| 448 | 465 | .Where(x => x.StatisticsMonth == statisticsMonth) |
| 449 | 466 | .Where(x => x.StorePerformanceDetail.Contains(entity.StoreId)) |
| 450 | 467 | .ToListAsync(); |
| 451 | 468 | |
| 452 | 469 | decimal gmBaseSalary = 0; |
| 453 | 470 | decimal gmCommission = 0; |
| 471 | + decimal managerBaseSalary = 0; | |
| 472 | + decimal managerCommission = 0; | |
| 454 | 473 | |
| 455 | - foreach (var gm in gmSalary) | |
| 474 | + foreach (var salaryRecord in businessUnitSalaries) | |
| 456 | 475 | { |
| 457 | 476 | // 底薪平均分摊 |
| 458 | - if (!string.IsNullOrEmpty(gm.StorePerformanceDetail)) | |
| 477 | + if (!string.IsNullOrEmpty(salaryRecord.StorePerformanceDetail)) | |
| 459 | 478 | { |
| 460 | 479 | try |
| 461 | 480 | { |
| 462 | - var storeDetails = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(gm.StorePerformanceDetail); | |
| 481 | + var storeDetails = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(salaryRecord.StorePerformanceDetail); | |
| 463 | 482 | var storeCount = storeDetails?.Count ?? 1; |
| 464 | - gmBaseSalary += gm.BaseSalary / storeCount; | |
| 465 | 483 | |
| 466 | - // 提取该门店的提成 | |
| 467 | 484 | var storeDetail = storeDetails?.FirstOrDefault(s => s.ContainsKey("StoreId") && s["StoreId"].ToString() == entity.StoreId); |
| 468 | - if (storeDetail != null && storeDetail.ContainsKey("Commission")) | |
| 485 | + if (storeDetail != null) | |
| 469 | 486 | { |
| 470 | - gmCommission += Convert.ToDecimal(storeDetail["Commission"]); | |
| 487 | + // 根据ManagerType区分总经理和经理 | |
| 488 | + if (salaryRecord.ManagerType == 1) // 总经理 | |
| 489 | + { | |
| 490 | + gmBaseSalary += salaryRecord.BaseSalary / storeCount; | |
| 491 | + | |
| 492 | + // 提取该门店的提成(JSON字段名是CommissionAmount) | |
| 493 | + object commissionValue = null; | |
| 494 | + if (storeDetail.ContainsKey("CommissionAmount")) | |
| 495 | + { | |
| 496 | + commissionValue = storeDetail["CommissionAmount"]; | |
| 497 | + } | |
| 498 | + else | |
| 499 | + { | |
| 500 | + // 尝试不区分大小写查找 | |
| 501 | + var key = storeDetail.Keys.FirstOrDefault(k => k.Equals("CommissionAmount", StringComparison.OrdinalIgnoreCase)); | |
| 502 | + if (key != null) | |
| 503 | + { | |
| 504 | + commissionValue = storeDetail[key]; | |
| 505 | + } | |
| 506 | + } | |
| 507 | + | |
| 508 | + if (commissionValue != null) | |
| 509 | + { | |
| 510 | + // JSON反序列化后,数值类型可能是long或double,需要先转换为decimal | |
| 511 | + decimal commission = 0; | |
| 512 | + if (commissionValue is long longValue) | |
| 513 | + { | |
| 514 | + commission = longValue; | |
| 515 | + } | |
| 516 | + else if (commissionValue is double doubleValue) | |
| 517 | + { | |
| 518 | + commission = (decimal)doubleValue; | |
| 519 | + } | |
| 520 | + else if (commissionValue is decimal decimalValue) | |
| 521 | + { | |
| 522 | + commission = decimalValue; | |
| 523 | + } | |
| 524 | + else | |
| 525 | + { | |
| 526 | + commission = Convert.ToDecimal(commissionValue); | |
| 527 | + } | |
| 528 | + gmCommission += commission; | |
| 529 | + } | |
| 530 | + } | |
| 531 | + else if (salaryRecord.ManagerType == 0) // 经理 | |
| 532 | + { | |
| 533 | + managerBaseSalary += salaryRecord.BaseSalary / storeCount; | |
| 534 | + | |
| 535 | + // 提取该门店的提成(JSON字段名是CommissionAmount) | |
| 536 | + object commissionValue = null; | |
| 537 | + if (storeDetail.ContainsKey("CommissionAmount")) | |
| 538 | + { | |
| 539 | + commissionValue = storeDetail["CommissionAmount"]; | |
| 540 | + } | |
| 541 | + else | |
| 542 | + { | |
| 543 | + // 尝试不区分大小写查找 | |
| 544 | + var key = storeDetail.Keys.FirstOrDefault(k => k.Equals("CommissionAmount", StringComparison.OrdinalIgnoreCase)); | |
| 545 | + if (key != null) | |
| 546 | + { | |
| 547 | + commissionValue = storeDetail[key]; | |
| 548 | + } | |
| 549 | + } | |
| 550 | + | |
| 551 | + if (commissionValue != null) | |
| 552 | + { | |
| 553 | + // JSON反序列化后,数值类型可能是long或double,需要先转换为decimal | |
| 554 | + decimal commission = 0; | |
| 555 | + if (commissionValue is long longValue) | |
| 556 | + { | |
| 557 | + commission = longValue; | |
| 558 | + } | |
| 559 | + else if (commissionValue is double doubleValue) | |
| 560 | + { | |
| 561 | + commission = (decimal)doubleValue; | |
| 562 | + } | |
| 563 | + else if (commissionValue is decimal decimalValue) | |
| 564 | + { | |
| 565 | + commission = decimalValue; | |
| 566 | + } | |
| 567 | + else | |
| 568 | + { | |
| 569 | + commission = Convert.ToDecimal(commissionValue); | |
| 570 | + } | |
| 571 | + managerCommission += commission; | |
| 572 | + } | |
| 573 | + } | |
| 471 | 574 | } |
| 472 | 575 | } |
| 473 | - catch | |
| 576 | + catch (Exception ex) | |
| 474 | 577 | { |
| 475 | - // JSON 解析失败,使用默认分摊 | |
| 476 | - gmBaseSalary += gm.BaseSalary; | |
| 578 | + _logger.LogError(ex, $"解析门店业绩明细JSON失败,使用默认分摊。BatchId: {salaryRecord.Id}"); | |
| 579 | + // JSON 解析失败,使用默认分摊到总经理 | |
| 580 | + gmBaseSalary += salaryRecord.BaseSalary; | |
| 477 | 581 | } |
| 478 | 582 | } |
| 479 | 583 | } |
| 480 | 584 | |
| 481 | 585 | entity.SalaryBaseGeneralManager = gmBaseSalary; |
| 482 | 586 | entity.SalaryCommissionGeneralManager = gmCommission; |
| 587 | + entity.SalaryBaseManager = managerBaseSalary; | |
| 588 | + entity.SalaryCommissionManager = managerCommission; | |
| 483 | 589 | |
| 484 | 590 | // 保留字段 |
| 485 | 591 | entity.SalaryAttendance = 0; |
| ... | ... | @@ -490,22 +596,22 @@ namespace NCC.Extend |
| 490 | 596 | /// </summary> |
| 491 | 597 | private async Task CalculateExpense(LqShareStatisticsStoreEntity entity, DateTime startDate, DateTime endDate, string statisticsMonth) |
| 492 | 598 | { |
| 493 | - // 1. 门店房租 - 需要通过合同关联门店 | |
| 494 | - // 从合同表关联租金明细 | |
| 599 | + // 1. 门店房租 - 从合同成本按月统计表获取 | |
| 495 | 600 | // 解析统计月份 |
| 496 | 601 | var year = int.Parse(statisticsMonth.Substring(0, 4)); |
| 497 | 602 | var month = int.Parse(statisticsMonth.Substring(4, 2)); |
| 603 | + var monthDate = new DateTime(year, month, 1); | |
| 498 | 604 | |
| 499 | - var rentDetail = await _db.Queryable<LqContractRentDetailEntity, NCC.Extend.Entitys.lq_contract.LqContractEntity>((rd, c) => new JoinQueryInfos( | |
| 500 | - JoinType.Inner, rd.ContractId == c.Id)) | |
| 501 | - .Where((rd, c) => c.StoreId == entity.StoreId | |
| 502 | - && rd.PaymentMonth.Year == year | |
| 503 | - && rd.PaymentMonth.Month == month | |
| 504 | - && rd.IsEffective == 1) | |
| 505 | - .Select((rd, c) => rd.DueAmount) | |
| 506 | - .ToListAsync(); | |
| 605 | + // 从 lq_contract_monthly_cost 表统计分类为"门店"的月度成本 | |
| 606 | + var storeRent = await _db.Queryable<LqContractMonthlyCostEntity>() | |
| 607 | + .Where(x => x.StoreId == entity.StoreId | |
| 608 | + && x.Month.Year == year | |
| 609 | + && x.Month.Month == month | |
| 610 | + && x.Category == "门店" | |
| 611 | + && x.IsEffective == 1) | |
| 612 | + .SumAsync(x => (decimal?)x.MonthlyCost); | |
| 507 | 613 | |
| 508 | - entity.StoreRent = rentDetail.Sum(); | |
| 614 | + entity.StoreRent = storeRent ?? 0; | |
| 509 | 615 | |
| 510 | 616 | // 2. 当期费用 = 报销费用中一级分类为“当期费用”的已审核通过记录 |
| 511 | 617 | // 只统计:报销申请审批通过 + 申请门店=当前门店 + 申请时间在当月 |
| ... | ... | @@ -541,9 +647,9 @@ namespace NCC.Extend |
| 541 | 647 | { |
| 542 | 648 | // 人工工资汇总 |
| 543 | 649 | var totalSalary = entity.SalaryBaseHealthCoach + entity.SalaryBaseAssistant + entity.SalaryBaseDirector |
| 544 | - + entity.SalaryBaseStoreManager + entity.SalaryBaseGeneralManager | |
| 650 | + + entity.SalaryBaseStoreManager + entity.SalaryBaseGeneralManager + entity.SalaryBaseManager | |
| 545 | 651 | + entity.SalaryCommissionHealthCoach + entity.SalaryCommissionAssistant + entity.SalaryCommissionDirector |
| 546 | - + entity.SalaryCommissionStoreManager + entity.SalaryCommissionGeneralManager | |
| 652 | + + entity.SalaryCommissionStoreManager + entity.SalaryCommissionGeneralManager + entity.SalaryCommissionManager | |
| 547 | 653 | + entity.SalaryManual + entity.SalaryAttendance + entity.SalaryPhoneCustody |
| 548 | 654 | + entity.SalaryHeadcountReward + entity.SalaryTZone; |
| 549 | 655 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs
| ... | ... | @@ -9,6 +9,12 @@ using NCC.Extend.Entitys.lq_kd_pxmx; |
| 9 | 9 | using NCC.Extend.Entitys.lq_kd_kdjlb; |
| 10 | 10 | using NCC.Extend.Entitys.lq_hytk_jksyj; |
| 11 | 11 | using NCC.Extend.Entitys.lq_xh_jksyj; |
| 12 | +using NCC.Extend.Entitys.lq_xh_kjbsyj; | |
| 13 | +using NCC.Extend.Entitys.lq_tech_teacher_salary_statistics; | |
| 14 | +using NCC.Extend.Entitys.lq_tech_general_manager_salary_statistics; | |
| 15 | +using NCC.Extend.Entitys.lq_md_general_manager_lifeline; | |
| 16 | +using NCC.System.Entitys.Permission; | |
| 17 | +using NCC.Extend.Entitys; | |
| 12 | 18 | using SqlSugar; |
| 13 | 19 | using System; |
| 14 | 20 | using System.Collections.Generic; |
| ... | ... | @@ -48,7 +54,7 @@ namespace NCC.Extend |
| 48 | 54 | var year = int.Parse(input.StatisticsMonth.Substring(0, 4)); |
| 49 | 55 | var month = int.Parse(input.StatisticsMonth.Substring(4, 2)); |
| 50 | 56 | var startDate = new DateTime(year, month, 1); |
| 51 | - var endDate = startDate.AddMonths(1).AddDays(-1); | |
| 57 | + var endDate = startDate.AddMonths(1); // 下个月的第一天,用于 < 比较 | |
| 52 | 58 | |
| 53 | 59 | // 确定要生成的部门列表 |
| 54 | 60 | var departments = new List<string>(); |
| ... | ... | @@ -171,9 +177,20 @@ namespace NCC.Extend |
| 171 | 177 | /// </summary> |
| 172 | 178 | private async Task CalculateIncome(LqShareStatisticsTechDeptEntity entity, string deptName, DateTime startDate, DateTime endDate) |
| 173 | 179 | { |
| 174 | - // 1. 找到该科技部管辖的所有门店 | |
| 180 | + // 1. 通过组织名称找到组织ID | |
| 181 | + var organize = await _db.Queryable<OrganizeEntity>() | |
| 182 | + .Where(x => x.FullName == deptName && x.DeleteMark == null && x.EnabledMark == 1) | |
| 183 | + .FirstAsync(); | |
| 184 | + | |
| 185 | + if (organize == null) | |
| 186 | + { | |
| 187 | + entity.Income = 0; | |
| 188 | + return; | |
| 189 | + } | |
| 190 | + | |
| 191 | + // 2. 找到该科技部管辖的所有门店(通过组织ID) | |
| 175 | 192 | var stores = await _db.Queryable<LqMdxxEntity>() |
| 176 | - .Where(x => x.Kjb == deptName) | |
| 193 | + .Where(x => x.Kjb == organize.Id) | |
| 177 | 194 | .Select(x => x.Id) |
| 178 | 195 | .ToListAsync(); |
| 179 | 196 | |
| ... | ... | @@ -183,21 +200,57 @@ namespace NCC.Extend |
| 183 | 200 | return; |
| 184 | 201 | } |
| 185 | 202 | |
| 186 | - // 2. 统计这些门店的科美项目开单实付业绩 | |
| 203 | + // 3. 统计这些门店的科美项目开单实付业绩 | |
| 187 | 204 | // 需要关联 lq_kd_kdjlb 来获取门店信息 |
| 188 | - var kemeiIncome = await _db.Queryable<LqKdPxmxEntity, LqKdKdjlbEntity>((px, kd) => new JoinQueryInfos( | |
| 189 | - JoinType.Inner, px.Glkdbh == kd.Id)) | |
| 190 | - .Where((px, kd) => stores.Contains(kd.Djmd) && px.Yjsj >= startDate && px.Yjsj <= endDate && px.IsEffective == 1) | |
| 191 | - .Where((px, kd) => px.ItemCategory == "科美") | |
| 192 | - .SumAsync((px, kd) => px.TotalPrice); | |
| 193 | - | |
| 194 | - // 3. 减去对应的科美项目实退金额 | |
| 195 | - var kemeiRefund = await _db.Queryable<LqHytkJksyjEntity>() | |
| 196 | - .Where(x => stores.Contains(x.StoreId) && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1) | |
| 197 | - .Where(x => x.ItemCategory == "科美") | |
| 198 | - .SumAsync(x => x.Jksyj ?? 0); | |
| 199 | - | |
| 200 | - // 4. 结果 * 30% | |
| 205 | + decimal kemeiIncome = 0; | |
| 206 | + if (stores.Count > 0) | |
| 207 | + { | |
| 208 | + var storeIdsStr = string.Join("','", stores); | |
| 209 | + var kemeiIncomeSql = $@" | |
| 210 | + SELECT COALESCE(SUM(px.F_TotalPrice), 0) as Total | |
| 211 | + FROM lq_kd_pxmx px | |
| 212 | + INNER JOIN lq_kd_kdjlb kd ON px.glkdbh = kd.F_Id | |
| 213 | + WHERE kd.djmd IN ('{storeIdsStr}') | |
| 214 | + AND px.yjsj >= '{startDate:yyyy-MM-dd} 00:00:00' | |
| 215 | + AND px.yjsj < '{endDate:yyyy-MM-dd} 00:00:00' | |
| 216 | + AND px.F_IsEffective = 1 | |
| 217 | + AND px.F_ItemCategory = '科美'"; | |
| 218 | + var kemeiIncomeResult = await _db.Ado.SqlQueryAsync<dynamic>(kemeiIncomeSql); | |
| 219 | + if (kemeiIncomeResult != null && kemeiIncomeResult.Any()) | |
| 220 | + { | |
| 221 | + var first = kemeiIncomeResult.FirstOrDefault(); | |
| 222 | + if (first != null) | |
| 223 | + { | |
| 224 | + kemeiIncome = Convert.ToDecimal(first.Total ?? 0); | |
| 225 | + } | |
| 226 | + } | |
| 227 | + } | |
| 228 | + | |
| 229 | + // 4. 减去对应的科美项目实退金额 | |
| 230 | + decimal kemeiRefund = 0; | |
| 231 | + if (stores.Count > 0) | |
| 232 | + { | |
| 233 | + var storeIdsStr = string.Join("','", stores); | |
| 234 | + var kemeiRefundSql = $@" | |
| 235 | + SELECT COALESCE(SUM(jksyj), 0) as Total | |
| 236 | + FROM lq_hytk_jksyj | |
| 237 | + WHERE F_StoreId IN ('{storeIdsStr}') | |
| 238 | + AND tksj >= '{startDate:yyyy-MM-dd} 00:00:00' | |
| 239 | + AND tksj < '{endDate:yyyy-MM-dd} 00:00:00' | |
| 240 | + AND F_IsEffective = 1 | |
| 241 | + AND F_ItemCategory = '科美'"; | |
| 242 | + var kemeiRefundResult = await _db.Ado.SqlQueryAsync<dynamic>(kemeiRefundSql); | |
| 243 | + if (kemeiRefundResult != null && kemeiRefundResult.Any()) | |
| 244 | + { | |
| 245 | + var first = kemeiRefundResult.FirstOrDefault(); | |
| 246 | + if (first != null) | |
| 247 | + { | |
| 248 | + kemeiRefund = Convert.ToDecimal(first.Total ?? 0); | |
| 249 | + } | |
| 250 | + } | |
| 251 | + } | |
| 252 | + | |
| 253 | + // 5. 结果 * 30% | |
| 201 | 254 | entity.Income = (kemeiIncome - kemeiRefund) * 0.3m; |
| 202 | 255 | } |
| 203 | 256 | |
| ... | ... | @@ -206,26 +259,103 @@ namespace NCC.Extend |
| 206 | 259 | /// </summary> |
| 207 | 260 | private async Task CalculateCost(LqShareStatisticsTechDeptEntity entity, string deptName, DateTime startDate, DateTime endDate, string statisticsMonth) |
| 208 | 261 | { |
| 209 | - // 1. 成本-报销 (TODO: 需要确认报销分类的具体判定方式) | |
| 210 | - entity.CostReimbursement = 0; | |
| 211 | - | |
| 212 | - // 2. 成本-人工-科技部老师底薪 (TODO: 需要确认科技部老师工资表名和字段) | |
| 213 | - entity.CostTeacherBase = 0; | |
| 214 | - | |
| 215 | - // 3. 成本-人工-科技部手工费 | |
| 216 | - // 从消耗表中统计科技部老师的手工费 | |
| 217 | - // TODO: 需要确认如何识别科技部老师 | |
| 218 | - entity.CostTeacherManual = 0; | |
| 262 | + // 1. 通过组织名称找到组织ID | |
| 263 | + var organize = await _db.Queryable<OrganizeEntity>() | |
| 264 | + .Where(x => x.FullName == deptName && x.DeleteMark == null && x.EnabledMark == 1) | |
| 265 | + .FirstAsync(); | |
| 219 | 266 | |
| 220 | - // 4. 成本-人工-科技部开单提成 (TODO: 需要确认字段名) | |
| 221 | - entity.CostTeacherBillingComm = 0; | |
| 267 | + if (organize == null) | |
| 268 | + { | |
| 269 | + // 如果找不到组织,所有成本为0 | |
| 270 | + entity.CostReimbursement = 0; | |
| 271 | + entity.CostTeacherBase = 0; | |
| 272 | + entity.CostTeacherManual = 0; | |
| 273 | + entity.CostTeacherBillingComm = 0; | |
| 274 | + entity.CostTeacherConsumeComm = 0; | |
| 275 | + entity.CostGMBase = 0; | |
| 276 | + entity.CostGMComm = 0; | |
| 277 | + entity.CostTeacherExpertComm = 0; | |
| 278 | + entity.CostTeacherOvertime = 0; | |
| 279 | + entity.RewardTechDept = 0; | |
| 280 | + entity.CostOther1 = 0; | |
| 281 | + entity.CostOther2 = 0; | |
| 282 | + return; | |
| 283 | + } | |
| 222 | 284 | |
| 223 | - // 5. 成本-人工-科技部消耗提成 (TODO: 需要确认字段名) | |
| 224 | - entity.CostTeacherConsumeComm = 0; | |
| 285 | + // 2. 找到该科技部管辖的所有门店(通过组织ID) | |
| 286 | + var stores = await _db.Queryable<LqMdxxEntity>() | |
| 287 | + .Where(x => x.Kjb == organize.Id) | |
| 288 | + .Select(x => x.Id) | |
| 289 | + .ToListAsync(); | |
| 225 | 290 | |
| 226 | - // 6. 成本-人工-科技部总经理 (TODO: 需要确认科技部总经理工资表) | |
| 227 | - entity.CostGMBase = 0; | |
| 228 | - entity.CostGMComm = 0; | |
| 291 | + // 1. 成本-报销:筛选一级分类为"科技部费用"的申请,根据申请门店的归属区分一部/二部 | |
| 292 | + var reimbursementAmount = await _db.Queryable<LqReimbursementApplicationEntity, LqPurchaseRecordsEntity, LqReimbursementCategoryEntity>( | |
| 293 | + (app, purchase, category) => app.PurchaseRecordsId == purchase.Id && purchase.ReimbursementCategoryId == category.Id) | |
| 294 | + .Where((app, purchase, category) => | |
| 295 | + category.Level1Name == "科技部费用" | |
| 296 | + && app.ApprovalStatus == "已通过" | |
| 297 | + && stores.Contains(app.ApplicationStoreId) | |
| 298 | + && app.ApplicationTime >= startDate | |
| 299 | + && app.ApplicationTime <= endDate.AddDays(1)) | |
| 300 | + .SumAsync((app, purchase, category) => purchase.Amount); | |
| 301 | + | |
| 302 | + entity.CostReimbursement = reimbursementAmount; | |
| 303 | + | |
| 304 | + // 2. 成本-人工-科技部老师底薪:从科技部老师工资统计表统计 | |
| 305 | + // 需要筛选该科技部管理的门店的老师 | |
| 306 | + var teacherBaseSalary = await _db.Queryable<LqTechTeacherSalaryStatisticsEntity>() | |
| 307 | + .Where(x => x.StatisticsMonth == statisticsMonth | |
| 308 | + && stores.Contains(x.StoreId)) | |
| 309 | + .SumAsync(x => x.BaseSalary); | |
| 310 | + | |
| 311 | + entity.CostTeacherBase = teacherBaseSalary; | |
| 312 | + | |
| 313 | + // 3. 成本-人工-科技部手工费:从消耗表中统计科技部老师的手工费 | |
| 314 | + // 数据来源:lq_xh_kjbsyj(科技部老师业绩表) | |
| 315 | + // 条件:ItemCategory = 科美,根据门店归属区分一部/二部 | |
| 316 | + var teacherManualFee = await _db.Queryable<LqXhKjbsyjEntity>() | |
| 317 | + .Where(x => x.ItemCategory == "科美" | |
| 318 | + && stores.Contains(x.StoreId) | |
| 319 | + && x.Yjsj >= startDate | |
| 320 | + && x.Yjsj < endDate | |
| 321 | + && x.IsEffective == 1) | |
| 322 | + .SumAsync(x => x.LaborCost ?? 0); | |
| 323 | + | |
| 324 | + entity.CostTeacherManual = teacherManualFee; | |
| 325 | + | |
| 326 | + // 4. 成本-人工-科技部开单提成:从科技部老师工资统计表统计业绩提成金额 | |
| 327 | + var teacherBillingComm = await _db.Queryable<LqTechTeacherSalaryStatisticsEntity>() | |
| 328 | + .Where(x => x.StatisticsMonth == statisticsMonth | |
| 329 | + && stores.Contains(x.StoreId)) | |
| 330 | + .SumAsync(x => x.PerformanceCommissionAmount); | |
| 331 | + | |
| 332 | + entity.CostTeacherBillingComm = teacherBillingComm; | |
| 333 | + | |
| 334 | + // 5. 成本-人工-科技部消耗提成:从科技部老师工资统计表统计消耗提成金额 | |
| 335 | + var teacherConsumeComm = await _db.Queryable<LqTechTeacherSalaryStatisticsEntity>() | |
| 336 | + .Where(x => x.StatisticsMonth == statisticsMonth | |
| 337 | + && stores.Contains(x.StoreId)) | |
| 338 | + .SumAsync(x => x.ConsumeCommissionAmount); | |
| 339 | + | |
| 340 | + entity.CostTeacherConsumeComm = teacherConsumeComm; | |
| 341 | + | |
| 342 | + // 6. 成本-人工-科技部总经理底薪和提成 | |
| 343 | + // 直接根据工资统计表中的 F_Position 字段区分科技一部/二部,不需要通过用户表过滤 | |
| 344 | + // 科技部总经理底薪 | |
| 345 | + var gmBaseSalary = await _db.Queryable<LqTechGeneralManagerSalaryStatisticsEntity>() | |
| 346 | + .Where(x => x.StatisticsMonth == statisticsMonth | |
| 347 | + && x.Position == deptName) | |
| 348 | + .SumAsync(x => x.BaseSalary); | |
| 349 | + | |
| 350 | + entity.CostGMBase = gmBaseSalary; | |
| 351 | + | |
| 352 | + // 科技部总经理提成 | |
| 353 | + var gmComm = await _db.Queryable<LqTechGeneralManagerSalaryStatisticsEntity>() | |
| 354 | + .Where(x => x.StatisticsMonth == statisticsMonth | |
| 355 | + && x.Position == deptName) | |
| 356 | + .SumAsync(x => x.TotalCommission); | |
| 357 | + | |
| 358 | + entity.CostGMComm = gmComm; | |
| 229 | 359 | |
| 230 | 360 | // 保留字段 |
| 231 | 361 | entity.CostTeacherExpertComm = 0; |
| ... | ... | @@ -240,19 +370,19 @@ namespace NCC.Extend |
| 240 | 370 | /// </summary> |
| 241 | 371 | private void CalculateProfit(LqShareStatisticsTechDeptEntity entity) |
| 242 | 372 | { |
| 243 | - // 科技部利润 = 收入 - 成本报销 - 成本人工 - 其他 | |
| 244 | - var totalCost = entity.CostReimbursement | |
| 245 | - + entity.CostTeacherBase | |
| 246 | - + entity.CostTeacherManual | |
| 247 | - + entity.CostTeacherBillingComm | |
| 248 | - + entity.CostTeacherConsumeComm | |
| 249 | - + entity.CostTeacherExpertComm | |
| 250 | - + entity.CostTeacherOvertime | |
| 251 | - + entity.CostGMBase | |
| 252 | - + entity.CostGMComm | |
| 253 | - + entity.RewardTechDept | |
| 254 | - + entity.CostOther1 | |
| 255 | - + entity.CostOther2; | |
| 373 | + // 科技部利润 = 收入 - 成本报销 - 成本人工(底薪 + 手工 + 开单提成 + 消耗提成 + 专家提成 + 加班 + 总经理底薪 + 总经理提成) - 其他 | |
| 374 | + var totalCost = entity.CostReimbursement // 成本报销 | |
| 375 | + + entity.CostTeacherBase // 成本人工-底薪 | |
| 376 | + + entity.CostTeacherManual // 成本人工-手工 | |
| 377 | + + entity.CostTeacherBillingComm // 成本人工-开单提成 | |
| 378 | + + entity.CostTeacherConsumeComm // 成本人工-消耗提成 | |
| 379 | + + entity.CostTeacherExpertComm // 成本人工-专家提成 | |
| 380 | + + entity.CostTeacherOvertime // 成本人工-加班 | |
| 381 | + + entity.CostGMBase // 成本人工-总经理底薪 | |
| 382 | + + entity.CostGMComm // 成本人工-总经理提成 | |
| 383 | + + entity.CostOther1 // 其他1 | |
| 384 | + + entity.CostOther2; // 其他2 | |
| 385 | + // 注意:RewardTechDept(奖励-科技部)是保留字段,不计入成本 | |
| 256 | 386 | |
| 257 | 387 | entity.Profit = entity.Income - totalCost; |
| 258 | 388 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs
| ... | ... | @@ -194,7 +194,7 @@ namespace NCC.Extend |
| 194 | 194 | // 1.5 消耗业绩和项目数数据 (lq_xh_kjbsyj,关联lq_xh_hyhk获取时间) |
| 195 | 195 | var consumePerformanceList = await _db.Queryable<LqXhKjbsyjEntity, LqXhHyhkEntity>( |
| 196 | 196 | (kjbsyj, hyhk) => kjbsyj.Glkdbh == hyhk.Id && hyhk.IsEffective == 1) |
| 197 | - .Where((kjbsyj, hyhk) => kjbsyj.IsEffective == 1 | |
| 197 | + .Where((kjbsyj, hyhk) => kjbsyj.IsEffective == 1 | |
| 198 | 198 | && hyhk.Hksj >= startDate && hyhk.Hksj <= endDate.AddDays(1)) |
| 199 | 199 | .Select((kjbsyj, hyhk) => new |
| 200 | 200 | { |
| ... | ... | @@ -314,8 +314,8 @@ namespace NCC.Extend |
| 314 | 314 | .Sum(x => x.Kjblsyj ?? 0m); |
| 315 | 315 | salary.RefundAchievement = refundPerformance; |
| 316 | 316 | |
| 317 | - // 总业绩 | |
| 318 | - salary.TotalPerformance = salary.OrderAchievement + salary.ConsumeAchievement + salary.RefundAchievement; | |
| 317 | + // 总业绩 = 开单业绩 - 退卡业绩(退卡是扣除) | |
| 318 | + salary.TotalPerformance = salary.OrderAchievement - salary.RefundAchievement; | |
| 319 | 319 | |
| 320 | 320 | // 2.5 考勤数据 |
| 321 | 321 | var attendance = attendanceDict.ContainsKey(teacherId) ? attendanceDict[teacherId] : null; |
| ... | ... | @@ -630,8 +630,8 @@ namespace NCC.Extend |
| 630 | 630 | |
| 631 | 631 | // 7. 统计人头(按月份+客户去重) |
| 632 | 632 | var personCountRecords = await _db.Queryable<LqPersonTimesRecordEntity>() |
| 633 | - .Where(x => x.PersonType == "科技老师" | |
| 634 | - && x.WorkMonth == monthStr | |
| 633 | + .Where(x => x.PersonType == "科技老师" | |
| 634 | + && x.WorkMonth == monthStr | |
| 635 | 635 | && x.IsEffective == 1 |
| 636 | 636 | && teacherIds.Contains(x.PersonId)) |
| 637 | 637 | .ToListAsync(); | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
| ... | ... | @@ -248,6 +248,8 @@ namespace NCC.Extend.LqTkjlb |
| 248 | 248 | IsEffective = StatusEnum.有效.GetHashCode(), |
| 249 | 249 | CreateTIme = DateTime.Now, |
| 250 | 250 | ItemCategory = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == "61").Select(x => x.Qt2).FirstAsync(), |
| 251 | + PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == "61").Select(x => x.Fl3).FirstAsync(), | |
| 252 | + BeautyType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == "61").Select(x => x.BeautyType).FirstAsync(), | |
| 251 | 253 | }; |
| 252 | 254 | var pxmxResult = await _db.Insertable(pxmxentity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); |
| 253 | 255 | if (!(pxmxResult > 0)) | ... | ... |
sql/同步业绩表门店ID字段.sql
| ... | ... | @@ -37,21 +37,27 @@ WHERE yj.glkdbh IS NOT NULL |
| 37 | 37 | |
| 38 | 38 | -- 3. 消耗健康师业绩表:通过glkdbh关联开单记录表获取门店ID |
| 39 | 39 | UPDATE lq_xh_jksyj yj |
| 40 | -INNER JOIN lq_kd_kdjlb kd ON yj.glkdbh = kd.F_Id | |
| 41 | -SET yj.F_StoreId = kd.djmd | |
| 42 | -WHERE yj.glkdbh IS NOT NULL | |
| 40 | +INNER JOIN lq_xh_hyhk xh ON yj.glkdbh = xh.F_Id | |
| 41 | +SET yj.F_StoreId = xh.md | |
| 42 | +WHERE (yj.F_StoreId IS NULL OR yj.F_StoreId = '') | |
| 43 | + AND yj.glkdbh IS NOT NULL | |
| 43 | 44 | AND yj.glkdbh != '' |
| 44 | - AND kd.djmd IS NOT NULL | |
| 45 | - AND kd.djmd != ''; | |
| 45 | + AND xh.F_Id IS NOT NULL | |
| 46 | + AND xh.md IS NOT NULL | |
| 47 | + AND xh.md != '' | |
| 48 | + AND xh.F_IsEffective = 1; | |
| 46 | 49 | |
| 47 | 50 | -- 4. 消耗科技老师业绩表:通过glkdbh关联开单记录表获取门店ID |
| 48 | 51 | UPDATE lq_xh_kjbsyj yj |
| 49 | -INNER JOIN lq_kd_kdjlb kd ON yj.glkdbh = kd.F_Id | |
| 50 | -SET yj.F_StoreId = kd.djmd | |
| 51 | -WHERE yj.glkdbh IS NOT NULL | |
| 52 | +INNER JOIN lq_xh_hyhk xh ON yj.glkdbh = xh.F_Id | |
| 53 | +SET yj.F_StoreId = xh.md | |
| 54 | +WHERE (yj.F_StoreId IS NULL OR yj.F_StoreId = '') | |
| 55 | + AND yj.glkdbh IS NOT NULL | |
| 52 | 56 | AND yj.glkdbh != '' |
| 53 | - AND kd.djmd IS NOT NULL | |
| 54 | - AND kd.djmd != ''; | |
| 57 | + AND xh.F_Id IS NOT NULL | |
| 58 | + AND xh.md IS NOT NULL | |
| 59 | + AND xh.md != '' | |
| 60 | + AND xh.F_IsEffective = 1; | |
| 55 | 61 | |
| 56 | 62 | -- 5. 退卡健康师业绩表:通过gltkbh关联退卡表获取门店ID |
| 57 | 63 | UPDATE lq_hytk_jksyj yj | ... | ... |
sql/排查生美业绩统计差异-简化版.sql
sql/排查生美业绩统计差异详细.sql
sql/检查生美业绩统计差异.sql
sql/添加合作成本表成本类型字段.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 添加合作成本表成本类型字段 | |
| 3 | +-- ============================================ | |
| 4 | +-- 说明: | |
| 5 | +-- 1. 在 lq_cooperation_cost 表中添加 F_CostType 字段 | |
| 6 | +-- 2. 字段类型:varchar(50),允许为空 | |
| 7 | +-- 3. 字段说明:成本类型 | |
| 8 | +-- ============================================ | |
| 9 | + | |
| 10 | +ALTER TABLE `lq_cooperation_cost` | |
| 11 | +ADD COLUMN `F_CostType` varchar(50) DEFAULT NULL COMMENT '成本类型' AFTER `F_Remarks`; | |
| 12 | + | ... | ... |
test_tianwang_api.py
科技部老师工资计算规则.md
| ... | ... | @@ -65,21 +65,24 @@ |
| 65 | 65 | |
| 66 | 66 | ### 总业绩(TotalPerformance) |
| 67 | 67 | |
| 68 | -**定义**:开单业绩 + 消耗业绩 + 退卡业绩 | |
| 68 | +**定义**:开单业绩 - 退卡业绩(退卡是扣除) | |
| 69 | 69 | |
| 70 | 70 | **数据来源表及字段**: |
| 71 | 71 | |
| 72 | 72 | | 业绩类型 | 数据表 | 字段 | 说明 | |
| 73 | 73 | |---------|--------|------|------| |
| 74 | 74 | | **开单业绩** | `lq_kd_kjbsyj` | `kjblsyj` | 科技部老师开单业绩 | |
| 75 | -| **消耗业绩** | `lq_xh_kjbsyj` | `kjblsyj` | 科技部老师消耗业绩 | | |
| 76 | -| **退卡业绩** | `lq_hytk_kjbsyj` | `kjblsyj` | 科技部老师退卡业绩 | | |
| 75 | +| **退卡业绩** | `lq_hytk_kjbsyj` | `kjblsyj` | 科技部老师退卡业绩(扣除项) | | |
| 77 | 76 | |
| 78 | 77 | **计算方式**: |
| 79 | 78 | ```sql |
| 80 | -总业绩 = SUM(开单业绩) + SUM(消耗业绩) + SUM(退卡业绩) | |
| 79 | +总业绩 = SUM(开单业绩) - SUM(退卡业绩) | |
| 81 | 80 | ``` |
| 82 | 81 | |
| 82 | +**注意**: | |
| 83 | +- 消耗业绩不包含在总业绩中 | |
| 84 | +- 退卡业绩是扣除项,所以用减法 | |
| 85 | + | |
| 83 | 86 | **过滤条件**: |
| 84 | 87 | - 所有表记录必须满足:`F_IsEffective = 1`(有效记录) |
| 85 | 88 | - 按统计月份(YYYYMM格式)过滤时间范围 |
| ... | ... | @@ -171,10 +174,10 @@ WHERE lq_xh_kjbsyj.F_IsEffective = 1 |
| 171 | 174 | |
| 172 | 175 | ### 步骤3:数据汇总(在职员工) |
| 173 | 176 | - 查询开单业绩:从 `lq_kd_kjbsyj` 表汇总 `kjblsyj` |
| 174 | -- 查询消耗业绩:从 `lq_xh_kjbsyj` 表汇总 `kjblsyj` | |
| 175 | -- 查询退卡业绩:从 `lq_hytk_kjbsyj` 表汇总 `kjblsyj` | |
| 177 | +- 查询消耗业绩:从 `lq_xh_kjbsyj` 表汇总 `kjblsyj`(用于消耗提成计算) | |
| 178 | +- 查询退卡业绩:从 `lq_hytk_kjbsyj` 表汇总 `kjblsyj`(扣除项) | |
| 176 | 179 | - 查询项目数:从 `lq_xh_kjbsyj` 表汇总 `F_hdpxNumber` |
| 177 | -- 计算总业绩:开单业绩 + 消耗业绩 + 退卡业绩 | |
| 180 | +- 计算总业绩:开单业绩 - 退卡业绩(退卡是扣除) | |
| 178 | 181 | |
| 179 | 182 | ### 步骤4:工资计算(在职员工) |
| 180 | 183 | |
| ... | ... | @@ -277,7 +280,8 @@ GROUP BY kjbsyj.kjblszh, kjbsyj.kjblsxm, hyhk.md, md.mdbm, md.dm |
| 277 | 280 | - 离职员工规则优先级最高 |
| 278 | 281 | |
| 279 | 282 | 3. **数据一致性**: |
| 280 | - - 总业绩 = 开单业绩 + 消耗业绩 + 退卡业绩 | |
| 283 | + - 总业绩 = 开单业绩 - 退卡业绩(退卡是扣除) | |
| 284 | + - 消耗业绩不包含在总业绩中,仅用于消耗提成计算 | |
| 281 | 285 | - 消耗和项目数必须来自同一数据源(`lq_xh_kjbsyj`) |
| 282 | 286 | |
| 283 | 287 | --- | ... | ... |
项目信息-薪酬规则与名词解释.md
| ... | ... | @@ -39,8 +39,10 @@ |
| 39 | 39 | ### 提成相关 |
| 40 | 40 | 12. **T区提成**:门店总业绩 × 0.05 × 0.05 |
| 41 | 41 | 13. **战队提成**:按照战队总业绩得到提成比例(提成比例有固定规则表) |
| 42 | -14. **基础业绩提成**:基础业绩 × 0.95 × 提成点 | |
| 43 | -15. **合作业绩提成**:合作业绩 × 0.95 × 0.65 × 提成点 | |
| 42 | +14. **基础业绩提成**:基础业绩 × 0.95 × 提成点(剩余0.05归门店T区) | |
| 43 | +15. **合作业绩提成**:合作业绩 × 0.95 × 0.65 × 提成点(剩余0.05归门店T区) | |
| 44 | +16. **新客转化率提成**:新客业绩 × 转化率提成比例 × 0.95(剩余0.05归门店T区) | |
| 45 | +17. **升单业绩提成**:升单业绩 × 升单提点 × 0.95(剩余0.05归门店T区) | |
| 44 | 46 | |
| 45 | 47 | ### 考核相关 |
| 46 | 48 | 16. **主任、店长考核指标**:生命线-业绩、人头-固定、消耗-固定 |
| ... | ... | @@ -80,9 +82,19 @@ |
| 80 | 82 | - **0星**:月消耗未达到10000元 且 项目数未达到96个 → 底薪1800元 |
| 81 | 83 | - **特殊情况**:月消耗或项目数中有一个为0星(未达到最低标准),底薪为一星(2000元) |
| 82 | 84 | |
| 85 | +**底薪计算说明**: | |
| 86 | +- 底薪按日均计算:日均消耗 = 月消耗 / 当月天数,日均项目数 = 项目数 / 当月天数 | |
| 87 | +- 星级标准按日均值判断:日均消耗 ≥ 10000/当月天数 且 日均项目数 ≥ 96/当月天数 → 一星 | |
| 88 | +- 最终底薪 = (档位底薪 / 当月天数) × 在店天数 | |
| 89 | + | |
| 83 | 90 | #### 新店健康师薪酬规则 |
| 84 | 91 | **重要说明**:新店不考核消耗,健康师分成3个阶段: |
| 85 | 92 | |
| 93 | +**新店底薪规则**: | |
| 94 | +- 新店底薪计算时,仍然会计算消耗和项目数的星级 | |
| 95 | +- 但如果计算出的底薪 < 2000元,则保底为2000元(一星) | |
| 96 | +- 即:新店底薪最低为一星(2000元),不满足一星条件时按一星计算 | |
| 97 | + | |
| 86 | 98 | **第一阶段:新客转化阶段** |
| 87 | 99 | - 主要考核新客转化率 |
| 88 | 100 | - 提成主要来源于新客转化业绩 |
| ... | ... | @@ -90,6 +102,11 @@ |
| 90 | 102 | **第二阶段:升单人头阶段** |
| 91 | 103 | - 主要考核升单人头数 |
| 92 | 104 | - 提成主要来源于升单业绩 |
| 105 | +- **升单提点规则**: | |
| 106 | + - 升单人头数 >= 10:升单提点 = 12% | |
| 107 | + - 升单人头数 >= 7 且 < 10:升单提点 = 10% | |
| 108 | + - 升单人头数 >= 4 且 < 7:升单提点 = 7% | |
| 109 | + - 升单人头数 < 4:升单提点 = 0% | |
| 93 | 110 | |
| 94 | 111 | **第三阶段:业绩和项目数阶段** |
| 95 | 112 | - 无新客转化和升单人头提成 |
| ... | ... | @@ -124,9 +141,20 @@ |
| 124 | 141 | |
| 125 | 142 | ### 顾问提成规则 |
| 126 | 143 | |
| 144 | +#### 3人及以上战队 | |
| 127 | 145 | - 战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8% |
| 146 | + | |
| 147 | +#### 2人战队 | |
| 128 | 148 | - 战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3% |
| 129 | 149 | |
| 150 | +#### 单人战队 | |
| 151 | +- 单人或者一个人的战队就没有顾问,不计算顾问提成 | |
| 152 | + | |
| 153 | +**注意**: | |
| 154 | +- 如果战队人数3人以上,按照3人的规则来计算 | |
| 155 | +- "组员业绩"指除顾问外的其他成员业绩总和 | |
| 156 | +- 新店顾问不考核消耗 | |
| 157 | + | |
| 130 | 158 | ### 店助考核规则 |
| 131 | 159 | |
| 132 | 160 | 店助底薪规则 | ... | ... |