From 6af31905e774562f0a0ee16861bdce45d3b448d0 Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Thu, 18 Dec 2025 23:18:59 +0800 Subject: [PATCH] feat: 优化多个接口功能 --- excel/合作成本表.xlsx | Bin 10275 -> 0 bytes excel/合作成本表_测试导入.xlsx | Bin 0 -> 5120 bytes netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/ContractExpenseStatisticsInput.cs | 12 ++++-------- netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs | 5 +++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs | 5 +++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs | 5 +++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs | 5 +++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs | 5 +++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowReturnInput.cs | 7 +++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowSendInput.cs | 7 +++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs | 6 ++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_store/LqShareStatisticsStoreEntity.cs | 16 ++++++++++++++-- netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs | 30 ++++++++++++++++++------------ netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------- netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------- netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs | 7 ++++--- netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs | 25 +++++++++++++++++++++---- netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------- netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------- netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs | 10 +++++----- netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs | 2 ++ sql/同步业绩表门店ID字段.sql | 26 ++++++++++++++++---------- sql/排查生美业绩统计差异-简化版.sql | 4 ++++ sql/排查生美业绩统计差异详细.sql | 4 ++++ sql/检查生美业绩统计差异.sql | 4 ++++ sql/添加合作成本表成本类型字段.sql | 12 ++++++++++++ test_tianwang_api.py | 2 ++ 科技部老师工资计算规则.md | 20 ++++++++++++-------- 项目信息-薪酬规则与名词解释.md | 32 ++++++++++++++++++++++++++++++-- 30 files changed, 734 insertions(+), 199 deletions(-) create mode 100644 excel/合作成本表_测试导入.xlsx create mode 100644 sql/添加合作成本表成本类型字段.sql diff --git a/excel/合作成本表.xlsx b/excel/合作成本表.xlsx index 862d084..443d055 100644 Binary files a/excel/合作成本表.xlsx and b/excel/合作成本表.xlsx differ diff --git a/excel/合作成本表_测试导入.xlsx b/excel/合作成本表_测试导入.xlsx new file mode 100644 index 0000000..7b3bdaa Binary files /dev/null and b/excel/合作成本表_测试导入.xlsx differ diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/ContractExpenseStatisticsInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/ContractExpenseStatisticsInput.cs index 6a99c1a..f91d691 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/ContractExpenseStatisticsInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/ContractExpenseStatisticsInput.cs @@ -16,20 +16,16 @@ namespace NCC.Extend.Entitys.Dto.LqContract public string StoreId { get; set; } /// - /// 统计年份(必填) + /// 统计年份(可选) /// - [Required(ErrorMessage = "年份不能为空")] - [Range(2020, 2100, ErrorMessage = "年份必须在2020-2100之间")] [Display(Name = "年份")] - public int Year { get; set; } + public int? Year { get; set; } /// - /// 统计月份(必填,1-12) + /// 统计月份(可选,1-12) /// - [Required(ErrorMessage = "月份不能为空")] - [Range(1, 12, ErrorMessage = "月份必须在1-12之间")] [Display(Name = "月份")] - public int Month { get; set; } + public int? Month { get; set; } /// /// 分类列表(可选,不传则统计所有分类) diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs index ff835a3..eb64828 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs @@ -36,6 +36,11 @@ namespace NCC.Extend.Entitys.Dto.LqCooperationCost /// 备注说明 /// public string remarks { get; set; } + + /// + /// 成本类型 + /// + public string costType { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs index c2a3238..d62a465 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs @@ -43,6 +43,11 @@ namespace NCC.Extend.Entitys.Dto.LqCooperationCost public string remarks { get; set; } /// + /// 成本类型 + /// + public string costType { get; set; } + + /// /// 创建人ID /// public string createUser { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs index 75f2c85..5ab9935 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs @@ -43,6 +43,11 @@ namespace NCC.Extend.Entitys.Dto.LqCooperationCost public string remarks { get; set; } /// + /// 成本类型 + /// + public string costType { get; set; } + + /// /// 创建人ID /// public string createUser { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs index e71c08f..047f916 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs @@ -41,6 +41,11 @@ namespace NCC.Extend.Entitys.Dto.LqCooperationCost /// 备注说明 /// public string remarks { get; set; } + + /// + /// 成本类型 + /// + public string costType { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs index f570665..9505c65 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs @@ -86,6 +86,11 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb /// 付款医院 /// public string fkyy { get; set; } + + /// + /// 付款方式 + /// + public string paymentMethod { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowReturnInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowReturnInput.cs index d3b642a..f568ed0 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowReturnInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowReturnInput.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel.DataAnnotations; namespace NCC.Extend.Entitys.Dto.LqLaundryFlow @@ -37,6 +38,12 @@ namespace NCC.Extend.Entitys.Dto.LqLaundryFlow [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")] [Display(Name = "备注")] public string Remark { get; set; } + + /// + /// 送回时间(可选,如果不传则使用当前时间) + /// + [Display(Name = "送回时间")] + public DateTime? ReturnTime { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowSendInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowSendInput.cs index d647b0c..3157cda 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowSendInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowSendInput.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel.DataAnnotations; namespace NCC.Extend.Entitys.Dto.LqLaundryFlow @@ -45,6 +46,12 @@ namespace NCC.Extend.Entitys.Dto.LqLaundryFlow [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")] [Display(Name = "备注")] public string Remark { get; set; } + + /// + /// 送出时间(可选,如果不传则使用当前时间) + /// + [Display(Name = "送出时间")] + public DateTime? SendTime { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs index 53efc8d..5581a85 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs @@ -54,6 +54,12 @@ namespace NCC.Extend.Entitys.lq_cooperation_cost public string Remarks { get; set; } /// + /// 成本类型 + /// + [SugarColumn(ColumnName = "F_CostType")] + public string CostType { get; set; } + + /// /// 是否有效(1:有效 0:无效) /// [SugarColumn(ColumnName = "F_IsEffective")] diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_store/LqShareStatisticsStoreEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_store/LqShareStatisticsStoreEntity.cs index aaf45fe..58259c6 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_share_statistics_store/LqShareStatisticsStoreEntity.cs +++ b/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 public decimal SalaryBaseStoreManager { get; set; } /// - /// 人工工资-总经理/经理底薪 + /// 人工工资-总经理底薪 /// [SugarColumn(ColumnName = "F_SalaryBaseGeneralManager")] public decimal SalaryBaseGeneralManager { get; set; } /// + /// 人工工资-经理底薪 + /// + [SugarColumn(ColumnName = "F_SalaryBaseManager")] + public decimal SalaryBaseManager { get; set; } + + /// /// 人工工资-健康师提成 /// [SugarColumn(ColumnName = "F_SalaryCommissionHealthCoach")] @@ -213,12 +219,18 @@ namespace NCC.Extend.Entitys.lq_share_statistics_store public decimal SalaryCommissionStoreManager { get; set; } /// - /// 人工工资-总经理/经理提成 + /// 人工工资-总经理提成 /// [SugarColumn(ColumnName = "F_SalaryCommissionGeneralManager")] public decimal SalaryCommissionGeneralManager { get; set; } /// + /// 人工工资-经理提成 + /// + [SugarColumn(ColumnName = "F_SalaryCommissionManager")] + public decimal SalaryCommissionManager { get; set; } + + /// /// 人工工资-手工 /// [SugarColumn(ColumnName = "F_SalaryManual")] diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs index ffa460c..c46ff4e 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs @@ -278,21 +278,27 @@ namespace NCC.Extend } // 2.3 获取门店目标信息(门店生命线和阶段目标) + // 如果没有设置门店目标,则相关字段设为0(适用于新店未开张等情况) if (!storeTargetDict.ContainsKey(storeId)) { - throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算店助工资"); + // 门店目标未设置时,所有目标相关字段设为0 + salary.StoreLifeline = 0; + salary.Stage1TargetHeadCount = 0; + salary.Stage2TargetHeadCount = 0; + } + else + { + var storeTarget = storeTargetDict[storeId]; + salary.StoreLifeline = storeTarget.StoreLifeline; + + // 阶段目标设置(如果未设置,则奖励金额为0) + salary.Stage1TargetHeadCount = storeTarget.AssistantHeadcountTargetStage1 > 0 + ? (int)storeTarget.AssistantHeadcountTargetStage1 + : 0; + salary.Stage2TargetHeadCount = storeTarget.AssistantHeadcountTargetStage2 > 0 + ? (int)storeTarget.AssistantHeadcountTargetStage2 + : 0; } - - var storeTarget = storeTargetDict[storeId]; - salary.StoreLifeline = storeTarget.StoreLifeline; - - // 阶段目标设置(如果未设置,则奖励金额为0) - salary.Stage1TargetHeadCount = storeTarget.AssistantHeadcountTargetStage1 > 0 - ? (int)storeTarget.AssistantHeadcountTargetStage1 - : 0; - salary.Stage2TargetHeadCount = storeTarget.AssistantHeadcountTargetStage2 > 0 - ? (int)storeTarget.AssistantHeadcountTargetStage2 - : 0; // 2.4 计算门店业绩 decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs index 87e4070..2433119 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqContractService.cs @@ -1177,10 +1177,15 @@ namespace NCC.Extend /// /// 参数说明: /// - storeId: 门店ID(必填) - /// - year: 统计年份(必填) - /// - month: 统计月份(必填,1-12) + /// - year: 统计年份(可选,不传则统计所有年份) + /// - month: 统计月份(可选,1-12,不传则统计所有月份;如果只传月份不传年份,则使用当前年份) /// - categories: 分类列表(可选,不传则统计所有分类) /// + /// 时间范围说明: + /// - 如果同时提供年份和月份:统计指定月份的数据 + /// - 如果只提供年份:统计整年的数据 + /// - 如果都不提供:统计所有时间的数据 + /// /// 返回数据说明: /// - storeId: 门店ID /// - storeName: 门店名称 @@ -1203,16 +1208,42 @@ namespace NCC.Extend { try { - // 验证月份 - if (input.Month < 1 || input.Month > 12) + // 验证年份(如果提供了年份) + if (input.Year.HasValue && (input.Year < 2020 || input.Year > 2100)) + { + throw NCCException.Oh("年份必须在2020-2100之间"); + } + + // 验证月份(如果提供了月份) + if (input.Month.HasValue && (input.Month < 1 || input.Month > 12)) { throw NCCException.Oh("月份必须在1-12之间"); } - // 构建统计月份的开始和结束时间 - var statisticsMonth = new DateTime(input.Year, input.Month, 1); - var monthStart = statisticsMonth; - var monthEnd = statisticsMonth.AddMonths(1).AddDays(-1); + // 如果提供了月份但没有提供年份,使用当前年份 + if (input.Month.HasValue && !input.Year.HasValue) + { + input.Year = DateTime.Now.Year; + } + + // 构建时间范围 + DateTime? monthStart = null; + DateTime? monthEnd = null; + + if (input.Year.HasValue && input.Month.HasValue) + { + // 统计指定月份 + var statisticsMonth = new DateTime(input.Year.Value, input.Month.Value, 1); + monthStart = statisticsMonth; + monthEnd = statisticsMonth.AddMonths(1).AddDays(-1); + } + else if (input.Year.HasValue) + { + // 只提供了年份,统计整年 + monthStart = new DateTime(input.Year.Value, 1, 1); + monthEnd = new DateTime(input.Year.Value, 12, 31, 23, 59, 59); + } + // 如果年份和月份都没提供,monthStart 和 monthEnd 保持为 null,表示不按时间过滤 // 查询门店信息 var store = await _db.Queryable() @@ -1225,16 +1256,25 @@ namespace NCC.Extend throw NCCException.Oh("门店不存在"); } - // 查询该门店在指定月份的月租明细 - var details = await _db.Queryable( + // 查询该门店在指定时间范围的月租明细 + var detailsQuery = _db.Queryable( (detail, contract) => new JoinQueryInfos( JoinType.Inner, detail.ContractId == contract.Id)) .Where((detail, contract) => contract.StoreId == input.StoreId && contract.IsEffective == StatusEnum.有效.GetHashCode() && - detail.IsEffective == StatusEnum.有效.GetHashCode() && - detail.PaymentMonth >= monthStart && - detail.PaymentMonth <= monthEnd) + detail.IsEffective == StatusEnum.有效.GetHashCode()); + + // 如果提供了时间范围,则添加时间过滤条件 + if (monthStart.HasValue && monthEnd.HasValue) + { + detailsQuery = detailsQuery.Where((detail, contract) => + detail.PaymentMonth >= monthStart.Value && + detail.PaymentMonth <= monthEnd.Value); + } + + // 如果指定了分类,则添加分类过滤条件 + var details = await detailsQuery .WhereIF(input.Categories != null && input.Categories.Length > 0, (detail, contract) => contract.Category != null && input.Categories.Contains(contract.Category)) .Select((detail, contract) => new @@ -1289,8 +1329,8 @@ namespace NCC.Extend { storeId = input.StoreId, storeName = store.Dm ?? "", - year = input.Year, - month = input.Month, + year = input.Year ?? 0, + month = input.Month ?? 0, totalAmount = totalAmount, categoryDetails = categoryDetails }; diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs index ea062f6..f59cf88 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs @@ -121,6 +121,7 @@ namespace NCC.Extend.LqCooperationCost month = x.Month, totalAmount = x.TotalAmount, remarks = x.Remarks, + costType = x.CostType, createUser = x.CreateUser, createTime = x.CreateTime, updateUser = x.UpdateUser, @@ -185,6 +186,7 @@ namespace NCC.Extend.LqCooperationCost month = x.Month, totalAmount = x.TotalAmount, remarks = x.Remarks, + costType = x.CostType, createUser = x.CreateUser, createTime = x.CreateTime, updateUser = x.UpdateUser, @@ -243,6 +245,7 @@ namespace NCC.Extend.LqCooperationCost entity.Month = input.month; entity.TotalAmount = input.totalAmount; entity.Remarks = input.remarks; + entity.CostType = input.costType; entity.UpdateUser = _userManager.UserId; entity.UpdateTime = DateTime.Now; @@ -301,7 +304,7 @@ namespace NCC.Extend.LqCooperationCost { exportData = await this.GetNoPagingList(input); } - List 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(); + List 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(); ExcelConfig excelconfig = new ExcelConfig(); excelconfig.FileName = "合作成本表.xls"; excelconfig.HeadFont = "微软雅黑"; @@ -329,15 +332,78 @@ namespace NCC.Extend.LqCooperationCost } /// + /// 下载导入模板 + /// + /// + /// 下载合作成本表导入模板Excel文件 + /// + /// Excel格式: + /// 第一行为标题行:门店名称、年份、月份、合计金额、成本类型、备注说明 + /// + /// 模板文件下载信息 + [HttpGet("Actions/TemplateDownload")] + public dynamic TemplateDownload() + { + try + { + // 创建模板数据(只有标题行) + var templateData = new List> + { + new Dictionary + { + { "storeName", "" }, + { "year", "" }, + { "month", "" }, + { "totalAmount", "" }, + { "costType", "" }, + { "remarks", "" } + } + }; + + ExcelConfig excelconfig = new ExcelConfig(); + excelconfig.FileName = "合作成本表模板.xlsx"; + excelconfig.HeadFont = "微软雅黑"; + excelconfig.HeadPoint = 10; + excelconfig.IsAllSizeColumn = true; + excelconfig.ColumnModel = new List + { + new ExcelColumnModel { Column = "storeName", ExcelColumn = "门店名称" }, + new ExcelColumnModel { Column = "year", ExcelColumn = "年份" }, + new ExcelColumnModel { Column = "month", ExcelColumn = "月份" }, + new ExcelColumnModel { Column = "totalAmount", ExcelColumn = "合计金额" }, + new ExcelColumnModel { Column = "costType", ExcelColumn = "成本类型" }, + new ExcelColumnModel { Column = "remarks", ExcelColumn = "备注说明" } + }; + + var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName; + var columnList = new List { "门店名称", "年份", "月份", "合计金额", "成本类型", "备注说明" }; + ExcelExportHelper>.Export(templateData, excelconfig, addPath, columnList); + + var fileName = _userManager.UserId + "|" + addPath + "|xlsx"; + return new + { + name = excelconfig.FileName, + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"生成模板失败:{ex.Message}"); + } + } + + /// /// 导入合作成本数据 /// /// /// 从Excel文件导入合作成本数据 /// /// Excel格式要求: - /// 第一行为标题行:门店ID、门店名称、年份、月份、合计金额、备注说明 + /// 第一行为标题行:门店名称、年份、月份、合计金额、成本类型、备注说明 /// 从第二行开始为数据行 /// + /// 注意:导入时通过门店名称查找门店ID,不需要填写门店ID + /// /// 示例请求: /// POST /api/Extend/LqCooperationCost/Actions/Import /// Content-Type: multipart/form-data @@ -391,27 +457,43 @@ namespace NCC.Extend.LqCooperationCost try { var row = dataTable.Rows[i]; - var storeId = row[0]?.ToString()?.Trim(); - var storeName = row[1]?.ToString()?.Trim(); - var yearText = row[2]?.ToString()?.Trim(); - var monthText = row[3]?.ToString()?.Trim(); - var totalAmountText = row[4]?.ToString()?.Trim(); + // Excel列顺序:门店名称、年份、月份、合计金额、成本类型、备注说明 + var storeName = row[0]?.ToString()?.Trim(); + var yearText = row[1]?.ToString()?.Trim(); + var monthText = row[2]?.ToString()?.Trim(); + var totalAmountText = row[3]?.ToString()?.Trim(); + var costType = row[4]?.ToString()?.Trim(); var remarks = row[5]?.ToString()?.Trim(); // 跳过空行 - if (string.IsNullOrEmpty(storeId) && string.IsNullOrEmpty(storeName)) + if (string.IsNullOrEmpty(storeName)) + { + continue; + } + + // 验证必填字段:门店名称 + if (string.IsNullOrEmpty(storeName)) { + failMessages.Add($"第{i + 1}行:门店名称不能为空"); + failCount++; continue; } - // 验证必填字段 - if (string.IsNullOrEmpty(storeId)) + // 根据门店名称查找门店ID + var store = await _db.Queryable() + .Where(x => x.Dm == storeName) + .FirstAsync(); + + if (store == null) { - failMessages.Add($"第{i + 1}行:门店ID不能为空"); + failMessages.Add($"第{i + 1}行:找不到门店名称'{storeName}'对应的门店"); failCount++; continue; } + var storeId = store.Id; + + // 验证年份 if (string.IsNullOrEmpty(yearText) || !int.TryParse(yearText, out int year)) { failMessages.Add($"第{i + 1}行:年份格式错误(应为数字)"); @@ -419,6 +501,7 @@ namespace NCC.Extend.LqCooperationCost continue; } + // 验证月份 if (string.IsNullOrEmpty(monthText) || monthText.Length != 6) { failMessages.Add($"第{i + 1}行:月份格式错误(应为YYYYMM格式,如:202501)"); @@ -426,6 +509,7 @@ namespace NCC.Extend.LqCooperationCost continue; } + // 验证合计金额 if (string.IsNullOrEmpty(totalAmountText) || !decimal.TryParse(totalAmountText, out decimal totalAmount)) { failMessages.Add($"第{i + 1}行:合计金额格式错误(应为数字)"); @@ -433,16 +517,6 @@ namespace NCC.Extend.LqCooperationCost continue; } - // 如果未提供门店名称,根据门店ID查询 - if (string.IsNullOrEmpty(storeName)) - { - var store = await _db.Queryable() - .Where(x => x.Id == storeId) - .Select(x => x.Dm) - .FirstAsync(); - storeName = store ?? ""; - } - // 检查是否已存在相同门店、年份、月份的记录 var exists = await _db.Queryable() .Where(x => x.StoreId == storeId && x.Year == year && x.Month == monthText && x.IsEffective == StatusEnum.有效.GetHashCode()) @@ -464,6 +538,7 @@ namespace NCC.Extend.LqCooperationCost Year = year, Month = monthText, TotalAmount = totalAmount, + CostType = costType, Remarks = remarks, IsEffective = StatusEnum.有效.GetHashCode(), CreateUser = _userManager.UserId, @@ -514,3 +589,4 @@ namespace NCC.Extend.LqCooperationCost } } + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs index 241d593..87aca0b 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs @@ -3802,11 +3802,11 @@ namespace NCC.Extend.LqKdKdjlb { var billings = await _db.Queryable() .Where(x => billingIds.Contains(x.Id)) - .Select(x => new { x.Id, x.Djmd, x.Hgjg, x.Fkyy }) + .Select(x => new { x.Id, x.Djmd, x.Hgjg, x.Fkyy, x.Fkfs }) .ToListAsync(); billingStoreDict = billings.ToDictionary(x => x.Id, x => x.Djmd ?? ""); - billingExtraInfoDict = billings.ToDictionary(x => x.Id, x => (dynamic)new { Hgjg = x.Hgjg, Fkyy = x.Fkyy }); + billingExtraInfoDict = billings.ToDictionary(x => x.Id, x => (dynamic)new { Hgjg = x.Hgjg, Fkyy = x.Fkyy, Fkfs = x.Fkfs }); } // 批量查询门店信息 @@ -3836,7 +3836,8 @@ namespace NCC.Extend.LqKdKdjlb storeId = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) ? billingStoreDict[pxmx.Glkdbh] : "", storeName = pxmx.Glkdbh != null && billingStoreDict.ContainsKey(pxmx.Glkdbh) && !string.IsNullOrEmpty(billingStoreDict[pxmx.Glkdbh]) && storeDict.ContainsKey(billingStoreDict[pxmx.Glkdbh]) ? storeDict[billingStoreDict[pxmx.Glkdbh]] : "", hgjg = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Hgjg : "", - fkyy = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Fkyy : "" + fkyy = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Fkyy : "", + paymentMethod = pxmx.Glkdbh != null && billingExtraInfoDict.ContainsKey(pxmx.Glkdbh) ? billingExtraInfoDict[pxmx.Glkdbh].Fkfs : "" }).ToList(); // 6. 返回分页结果 diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs index a17ec7d..ad9c813 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs @@ -57,9 +57,18 @@ namespace NCC.Extend /// "productType": "毛巾", /// "laundrySupplierId": "清洗商ID", /// "quantity": 100, - /// "remark": "备注" + /// "remark": "备注", + /// "sendTime": "2025-01-15T10:30:00" // 可选,如果不传则使用当前时间 /// } /// ``` + /// + /// 参数说明: + /// - storeId: 门店ID(必填) + /// - productType: 产品类型(必填) + /// - laundrySupplierId: 清洗商ID(必填) + /// - quantity: 送出数量(必填) + /// - remark: 备注(可选) + /// - sendTime: 送出时间(可选,如果不传则使用当前时间) /// /// 送出输入 /// 创建结果(包含批次号) @@ -116,7 +125,7 @@ namespace NCC.Extend IsEffective = StatusEnum.有效.GetHashCode(), CreateUser = _userManager.UserId, CreateTime = DateTime.Now, - SendTime = DateTime.Now // 设置送出时间 + SendTime = input.SendTime ?? DateTime.Now // 如果传入了送出时间则使用,否则使用当前时间 }; var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); @@ -145,9 +154,17 @@ namespace NCC.Extend /// "batchNumber": "批次号(对应的送出记录的ID)", /// "laundrySupplierId": "清洗商ID", /// "quantity": 95, - /// "remark": "5条损坏" + /// "remark": "5条损坏", + /// "returnTime": "2025-01-20T14:30:00" // 可选,如果不传则使用当前时间 /// } /// ``` + /// + /// 参数说明: + /// - batchNumber: 批次号(必填) + /// - laundrySupplierId: 清洗商ID(必填) + /// - quantity: 送回数量(必填) + /// - remark: 备注(可选) + /// - returnTime: 送回时间(可选,如果不传则使用当前时间) /// /// 送回输入 /// 创建结果 @@ -214,7 +231,7 @@ namespace NCC.Extend IsEffective = StatusEnum.有效.GetHashCode(), CreateUser = _userManager.UserId, CreateTime = DateTime.Now, - ReturnTime = DateTime.Now // 设置送回时间 + ReturnTime = input.ReturnTime ?? DateTime.Now // 如果传入了送回时间则使用,否则使用当前时间 }; var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs index 6f412e9..4a22406 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs @@ -327,18 +327,26 @@ namespace NCC.Extend // 1.8 批量获取员工信息 (BASE_USER + BASE_POSITION) // 使用 allEmployeeIds 作为驱动,查询 BASE_USER + // 重要:只统计岗位为"健康师"的员工 var userList = await _db.Queryable() - .Where(x => allEmployeeIds.Contains(x.Id)) + .Where(x => allEmployeeIds.Contains(x.Id) + && x.Gw == "健康师" // 只统计岗位为"健康师"的员工 + && x.DeleteMark == null + && x.EnabledMark == 1) .Select(x => new { x.Id, x.RealName, x.PositionId, x.Mdid }) .ToListAsync(); + // 过滤出健康师ID列表 + var healthCoachIds = userList.Select(x => x.Id).ToList(); + var userDict = userList.ToDictionary(x => x.Id, x => x); var positionIds = userList.Select(x => x.PositionId).Distinct().ToList(); var positionList = await _db.Queryable().Where(x => positionIds.Contains(x.Id)).ToListAsync(); var positionLookup = positionList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x.FullName); - foreach (var empId in allEmployeeIds) + // 只处理健康师员工 + foreach (var empId in allEmployeeIds.Where(x => healthCoachIds.Contains(x))) { var salary = new LqSalaryStatisticsEntity { @@ -671,6 +679,10 @@ namespace NCC.Extend // 计算新客转化率提成和升单人头提成(根据新店阶段) // isNewStore 和 newStoreStage 已在上面定义 + // 升单提点总是赋值(根据升单人头数) + decimal upgradeCommissionRate = GetUpgradeCommissionRate(salary.UpgradeCustomerCount); + salary.UpgradePoint = upgradeCommissionRate; + if (isNewStore) { if (newStoreStage == 1) @@ -695,10 +707,23 @@ namespace NCC.Extend // 注意:这里需要重新判断是否是顾问,因为可能被降级了 if (salary.Position == "顾问" && !string.IsNullOrEmpty(salary.GoldTriangleId)) { - salary.ConsultantCommission = CalculateConsultantCommission( - salary.TeamPerformance, - employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList(), - isNewStore); + // 获取战队人数 + var teamMemberList = employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList(); + var teamMemberCount = teamMemberList.Count; + + // 单人或者一个人的战队就没有顾问 + if (teamMemberCount > 1) + { + salary.ConsultantCommission = CalculateConsultantCommission( + salary.TeamPerformance, + teamMemberList, + teamMemberCount, + isNewStore); + } + else + { + salary.ConsultantCommission = 0; + } } // 计算门店T区提成 @@ -865,18 +890,25 @@ namespace NCC.Extend /// /// 计算顾问提成 /// - private decimal CalculateConsultantCommission(decimal teamPerformance, List teamMembers, bool isNewStore) + /// 战队总业绩 + /// 战队成员列表 + /// 战队人数 + /// 是否为新店 + /// 顾问提成金额 + private decimal CalculateConsultantCommission(decimal teamPerformance, List teamMembers, int teamMemberCount, bool isNewStore) { // 顾问提成规则: - // 高级顾问:战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8% - // 普通顾问:战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3% + // 战队人数3人:战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8% + // 战队人数2人:战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3% + // 如果3人以上,就按照3人的规则来 + // 单人或者一个人的战队就没有顾问(已在调用处处理) // 注意: // 1. "组员业绩"指除顾问外的其他成员业绩总和 // 2. 只统计有效战队成员(考勤≥20天,未被剔除的成员) // 3. "达到X%以上"指:组员业绩总和 ≥ 团队总业绩 × X% // 4. 新店顾问不考核消耗 - // 5. 消耗达标:高级顾问整组消耗>=6万,普通顾问整组消耗>=4万 + // 5. 消耗达标:3人战队整组消耗>=6万,2人战队整组消耗>=4万 // 使用传入的 teamMembers 计算总消耗 var teamConsumption = teamMembers.Sum(x => x.Consumption); @@ -884,28 +916,29 @@ namespace NCC.Extend // 计算组员(非顾问)业绩总和 var memberPerformance = teamMembers.Where(x => x.Position != "顾问").Sum(x => x.TotalPerformance); - // 高级顾问:业绩≥6万 且 组员业绩≥40% 且 (新店(第1,2阶段) 或 消耗≥6万) - // 注意:isNewStore 仅代表是否为新店,具体免考核阶段需确认。假设新店前两个阶段免考核,第三阶段需考核。 - // 这里暂且沿用 isNewStore 逻辑,如果需要更细粒度控制,应传入 NewStoreProtectionStage - // 如果 isNewStore 为 true,则默认免考核消耗(根据原需求描述:新店第3个阶段时,有金三角,但是不考核消耗) - // 用户最新指示:新店第3个阶段时,有金三角,但是不考核消耗,默认达标 -> 意味着只要是新店,不管阶段,都不考核消耗 - - if (teamPerformance >= 60000 && memberPerformance >= teamPerformance * 0.4m) + // 3人及以上战队:业绩≥6万 且 组员业绩≥40% 且 (新店 或 消耗≥6万) → 0.8% + if (teamMemberCount >= 3) { - if (isNewStore || teamConsumption >= 60000) + if (teamPerformance >= 60000 && memberPerformance >= teamPerformance * 0.4m) { - return teamPerformance * 0.008m; + if (isNewStore || teamConsumption >= 60000) + { + return teamPerformance * 0.008m; + } } } - - // 普通顾问:业绩≥4万 且 组员业绩≥30% 且 (新店 或 消耗≥4万) - if (teamPerformance >= 40000 && memberPerformance >= teamPerformance * 0.3m) + // 2人战队:业绩≥4万 且 组员业绩≥30% 且 (新店 或 消耗≥4万) → 0.3% + else if (teamMemberCount == 2) { - if (isNewStore || teamConsumption >= 40000) + if (teamPerformance >= 40000 && memberPerformance >= teamPerformance * 0.3m) { - return teamPerformance * 0.003m; + if (isNewStore || teamConsumption >= 40000) + { + return teamPerformance * 0.003m; + } } } + // 1人战队:没有顾问,返回0(已在调用处处理,这里不会执行到) return 0; } @@ -926,16 +959,23 @@ namespace NCC.Extend } /// + /// 获取升单提成比例 + /// + private decimal GetUpgradeCommissionRate(decimal upgradeCustomerCount) + { + if (upgradeCustomerCount >= 10) return 0.12m; // 大于等于10:12% + else if (upgradeCustomerCount >= 7 && upgradeCustomerCount < 10) return 0.10m; // 大于等于7且小于10:10% + else if (upgradeCustomerCount >= 4 && upgradeCustomerCount < 7) return 0.07m; // 大于等于4且小于7:7% + // 小于4:0% + return 0m; + } + + /// /// 计算升单人头提成 /// private decimal CalculateUpgradeCustomerCommission(decimal upgradePerformance, decimal upgradeCustomerCount) { - decimal commissionRate = 0; - - if (upgradeCustomerCount >= 10) commissionRate = 0.20m; - else if (upgradeCustomerCount >= 4) commissionRate = 0.10m; - // 0-4个: 0% - + decimal commissionRate = GetUpgradeCommissionRate(upgradeCustomerCount); return upgradePerformance * commissionRate; } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs index 33c4540..d97504e 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsStoreService.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using NCC.Common.Filter; using NCC.Dependency; using NCC.DynamicApiController; @@ -16,6 +17,7 @@ using NCC.Extend.Entitys.lq_director_salary_statistics; using NCC.Extend.Entitys.lq_store_manager_salary_statistics; using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics; using NCC.Extend.Entitys.lq_contract_rent_detail; +using NCC.Extend.Entitys.lq_contract_monthly_cost; using NCC.Extend.Entitys.lq_mdxx; using NCC.Extend.Entitys.lq_kd_pxmx; using NCC.Extend.Entitys.lq_product; @@ -38,10 +40,12 @@ namespace NCC.Extend public class LqShareStatisticsStoreService : IDynamicApiController, ITransient { private readonly ISqlSugarClient _db; + private readonly Microsoft.Extensions.Logging.ILogger _logger; - public LqShareStatisticsStoreService(ISqlSugarClient db) + public LqShareStatisticsStoreService(ISqlSugarClient db, Microsoft.Extensions.Logging.ILogger logger) { _db = db; + _logger = logger; } /// @@ -60,7 +64,7 @@ namespace NCC.Extend var year = int.Parse(input.StatisticsMonth.Substring(0, 4)); var month = int.Parse(input.StatisticsMonth.Substring(4, 2)); var startDate = new DateTime(year, month, 1); - var endDate = startDate.AddMonths(1).AddDays(-1); + var endDate = startDate.AddMonths(1).AddDays(-1).Date.AddHours(23).AddMinutes(59).AddSeconds(59); // 获取门店列表 var storeQuery = _db.Queryable(); @@ -346,13 +350,26 @@ namespace NCC.Extend && x.IsEffective == 1) .SumAsync(x => x.TotalPrice); - // 4. 科技部成本 = 科美业绩 * 30% - var kemeiPerformance = await _db.Queryable() - .Where(x => x.Glkdbh.StartsWith(entity.StoreId) && x.Yjsj >= startDate && x.Yjsj <= endDate && x.IsEffective == 1) - .Where(x => x.ItemCategory == "科美") - .SumAsync(x => x.TotalPrice); + // 4. 科技部成本 = (科美开单业绩 - 科美退款业绩) * 30% + // 4.1 统计科美开单业绩(通过关联开单表获取门店信息) + var kemeiBilling = await _db.Queryable((px, kd) => new JoinQueryInfos( + JoinType.Inner, px.Glkdbh == kd.Id)) + .Where((px, kd) => kd.Djmd == entity.StoreId + && px.Yjsj >= startDate && px.Yjsj <= endDate + && px.IsEffective == 1 + && px.ItemCategory == "科美") + .SumAsync((px, kd) => (decimal?)px.TotalPrice); + + // 4.2 统计科美退款业绩(从退卡健康师业绩表统计) + var kemeiRefund = await _db.Queryable() + .Where(x => x.StoreId == entity.StoreId + && x.Tksj >= startDate && x.Tksj <= endDate + && x.IsEffective == 1 + && x.ItemCategory == "科美") + .SumAsync(x => (decimal?)(x.Jksyj ?? 0)); - entity.CostTechDept = kemeiPerformance * 0.3m; + // 4.3 计算科技部成本 = (开单业绩 - 退款业绩) * 30% + entity.CostTechDept = ((kemeiBilling ?? 0m) - (kemeiRefund ?? 0m)) * 0.3m; // 5. 管理费 = 总业绩 * 9% var totalPerformance = await _db.Queryable() @@ -444,42 +461,131 @@ namespace NCC.Extend // 5. 总经理/经理底薪和提成 // 底薪按门店数平均分摊,提成需要从 F_StorePerformanceDetail JSON 中提取该门店的提成 - var gmSalary = await _db.Queryable() + var businessUnitSalaries = await _db.Queryable() .Where(x => x.StatisticsMonth == statisticsMonth) .Where(x => x.StorePerformanceDetail.Contains(entity.StoreId)) .ToListAsync(); decimal gmBaseSalary = 0; decimal gmCommission = 0; + decimal managerBaseSalary = 0; + decimal managerCommission = 0; - foreach (var gm in gmSalary) + foreach (var salaryRecord in businessUnitSalaries) { // 底薪平均分摊 - if (!string.IsNullOrEmpty(gm.StorePerformanceDetail)) + if (!string.IsNullOrEmpty(salaryRecord.StorePerformanceDetail)) { try { - var storeDetails = Newtonsoft.Json.JsonConvert.DeserializeObject>>(gm.StorePerformanceDetail); + var storeDetails = Newtonsoft.Json.JsonConvert.DeserializeObject>>(salaryRecord.StorePerformanceDetail); var storeCount = storeDetails?.Count ?? 1; - gmBaseSalary += gm.BaseSalary / storeCount; - // 提取该门店的提成 var storeDetail = storeDetails?.FirstOrDefault(s => s.ContainsKey("StoreId") && s["StoreId"].ToString() == entity.StoreId); - if (storeDetail != null && storeDetail.ContainsKey("Commission")) + if (storeDetail != null) { - gmCommission += Convert.ToDecimal(storeDetail["Commission"]); + // 根据ManagerType区分总经理和经理 + if (salaryRecord.ManagerType == 1) // 总经理 + { + gmBaseSalary += salaryRecord.BaseSalary / storeCount; + + // 提取该门店的提成(JSON字段名是CommissionAmount) + object commissionValue = null; + if (storeDetail.ContainsKey("CommissionAmount")) + { + commissionValue = storeDetail["CommissionAmount"]; + } + else + { + // 尝试不区分大小写查找 + var key = storeDetail.Keys.FirstOrDefault(k => k.Equals("CommissionAmount", StringComparison.OrdinalIgnoreCase)); + if (key != null) + { + commissionValue = storeDetail[key]; + } + } + + if (commissionValue != null) + { + // JSON反序列化后,数值类型可能是long或double,需要先转换为decimal + decimal commission = 0; + if (commissionValue is long longValue) + { + commission = longValue; + } + else if (commissionValue is double doubleValue) + { + commission = (decimal)doubleValue; + } + else if (commissionValue is decimal decimalValue) + { + commission = decimalValue; + } + else + { + commission = Convert.ToDecimal(commissionValue); + } + gmCommission += commission; + } + } + else if (salaryRecord.ManagerType == 0) // 经理 + { + managerBaseSalary += salaryRecord.BaseSalary / storeCount; + + // 提取该门店的提成(JSON字段名是CommissionAmount) + object commissionValue = null; + if (storeDetail.ContainsKey("CommissionAmount")) + { + commissionValue = storeDetail["CommissionAmount"]; + } + else + { + // 尝试不区分大小写查找 + var key = storeDetail.Keys.FirstOrDefault(k => k.Equals("CommissionAmount", StringComparison.OrdinalIgnoreCase)); + if (key != null) + { + commissionValue = storeDetail[key]; + } + } + + if (commissionValue != null) + { + // JSON反序列化后,数值类型可能是long或double,需要先转换为decimal + decimal commission = 0; + if (commissionValue is long longValue) + { + commission = longValue; + } + else if (commissionValue is double doubleValue) + { + commission = (decimal)doubleValue; + } + else if (commissionValue is decimal decimalValue) + { + commission = decimalValue; + } + else + { + commission = Convert.ToDecimal(commissionValue); + } + managerCommission += commission; + } + } } } - catch + catch (Exception ex) { - // JSON 解析失败,使用默认分摊 - gmBaseSalary += gm.BaseSalary; + _logger.LogError(ex, $"解析门店业绩明细JSON失败,使用默认分摊。BatchId: {salaryRecord.Id}"); + // JSON 解析失败,使用默认分摊到总经理 + gmBaseSalary += salaryRecord.BaseSalary; } } } entity.SalaryBaseGeneralManager = gmBaseSalary; entity.SalaryCommissionGeneralManager = gmCommission; + entity.SalaryBaseManager = managerBaseSalary; + entity.SalaryCommissionManager = managerCommission; // 保留字段 entity.SalaryAttendance = 0; @@ -490,22 +596,22 @@ namespace NCC.Extend /// private async Task CalculateExpense(LqShareStatisticsStoreEntity entity, DateTime startDate, DateTime endDate, string statisticsMonth) { - // 1. 门店房租 - 需要通过合同关联门店 - // 从合同表关联租金明细 + // 1. 门店房租 - 从合同成本按月统计表获取 // 解析统计月份 var year = int.Parse(statisticsMonth.Substring(0, 4)); var month = int.Parse(statisticsMonth.Substring(4, 2)); + var monthDate = new DateTime(year, month, 1); - var rentDetail = await _db.Queryable((rd, c) => new JoinQueryInfos( - JoinType.Inner, rd.ContractId == c.Id)) - .Where((rd, c) => c.StoreId == entity.StoreId - && rd.PaymentMonth.Year == year - && rd.PaymentMonth.Month == month - && rd.IsEffective == 1) - .Select((rd, c) => rd.DueAmount) - .ToListAsync(); + // 从 lq_contract_monthly_cost 表统计分类为"门店"的月度成本 + var storeRent = await _db.Queryable() + .Where(x => x.StoreId == entity.StoreId + && x.Month.Year == year + && x.Month.Month == month + && x.Category == "门店" + && x.IsEffective == 1) + .SumAsync(x => (decimal?)x.MonthlyCost); - entity.StoreRent = rentDetail.Sum(); + entity.StoreRent = storeRent ?? 0; // 2. 当期费用 = 报销费用中一级分类为“当期费用”的已审核通过记录 // 只统计:报销申请审批通过 + 申请门店=当前门店 + 申请时间在当月 @@ -541,9 +647,9 @@ namespace NCC.Extend { // 人工工资汇总 var totalSalary = entity.SalaryBaseHealthCoach + entity.SalaryBaseAssistant + entity.SalaryBaseDirector - + entity.SalaryBaseStoreManager + entity.SalaryBaseGeneralManager + + entity.SalaryBaseStoreManager + entity.SalaryBaseGeneralManager + entity.SalaryBaseManager + entity.SalaryCommissionHealthCoach + entity.SalaryCommissionAssistant + entity.SalaryCommissionDirector - + entity.SalaryCommissionStoreManager + entity.SalaryCommissionGeneralManager + + entity.SalaryCommissionStoreManager + entity.SalaryCommissionGeneralManager + entity.SalaryCommissionManager + entity.SalaryManual + entity.SalaryAttendance + entity.SalaryPhoneCustody + entity.SalaryHeadcountReward + entity.SalaryTZone; diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs index 974123a..fee903b 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsTechDeptService.cs @@ -9,6 +9,12 @@ using NCC.Extend.Entitys.lq_kd_pxmx; using NCC.Extend.Entitys.lq_kd_kdjlb; using NCC.Extend.Entitys.lq_hytk_jksyj; using NCC.Extend.Entitys.lq_xh_jksyj; +using NCC.Extend.Entitys.lq_xh_kjbsyj; +using NCC.Extend.Entitys.lq_tech_teacher_salary_statistics; +using NCC.Extend.Entitys.lq_tech_general_manager_salary_statistics; +using NCC.Extend.Entitys.lq_md_general_manager_lifeline; +using NCC.System.Entitys.Permission; +using NCC.Extend.Entitys; using SqlSugar; using System; using System.Collections.Generic; @@ -48,7 +54,7 @@ namespace NCC.Extend var year = int.Parse(input.StatisticsMonth.Substring(0, 4)); var month = int.Parse(input.StatisticsMonth.Substring(4, 2)); var startDate = new DateTime(year, month, 1); - var endDate = startDate.AddMonths(1).AddDays(-1); + var endDate = startDate.AddMonths(1); // 下个月的第一天,用于 < 比较 // 确定要生成的部门列表 var departments = new List(); @@ -171,9 +177,20 @@ namespace NCC.Extend /// private async Task CalculateIncome(LqShareStatisticsTechDeptEntity entity, string deptName, DateTime startDate, DateTime endDate) { - // 1. 找到该科技部管辖的所有门店 + // 1. 通过组织名称找到组织ID + var organize = await _db.Queryable() + .Where(x => x.FullName == deptName && x.DeleteMark == null && x.EnabledMark == 1) + .FirstAsync(); + + if (organize == null) + { + entity.Income = 0; + return; + } + + // 2. 找到该科技部管辖的所有门店(通过组织ID) var stores = await _db.Queryable() - .Where(x => x.Kjb == deptName) + .Where(x => x.Kjb == organize.Id) .Select(x => x.Id) .ToListAsync(); @@ -183,21 +200,57 @@ namespace NCC.Extend return; } - // 2. 统计这些门店的科美项目开单实付业绩 + // 3. 统计这些门店的科美项目开单实付业绩 // 需要关联 lq_kd_kdjlb 来获取门店信息 - var kemeiIncome = await _db.Queryable((px, kd) => new JoinQueryInfos( - JoinType.Inner, px.Glkdbh == kd.Id)) - .Where((px, kd) => stores.Contains(kd.Djmd) && px.Yjsj >= startDate && px.Yjsj <= endDate && px.IsEffective == 1) - .Where((px, kd) => px.ItemCategory == "科美") - .SumAsync((px, kd) => px.TotalPrice); - - // 3. 减去对应的科美项目实退金额 - var kemeiRefund = await _db.Queryable() - .Where(x => stores.Contains(x.StoreId) && x.Tksj >= startDate && x.Tksj <= endDate && x.IsEffective == 1) - .Where(x => x.ItemCategory == "科美") - .SumAsync(x => x.Jksyj ?? 0); - - // 4. 结果 * 30% + decimal kemeiIncome = 0; + if (stores.Count > 0) + { + var storeIdsStr = string.Join("','", stores); + var kemeiIncomeSql = $@" + SELECT COALESCE(SUM(px.F_TotalPrice), 0) as Total + FROM lq_kd_pxmx px + INNER JOIN lq_kd_kdjlb kd ON px.glkdbh = kd.F_Id + WHERE kd.djmd IN ('{storeIdsStr}') + AND px.yjsj >= '{startDate:yyyy-MM-dd} 00:00:00' + AND px.yjsj < '{endDate:yyyy-MM-dd} 00:00:00' + AND px.F_IsEffective = 1 + AND px.F_ItemCategory = '科美'"; + var kemeiIncomeResult = await _db.Ado.SqlQueryAsync(kemeiIncomeSql); + if (kemeiIncomeResult != null && kemeiIncomeResult.Any()) + { + var first = kemeiIncomeResult.FirstOrDefault(); + if (first != null) + { + kemeiIncome = Convert.ToDecimal(first.Total ?? 0); + } + } + } + + // 4. 减去对应的科美项目实退金额 + decimal kemeiRefund = 0; + if (stores.Count > 0) + { + var storeIdsStr = string.Join("','", stores); + var kemeiRefundSql = $@" + SELECT COALESCE(SUM(jksyj), 0) as Total + FROM lq_hytk_jksyj + WHERE F_StoreId IN ('{storeIdsStr}') + AND tksj >= '{startDate:yyyy-MM-dd} 00:00:00' + AND tksj < '{endDate:yyyy-MM-dd} 00:00:00' + AND F_IsEffective = 1 + AND F_ItemCategory = '科美'"; + var kemeiRefundResult = await _db.Ado.SqlQueryAsync(kemeiRefundSql); + if (kemeiRefundResult != null && kemeiRefundResult.Any()) + { + var first = kemeiRefundResult.FirstOrDefault(); + if (first != null) + { + kemeiRefund = Convert.ToDecimal(first.Total ?? 0); + } + } + } + + // 5. 结果 * 30% entity.Income = (kemeiIncome - kemeiRefund) * 0.3m; } @@ -206,26 +259,103 @@ namespace NCC.Extend /// private async Task CalculateCost(LqShareStatisticsTechDeptEntity entity, string deptName, DateTime startDate, DateTime endDate, string statisticsMonth) { - // 1. 成本-报销 (TODO: 需要确认报销分类的具体判定方式) - entity.CostReimbursement = 0; - - // 2. 成本-人工-科技部老师底薪 (TODO: 需要确认科技部老师工资表名和字段) - entity.CostTeacherBase = 0; - - // 3. 成本-人工-科技部手工费 - // 从消耗表中统计科技部老师的手工费 - // TODO: 需要确认如何识别科技部老师 - entity.CostTeacherManual = 0; + // 1. 通过组织名称找到组织ID + var organize = await _db.Queryable() + .Where(x => x.FullName == deptName && x.DeleteMark == null && x.EnabledMark == 1) + .FirstAsync(); - // 4. 成本-人工-科技部开单提成 (TODO: 需要确认字段名) - entity.CostTeacherBillingComm = 0; + if (organize == null) + { + // 如果找不到组织,所有成本为0 + entity.CostReimbursement = 0; + entity.CostTeacherBase = 0; + entity.CostTeacherManual = 0; + entity.CostTeacherBillingComm = 0; + entity.CostTeacherConsumeComm = 0; + entity.CostGMBase = 0; + entity.CostGMComm = 0; + entity.CostTeacherExpertComm = 0; + entity.CostTeacherOvertime = 0; + entity.RewardTechDept = 0; + entity.CostOther1 = 0; + entity.CostOther2 = 0; + return; + } - // 5. 成本-人工-科技部消耗提成 (TODO: 需要确认字段名) - entity.CostTeacherConsumeComm = 0; + // 2. 找到该科技部管辖的所有门店(通过组织ID) + var stores = await _db.Queryable() + .Where(x => x.Kjb == organize.Id) + .Select(x => x.Id) + .ToListAsync(); - // 6. 成本-人工-科技部总经理 (TODO: 需要确认科技部总经理工资表) - entity.CostGMBase = 0; - entity.CostGMComm = 0; + // 1. 成本-报销:筛选一级分类为"科技部费用"的申请,根据申请门店的归属区分一部/二部 + var reimbursementAmount = await _db.Queryable( + (app, purchase, category) => app.PurchaseRecordsId == purchase.Id && purchase.ReimbursementCategoryId == category.Id) + .Where((app, purchase, category) => + category.Level1Name == "科技部费用" + && app.ApprovalStatus == "已通过" + && stores.Contains(app.ApplicationStoreId) + && app.ApplicationTime >= startDate + && app.ApplicationTime <= endDate.AddDays(1)) + .SumAsync((app, purchase, category) => purchase.Amount); + + entity.CostReimbursement = reimbursementAmount; + + // 2. 成本-人工-科技部老师底薪:从科技部老师工资统计表统计 + // 需要筛选该科技部管理的门店的老师 + var teacherBaseSalary = await _db.Queryable() + .Where(x => x.StatisticsMonth == statisticsMonth + && stores.Contains(x.StoreId)) + .SumAsync(x => x.BaseSalary); + + entity.CostTeacherBase = teacherBaseSalary; + + // 3. 成本-人工-科技部手工费:从消耗表中统计科技部老师的手工费 + // 数据来源:lq_xh_kjbsyj(科技部老师业绩表) + // 条件:ItemCategory = 科美,根据门店归属区分一部/二部 + var teacherManualFee = await _db.Queryable() + .Where(x => x.ItemCategory == "科美" + && stores.Contains(x.StoreId) + && x.Yjsj >= startDate + && x.Yjsj < endDate + && x.IsEffective == 1) + .SumAsync(x => x.LaborCost ?? 0); + + entity.CostTeacherManual = teacherManualFee; + + // 4. 成本-人工-科技部开单提成:从科技部老师工资统计表统计业绩提成金额 + var teacherBillingComm = await _db.Queryable() + .Where(x => x.StatisticsMonth == statisticsMonth + && stores.Contains(x.StoreId)) + .SumAsync(x => x.PerformanceCommissionAmount); + + entity.CostTeacherBillingComm = teacherBillingComm; + + // 5. 成本-人工-科技部消耗提成:从科技部老师工资统计表统计消耗提成金额 + var teacherConsumeComm = await _db.Queryable() + .Where(x => x.StatisticsMonth == statisticsMonth + && stores.Contains(x.StoreId)) + .SumAsync(x => x.ConsumeCommissionAmount); + + entity.CostTeacherConsumeComm = teacherConsumeComm; + + // 6. 成本-人工-科技部总经理底薪和提成 + // 直接根据工资统计表中的 F_Position 字段区分科技一部/二部,不需要通过用户表过滤 + // 科技部总经理底薪 + var gmBaseSalary = await _db.Queryable() + .Where(x => x.StatisticsMonth == statisticsMonth + && x.Position == deptName) + .SumAsync(x => x.BaseSalary); + + entity.CostGMBase = gmBaseSalary; + + // 科技部总经理提成 + var gmComm = await _db.Queryable() + .Where(x => x.StatisticsMonth == statisticsMonth + && x.Position == deptName) + .SumAsync(x => x.TotalCommission); + + entity.CostGMComm = gmComm; // 保留字段 entity.CostTeacherExpertComm = 0; @@ -240,19 +370,19 @@ namespace NCC.Extend /// private void CalculateProfit(LqShareStatisticsTechDeptEntity entity) { - // 科技部利润 = 收入 - 成本报销 - 成本人工 - 其他 - var totalCost = entity.CostReimbursement - + entity.CostTeacherBase - + entity.CostTeacherManual - + entity.CostTeacherBillingComm - + entity.CostTeacherConsumeComm - + entity.CostTeacherExpertComm - + entity.CostTeacherOvertime - + entity.CostGMBase - + entity.CostGMComm - + entity.RewardTechDept - + entity.CostOther1 - + entity.CostOther2; + // 科技部利润 = 收入 - 成本报销 - 成本人工(底薪 + 手工 + 开单提成 + 消耗提成 + 专家提成 + 加班 + 总经理底薪 + 总经理提成) - 其他 + var totalCost = entity.CostReimbursement // 成本报销 + + entity.CostTeacherBase // 成本人工-底薪 + + entity.CostTeacherManual // 成本人工-手工 + + entity.CostTeacherBillingComm // 成本人工-开单提成 + + entity.CostTeacherConsumeComm // 成本人工-消耗提成 + + entity.CostTeacherExpertComm // 成本人工-专家提成 + + entity.CostTeacherOvertime // 成本人工-加班 + + entity.CostGMBase // 成本人工-总经理底薪 + + entity.CostGMComm // 成本人工-总经理提成 + + entity.CostOther1 // 其他1 + + entity.CostOther2; // 其他2 + // 注意:RewardTechDept(奖励-科技部)是保留字段,不计入成本 entity.Profit = entity.Income - totalCost; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs index 655a5f0..3118f50 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs @@ -194,7 +194,7 @@ namespace NCC.Extend // 1.5 消耗业绩和项目数数据 (lq_xh_kjbsyj,关联lq_xh_hyhk获取时间) var consumePerformanceList = await _db.Queryable( (kjbsyj, hyhk) => kjbsyj.Glkdbh == hyhk.Id && hyhk.IsEffective == 1) - .Where((kjbsyj, hyhk) => kjbsyj.IsEffective == 1 + .Where((kjbsyj, hyhk) => kjbsyj.IsEffective == 1 && hyhk.Hksj >= startDate && hyhk.Hksj <= endDate.AddDays(1)) .Select((kjbsyj, hyhk) => new { @@ -314,8 +314,8 @@ namespace NCC.Extend .Sum(x => x.Kjblsyj ?? 0m); salary.RefundAchievement = refundPerformance; - // 总业绩 - salary.TotalPerformance = salary.OrderAchievement + salary.ConsumeAchievement + salary.RefundAchievement; + // 总业绩 = 开单业绩 - 退卡业绩(退卡是扣除) + salary.TotalPerformance = salary.OrderAchievement - salary.RefundAchievement; // 2.5 考勤数据 var attendance = attendanceDict.ContainsKey(teacherId) ? attendanceDict[teacherId] : null; @@ -630,8 +630,8 @@ namespace NCC.Extend // 7. 统计人头(按月份+客户去重) var personCountRecords = await _db.Queryable() - .Where(x => x.PersonType == "科技老师" - && x.WorkMonth == monthStr + .Where(x => x.PersonType == "科技老师" + && x.WorkMonth == monthStr && x.IsEffective == 1 && teacherIds.Contains(x.PersonId)) .ToListAsync(); diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs index 962f059..1a79ae3 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs @@ -248,6 +248,8 @@ namespace NCC.Extend.LqTkjlb IsEffective = StatusEnum.有效.GetHashCode(), CreateTIme = DateTime.Now, ItemCategory = await _db.Queryable().Where(x => x.Id == "61").Select(x => x.Qt2).FirstAsync(), + PerformanceType = await _db.Queryable().Where(x => x.Id == "61").Select(x => x.Fl3).FirstAsync(), + BeautyType = await _db.Queryable().Where(x => x.Id == "61").Select(x => x.BeautyType).FirstAsync(), }; var pxmxResult = await _db.Insertable(pxmxentity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); if (!(pxmxResult > 0)) diff --git a/sql/同步业绩表门店ID字段.sql b/sql/同步业绩表门店ID字段.sql index 5b0ab4a..6709ebb 100644 --- a/sql/同步业绩表门店ID字段.sql +++ b/sql/同步业绩表门店ID字段.sql @@ -37,21 +37,27 @@ WHERE yj.glkdbh IS NOT NULL -- 3. 消耗健康师业绩表:通过glkdbh关联开单记录表获取门店ID UPDATE lq_xh_jksyj yj -INNER JOIN lq_kd_kdjlb kd ON yj.glkdbh = kd.F_Id -SET yj.F_StoreId = kd.djmd -WHERE yj.glkdbh IS NOT NULL +INNER JOIN lq_xh_hyhk xh ON yj.glkdbh = xh.F_Id +SET yj.F_StoreId = xh.md +WHERE (yj.F_StoreId IS NULL OR yj.F_StoreId = '') + AND yj.glkdbh IS NOT NULL AND yj.glkdbh != '' - AND kd.djmd IS NOT NULL - AND kd.djmd != ''; + AND xh.F_Id IS NOT NULL + AND xh.md IS NOT NULL + AND xh.md != '' + AND xh.F_IsEffective = 1; -- 4. 消耗科技老师业绩表:通过glkdbh关联开单记录表获取门店ID UPDATE lq_xh_kjbsyj yj -INNER JOIN lq_kd_kdjlb kd ON yj.glkdbh = kd.F_Id -SET yj.F_StoreId = kd.djmd -WHERE yj.glkdbh IS NOT NULL +INNER JOIN lq_xh_hyhk xh ON yj.glkdbh = xh.F_Id +SET yj.F_StoreId = xh.md +WHERE (yj.F_StoreId IS NULL OR yj.F_StoreId = '') + AND yj.glkdbh IS NOT NULL AND yj.glkdbh != '' - AND kd.djmd IS NOT NULL - AND kd.djmd != ''; + AND xh.F_Id IS NOT NULL + AND xh.md IS NOT NULL + AND xh.md != '' + AND xh.F_IsEffective = 1; -- 5. 退卡健康师业绩表:通过gltkbh关联退卡表获取门店ID UPDATE lq_hytk_jksyj yj diff --git a/sql/排查生美业绩统计差异-简化版.sql b/sql/排查生美业绩统计差异-简化版.sql index 20bf328..ab20c6b 100644 --- a/sql/排查生美业绩统计差异-简化版.sql +++ b/sql/排查生美业绩统计差异-简化版.sql @@ -120,3 +120,7 @@ ORDER BY 生美业绩 DESC; + + + + diff --git a/sql/排查生美业绩统计差异详细.sql b/sql/排查生美业绩统计差异详细.sql index 6039721..2342e33 100644 --- a/sql/排查生美业绩统计差异详细.sql +++ b/sql/排查生美业绩统计差异详细.sql @@ -172,3 +172,7 @@ HAVING COUNT(*) > 1; + + + + diff --git a/sql/检查生美业绩统计差异.sql b/sql/检查生美业绩统计差异.sql index 40ef5d1..b6b0e02 100644 --- a/sql/检查生美业绩统计差异.sql +++ b/sql/检查生美业绩统计差异.sql @@ -142,3 +142,7 @@ ORDER BY 生美业绩 DESC; + + + + diff --git a/sql/添加合作成本表成本类型字段.sql b/sql/添加合作成本表成本类型字段.sql new file mode 100644 index 0000000..671bbb0 --- /dev/null +++ b/sql/添加合作成本表成本类型字段.sql @@ -0,0 +1,12 @@ +-- ============================================ +-- 添加合作成本表成本类型字段 +-- ============================================ +-- 说明: +-- 1. 在 lq_cooperation_cost 表中添加 F_CostType 字段 +-- 2. 字段类型:varchar(50),允许为空 +-- 3. 字段说明:成本类型 +-- ============================================ + +ALTER TABLE `lq_cooperation_cost` +ADD COLUMN `F_CostType` varchar(50) DEFAULT NULL COMMENT '成本类型' AFTER `F_Remarks`; + diff --git a/test_tianwang_api.py b/test_tianwang_api.py index 3b1e749..a6c559b 100644 --- a/test_tianwang_api.py +++ b/test_tianwang_api.py @@ -75,3 +75,5 @@ print(f"\n教育一部+教育二部合计 BillingPerformance: {total2}") + + diff --git a/科技部老师工资计算规则.md b/科技部老师工资计算规则.md index 133bfbb..8e943fa 100644 --- a/科技部老师工资计算规则.md +++ b/科技部老师工资计算规则.md @@ -65,21 +65,24 @@ ### 总业绩(TotalPerformance) -**定义**:开单业绩 + 消耗业绩 + 退卡业绩 +**定义**:开单业绩 - 退卡业绩(退卡是扣除) **数据来源表及字段**: | 业绩类型 | 数据表 | 字段 | 说明 | |---------|--------|------|------| | **开单业绩** | `lq_kd_kjbsyj` | `kjblsyj` | 科技部老师开单业绩 | -| **消耗业绩** | `lq_xh_kjbsyj` | `kjblsyj` | 科技部老师消耗业绩 | -| **退卡业绩** | `lq_hytk_kjbsyj` | `kjblsyj` | 科技部老师退卡业绩 | +| **退卡业绩** | `lq_hytk_kjbsyj` | `kjblsyj` | 科技部老师退卡业绩(扣除项) | **计算方式**: ```sql -总业绩 = SUM(开单业绩) + SUM(消耗业绩) + SUM(退卡业绩) +总业绩 = SUM(开单业绩) - SUM(退卡业绩) ``` +**注意**: +- 消耗业绩不包含在总业绩中 +- 退卡业绩是扣除项,所以用减法 + **过滤条件**: - 所有表记录必须满足:`F_IsEffective = 1`(有效记录) - 按统计月份(YYYYMM格式)过滤时间范围 @@ -171,10 +174,10 @@ WHERE lq_xh_kjbsyj.F_IsEffective = 1 ### 步骤3:数据汇总(在职员工) - 查询开单业绩:从 `lq_kd_kjbsyj` 表汇总 `kjblsyj` -- 查询消耗业绩:从 `lq_xh_kjbsyj` 表汇总 `kjblsyj` -- 查询退卡业绩:从 `lq_hytk_kjbsyj` 表汇总 `kjblsyj` +- 查询消耗业绩:从 `lq_xh_kjbsyj` 表汇总 `kjblsyj`(用于消耗提成计算) +- 查询退卡业绩:从 `lq_hytk_kjbsyj` 表汇总 `kjblsyj`(扣除项) - 查询项目数:从 `lq_xh_kjbsyj` 表汇总 `F_hdpxNumber` -- 计算总业绩:开单业绩 + 消耗业绩 + 退卡业绩 +- 计算总业绩:开单业绩 - 退卡业绩(退卡是扣除) ### 步骤4:工资计算(在职员工) @@ -277,7 +280,8 @@ GROUP BY kjbsyj.kjblszh, kjbsyj.kjblsxm, hyhk.md, md.mdbm, md.dm - 离职员工规则优先级最高 3. **数据一致性**: - - 总业绩 = 开单业绩 + 消耗业绩 + 退卡业绩 + - 总业绩 = 开单业绩 - 退卡业绩(退卡是扣除) + - 消耗业绩不包含在总业绩中,仅用于消耗提成计算 - 消耗和项目数必须来自同一数据源(`lq_xh_kjbsyj`) --- diff --git a/项目信息-薪酬规则与名词解释.md b/项目信息-薪酬规则与名词解释.md index 434162c..0104be5 100644 --- a/项目信息-薪酬规则与名词解释.md +++ b/项目信息-薪酬规则与名词解释.md @@ -39,8 +39,10 @@ ### 提成相关 12. **T区提成**:门店总业绩 × 0.05 × 0.05 13. **战队提成**:按照战队总业绩得到提成比例(提成比例有固定规则表) -14. **基础业绩提成**:基础业绩 × 0.95 × 提成点 -15. **合作业绩提成**:合作业绩 × 0.95 × 0.65 × 提成点 +14. **基础业绩提成**:基础业绩 × 0.95 × 提成点(剩余0.05归门店T区) +15. **合作业绩提成**:合作业绩 × 0.95 × 0.65 × 提成点(剩余0.05归门店T区) +16. **新客转化率提成**:新客业绩 × 转化率提成比例 × 0.95(剩余0.05归门店T区) +17. **升单业绩提成**:升单业绩 × 升单提点 × 0.95(剩余0.05归门店T区) ### 考核相关 16. **主任、店长考核指标**:生命线-业绩、人头-固定、消耗-固定 @@ -80,9 +82,19 @@ - **0星**:月消耗未达到10000元 且 项目数未达到96个 → 底薪1800元 - **特殊情况**:月消耗或项目数中有一个为0星(未达到最低标准),底薪为一星(2000元) +**底薪计算说明**: +- 底薪按日均计算:日均消耗 = 月消耗 / 当月天数,日均项目数 = 项目数 / 当月天数 +- 星级标准按日均值判断:日均消耗 ≥ 10000/当月天数 且 日均项目数 ≥ 96/当月天数 → 一星 +- 最终底薪 = (档位底薪 / 当月天数) × 在店天数 + #### 新店健康师薪酬规则 **重要说明**:新店不考核消耗,健康师分成3个阶段: +**新店底薪规则**: +- 新店底薪计算时,仍然会计算消耗和项目数的星级 +- 但如果计算出的底薪 < 2000元,则保底为2000元(一星) +- 即:新店底薪最低为一星(2000元),不满足一星条件时按一星计算 + **第一阶段:新客转化阶段** - 主要考核新客转化率 - 提成主要来源于新客转化业绩 @@ -90,6 +102,11 @@ **第二阶段:升单人头阶段** - 主要考核升单人头数 - 提成主要来源于升单业绩 +- **升单提点规则**: + - 升单人头数 >= 10:升单提点 = 12% + - 升单人头数 >= 7 且 < 10:升单提点 = 10% + - 升单人头数 >= 4 且 < 7:升单提点 = 7% + - 升单人头数 < 4:升单提点 = 0% **第三阶段:业绩和项目数阶段** - 无新客转化和升单人头提成 @@ -124,9 +141,20 @@ ### 顾问提成规则 +#### 3人及以上战队 - 战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8% + +#### 2人战队 - 战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3% +#### 单人战队 +- 单人或者一个人的战队就没有顾问,不计算顾问提成 + +**注意**: +- 如果战队人数3人以上,按照3人的规则来计算 +- "组员业绩"指除顾问外的其他成员业绩总和 +- 新店顾问不考核消耗 + ### 店助考核规则 店助底薪规则 -- libgit2 0.21.4