diff --git a/antis-ncc-admin/.env.development b/antis-ncc-admin/.env.development
index 198955a..6462393 100644
--- a/antis-ncc-admin/.env.development
+++ b/antis-ncc-admin/.env.development
@@ -2,8 +2,8 @@
VUE_CLI_BABEL_TRANSPILE_MODULES = true
# VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com'
-VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
-# VUE_APP_BASE_API = 'http://localhost:2011'
+# VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
+VUE_APP_BASE_API = 'http://localhost:2011'
# VUE_APP_BASE_API = 'http://localhost:2011'
VUE_APP_IMG_API = ''
VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket'
diff --git a/antis-ncc-admin/src/api/extend/healthCoachSalary.js b/antis-ncc-admin/src/api/extend/healthCoachSalary.js
index 1480373..69bcef0 100644
--- a/antis-ncc-admin/src/api/extend/healthCoachSalary.js
+++ b/antis-ncc-admin/src/api/extend/healthCoachSalary.js
@@ -27,9 +27,10 @@ export function getExtraCalculationList(params) {
}
// 导入额外计算数据
-export function importExtraCalculationFromExcel(file) {
+export function importExtraCalculationFromExcel(file, clearBeforeImport = false) {
const formData = new FormData()
formData.append('file', file)
+ formData.append('clearBeforeImport', clearBeforeImport)
return request({
url: '/api/Extend/lqsalaryextracalculation/ImportFromExcel',
method: 'POST',
diff --git a/antis-ncc-admin/src/views/lqCooperationCost/import-dialog.vue b/antis-ncc-admin/src/views/lqCooperationCost/import-dialog.vue
index 9a6a4ef..f78767e 100644
--- a/antis-ncc-admin/src/views/lqCooperationCost/import-dialog.vue
+++ b/antis-ncc-admin/src/views/lqCooperationCost/import-dialog.vue
@@ -21,12 +21,25 @@
将文件拖到此处,或点击上传
只能上传xlsx/xls文件,且不超过10MB
+
+
+ 清理导入月份数据
+
+
{{ uploadResult.title }}
{{ uploadResult.description }}
+
+
成功导入的记录(包含成本类型):
+
+ 门店ID: {{ record.storeId }}, 年份: {{ record.year }}, 月份: {{ record.month }},
+ 金额: {{ record.totalAmount }}, 成本类型: {{ record.costType || '无' }},
+ 备注: {{ record.remarks || '无' }}
+
+
@@ -190,6 +196,7 @@ export default {
loading: false,
exportTemplateLoading: false,
importLoading: false,
+ clearBeforeImport: true, // 是否需要清理导入月份数据,默认true
list: [],
total: 0,
queryParams: {
@@ -451,7 +458,7 @@ export default {
this.importLoading = true
try {
- const response = await importExtraCalculationFromExcel(file)
+ const response = await importExtraCalculationFromExcel(file, this.clearBeforeImport)
if (response.code === 200) {
this.$message.success('导入成功')
diff --git a/excel/健康师额外数据模板_2025年11月_20251219120434.xlsx b/excel/健康师额外数据模板_2025年11月_20251219120434.xlsx
new file mode 100644
index 0000000..5287535
--- /dev/null
+++ b/excel/健康师额外数据模板_2025年11月_20251219120434.xlsx
diff --git a/excel/合作成本表.xlsx b/excel/合作成本表.xlsx
index 862d084..443d055 100644
--- a/excel/合作成本表.xlsx
+++ b/excel/合作成本表.xlsx
diff --git a/excel/合作成本表_测试导入.xlsx b/excel/合作成本表_测试导入.xlsx
new file mode 100644
index 0000000..7b3bdaa
--- /dev/null
+++ b/excel/合作成本表_测试导入.xlsx
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/Dto/LqMajorProjectTeacherSalary/MajorProjectTeacherSalaryOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMajorProjectTeacherSalary/MajorProjectTeacherSalaryOutput.cs
index 42d3c48..dcedd21 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMajorProjectTeacherSalary/MajorProjectTeacherSalaryOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMajorProjectTeacherSalary/MajorProjectTeacherSalaryOutput.cs
@@ -18,12 +18,12 @@ namespace NCC.Extend.Entitys.Dto.LqMajorProjectTeacherSalary
public string StatisticsMonth { get; set; }
///
- /// 门店ID
+ /// 部门ID(使用StoreId字段存储部门ID)
///
public string StoreId { get; set; }
///
- /// 门店名称
+ /// 部门名称(使用StoreName字段存储部门名称)
///
public string StoreName { 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..a4d1f61 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;
@@ -328,53 +334,18 @@ namespace NCC.Extend
decimal performanceRatio = salary.StoreTotalPerformance / salary.StoreLifeline;
// 根据岗位类型确定提成比例
- if (isDirector)
+ // 店助和店助主任使用相同的阶梯提成规则
+ // 先计算总提成金额(阶梯计算)
+ decimal totalCommission = CalculateAssistantCommission(salary.StoreTotalPerformance, salary.StoreLifeline);
+
+ // 计算平均提成比例(用于显示)
+ if (salary.StoreTotalPerformance > 0)
{
- // 店助主任:使用固定比例(按规则文档,业绩≥100%时使用阶梯提成,但为保持比例固定,使用平均比例)
- // 业绩 < 70%:0%
- // 70% ≤ 业绩 < 100%:0.4%
- // 业绩 ≥ 100%:使用阶梯提成计算总提成,然后计算平均比例
- if (performanceRatio < 0.7m)
- {
- commissionRate = 0;
- }
- else if (performanceRatio < 1.0m)
- {
- commissionRate = 0.004m; // 0.4%
- }
- else
- {
- // 业绩 ≥ 100%:使用阶梯提成计算总提成,然后计算平均比例
- // ≤生命线部分:0.6%,>生命线部分:1%
- decimal storeTotalCommission = CalculateDirectorCommission(salary.StoreTotalPerformance, salary.StoreLifeline);
- if (salary.StoreTotalPerformance > 0)
- {
- commissionRate = storeTotalCommission / salary.StoreTotalPerformance;
- }
- else
- {
- commissionRate = 0;
- }
- }
+ commissionRate = totalCommission / salary.StoreTotalPerformance;
}
else
{
- // 店助:使用固定比例
- // 业绩 < 70%:0%
- // 70% ≤ 业绩 < 100%:0.4%
- // 业绩 ≥ 100%:0.6%
- if (performanceRatio < 0.7m)
- {
- commissionRate = 0;
- }
- else if (performanceRatio < 1.0m)
- {
- commissionRate = 0.004m; // 0.4%
- }
- else
- {
- commissionRate = 0.006m; // 0.6%
- }
+ commissionRate = 0;
}
}
@@ -461,12 +432,15 @@ namespace NCC.Extend
}
// 2.11 按在店天数比例计算店助的提成和奖励
- // 逻辑:提成金额 = 门店业绩 × 提成比例 / 当月天数 × 在店天数
+ // 逻辑:提成金额 = 门店总提成(阶梯计算) / 当月天数 × 在店天数
// 阶段奖励 = 门店总奖励 / 当月天数 × 在店天数
if (daysInMonth > 0 && workingDays > 0)
{
- // 按比例计算提成:门店业绩 × 提成比例 / 当月天数 × 在店天数
- salary.CommissionAmount = salary.StoreTotalPerformance * commissionRate / daysInMonth * workingDays;
+ // 先计算门店总提成(阶梯计算)- 店助和店助主任使用相同的规则
+ decimal storeTotalCommission = CalculateAssistantCommission(salary.StoreTotalPerformance, salary.StoreLifeline);
+
+ // 按比例计算提成:门店总提成 / 当月天数 × 在店天数
+ salary.CommissionAmount = storeTotalCommission / daysInMonth * workingDays;
// 按比例计算奖励
salary.StageRewardAmount = storeTotalStageReward / daysInMonth * workingDays;
@@ -644,21 +618,18 @@ namespace NCC.Extend
else
{
// 门店业绩 ≥ 门店生命线 × 100% → 阶梯提成
- // 70%以下部分:0%
- // 70%-100%部分:0.4%
- // ≤生命线部分:0.6%,>生命线部分:1%
- decimal stage70 = storeLifeline * 0.7m;
+ // ≤生命线部分(整个生命线):0.6%
+ // >生命线部分:1%
decimal stage100 = storeLifeline;
- decimal performance70To100 = stage100 - stage70;
- decimal performanceAbove100 = storePerformance - stage100;
+ decimal performanceUpToLifeline = stage100; // ≤生命线部分(整个生命线)
+ decimal performanceAbove100 = storePerformance - stage100; // >生命线部分
- // 70%-100%部分:0.4%
- decimal commission70To100 = performance70To100 * 0.004m;
- // ≤生命线部分(0-70%):0%,70%-100%部分:0.4%,已计算
+ // ≤生命线部分:0.6%
+ decimal commissionUpToLifeline = performanceUpToLifeline * 0.006m;
// >生命线部分:1%
decimal commissionAbove100 = performanceAbove100 * 0.01m; // 1%
- return commission70To100 + commissionAbove100;
+ return commissionUpToLifeline + commissionAbove100;
}
}
}
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..d4c88ab 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,25 +332,89 @@ 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
///
/// Excel文件
+ /// 是否需要清理导入月份数据(默认:true,清理)
/// 导入结果
/// 导入成功
/// 文件格式错误或数据验证失败
[HttpPost("Actions/Import")]
- public async Task Import(IFormFile file)
+ public async Task Import(IFormFile file, bool clearBeforeImport = true)
{
try
{
@@ -367,6 +434,7 @@ namespace NCC.Extend.LqCooperationCost
var successCount = 0;
var failCount = 0;
var failMessages = new List();
+ var importData = new List<(string StoreId, int Year, string Month, decimal TotalAmount, string CostType, string Remarks)>();
// 保存临时文件
var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName));
@@ -391,27 +459,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 +503,7 @@ namespace NCC.Extend.LqCooperationCost
continue;
}
+ // 验证月份
if (string.IsNullOrEmpty(monthText) || monthText.Length != 6)
{
failMessages.Add($"第{i + 1}行:月份格式错误(应为YYYYMM格式,如:202501)");
@@ -426,6 +511,7 @@ namespace NCC.Extend.LqCooperationCost
continue;
}
+ // 验证合计金额
if (string.IsNullOrEmpty(totalAmountText) || !decimal.TryParse(totalAmountText, out decimal totalAmount))
{
failMessages.Add($"第{i + 1}行:合计金额格式错误(应为数字)");
@@ -433,53 +519,14 @@ 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())
- .AnyAsync();
-
- if (exists)
- {
- failMessages.Add($"第{i + 1}行:该门店{year}年{monthText}月的记录已存在");
- failCount++;
- continue;
- }
-
- // 创建记录
- var entity = new LqCooperationCostEntity
- {
- Id = YitIdHelper.NextId().ToString(),
- StoreId = storeId,
- StoreName = storeName,
- Year = year,
- Month = monthText,
- TotalAmount = totalAmount,
- Remarks = remarks,
- IsEffective = StatusEnum.有效.GetHashCode(),
- CreateUser = _userManager.UserId,
- CreateTime = DateTime.Now
- };
-
- var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
- if (isOk > 0)
- {
- successCount++;
- }
- else
- {
- failMessages.Add($"第{i + 1}行:保存失败");
- failCount++;
- }
+ // 添加到导入数据列表
+ importData.Add((storeId, year, monthText, totalAmount, costType, remarks));
+ successCount++;
}
catch (Exception ex)
{
@@ -497,13 +544,71 @@ namespace NCC.Extend.LqCooperationCost
}
}
+ // 如果需要清理导入月份数据,先删除导入数据中所有涉及的年份+月份组合的数据
+ if (clearBeforeImport && importData.Any())
+ {
+ // 获取导入数据中所有唯一的年份+月份组合
+ var yearMonthPairs = importData
+ .Select(x => new { x.Year, x.Month })
+ .Distinct()
+ .ToList();
+
+ foreach (var pair in yearMonthPairs)
+ {
+ await _db.Deleteable()
+ .Where(x => x.Year == pair.Year && x.Month == pair.Month && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .ExecuteCommandAsync();
+ }
+ }
+
+ // 批量插入导入数据
+ if (importData.Any())
+ {
+ // 批量获取门店信息
+ var storeIds = importData.Select(x => x.StoreId).Distinct().ToList();
+ var stores = await _db.Queryable()
+ .Where(x => storeIds.Contains(x.Id))
+ .Select(x => new { x.Id, x.Dm })
+ .ToListAsync();
+ var storeDict = stores.ToDictionary(x => x.Id, x => x.Dm);
+
+ var entities = importData.Select(x => new LqCooperationCostEntity
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ StoreId = x.StoreId,
+ StoreName = storeDict.ContainsKey(x.StoreId) ? storeDict[x.StoreId] : "",
+ Year = x.Year,
+ Month = x.Month,
+ TotalAmount = x.TotalAmount,
+ CostType = x.CostType,
+ Remarks = x.Remarks,
+ IsEffective = StatusEnum.有效.GetHashCode(),
+ CreateUser = _userManager.UserId,
+ CreateTime = DateTime.Now
+ }).ToList();
+
+ await _db.Insertable(entities).ExecuteCommandAsync();
+ }
+
+ // 构建返回结果,包含成功导入的记录的成本类型信息
+ var successRecords = importData.Select(x => new
+ {
+ storeId = x.StoreId,
+ year = x.Year,
+ month = x.Month,
+ totalAmount = x.TotalAmount,
+ costType = x.CostType,
+ remarks = x.Remarks
+ }).ToList();
+
return new
{
success = true,
message = $"导入完成:成功{successCount}条,失败{failCount}条",
successCount = successCount,
failCount = failCount,
- failMessages = failMessages
+ failMessages = failMessages,
+ successRecords = successRecords
};
}
catch (Exception ex)
@@ -514,3 +619,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/LqMajorProjectDirectorSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs
index 345ed02..3caeb68 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs
@@ -7,7 +7,10 @@ using NCC.DynamicApiController;
using NCC.Extend.Entitys.Dto.LqMajorProjectDirectorSalary;
using NCC.Extend.Entitys.lq_attendance_summary;
using NCC.Extend.Entitys.lq_hytk_hytk;
+using NCC.Extend.Entitys.lq_hytk_mx;
using NCC.Extend.Entitys.lq_kd_kdjlb;
+using NCC.Extend.Entitys.lq_kd_pxmx;
+using NCC.Extend.Entitys.lq_kd_jksyj;
using NCC.Extend.Entitys.lq_md_target;
using NCC.Extend.Entitys.lq_mdxx;
using NCC.Extend.Entitys.lq_major_project_director_salary_statistics;
@@ -214,19 +217,33 @@ namespace NCC.Extend
{
foreach (var storeId in allManagedStoreIds)
{
- // 该门店的开单金额
- var storeBillingAmount = await _db.Queryable()
- .Where(x => x.IsEffective == 1
- && x.Djmd == storeId
- && x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1))
- .SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
-
- // 该门店的退卡金额(优先使用ActualRefundAmount,如果没有则使用Tkje)
- var storeRefundAmount = await _db.Queryable()
- .Where(x => x.IsEffective == 1
- && x.Md == storeId
- && x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1))
- .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
+ // 该门店的开单金额(只统计医美类型的开单,从lq_kd_jksyj表统计医美品项的健康师业绩)
+ var storeBillingList = await _db.Queryable(
+ (jksyj, kdjlb) => new JoinQueryInfos(
+ JoinType.Inner, jksyj.Glkdbh == kdjlb.Id))
+ .Where((jksyj, kdjlb) =>
+ kdjlb.IsEffective == 1
+ && jksyj.IsEffective == 1
+ && kdjlb.Djmd == storeId
+ && kdjlb.Kdrq >= startDate && kdjlb.Kdrq <= endDate.AddDays(1)
+ && jksyj.ItemCategory == "医美"
+ && !string.IsNullOrEmpty(jksyj.Jksyj))
+ .Select((jksyj, kdjlb) => jksyj.Jksyj)
+ .ToListAsync();
+ var storeBillingAmount = storeBillingList
+ .Sum(x => decimal.TryParse(x, out var val) ? val : 0m);
+
+ // 该门店的退卡金额(只统计医美类型的退卡,从lq_hytk_mx表统计医美品项的退卡金额)
+ var storeRefundAmount = await _db.Queryable(
+ (mx, hytk) => new JoinQueryInfos(
+ JoinType.Inner, mx.RefundInfoId == hytk.Id))
+ .Where((mx, hytk) =>
+ hytk.IsEffective == 1
+ && mx.IsEffective == 1
+ && hytk.Md == storeId
+ && hytk.Tksj >= startDate && hytk.Tksj <= endDate.AddDays(1)
+ && mx.ItemCategory == "医美")
+ .SumAsync((mx, hytk) => (decimal?)(mx.Tkje ?? 0)) ?? 0m;
// 该门店的净总业绩
var storeTotalPerformance = storeBillingAmount - storeRefundAmount;
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs
index c2de6aa..4aebcb7 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs
@@ -7,7 +7,10 @@ using NCC.DynamicApiController;
using NCC.Extend.Entitys.Dto.LqMajorProjectTeacherSalary;
using NCC.Extend.Entitys.lq_attendance_summary;
using NCC.Extend.Entitys.lq_hytk_hytk;
+using NCC.Extend.Entitys.lq_hytk_mx;
using NCC.Extend.Entitys.lq_kd_kdjlb;
+using NCC.Extend.Entitys.lq_kd_pxmx;
+using NCC.Extend.Entitys.lq_kd_jksyj;
using NCC.Extend.Entitys.lq_md_major_project_teacher_assignment;
using NCC.Extend.Entitys.lq_md_xdbhsj;
using NCC.Extend.Entitys.lq_mdxx;
@@ -182,25 +185,40 @@ namespace NCC.Extend
.ToDictionary(g => g.Key, g => g.First());
// 1.4 门店总业绩计算 (开单实付 - 退卡金额)
- // 开单实付(从lq_kd_kdjlb表统计sfyj字段)
- var storeBillingList = await _db.Queryable()
- .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1)
- .Select(x => new { x.Djmd, x.Sfyj })
+ // 开单实付(只统计医美类型的开单,从lq_kd_jksyj表统计医美品项的健康师业绩)
+ var storeBillingList = await _db.Queryable(
+ (jksyj, kdjlb) => new JoinQueryInfos(
+ JoinType.Inner, jksyj.Glkdbh == kdjlb.Id))
+ .Where((jksyj, kdjlb) =>
+ kdjlb.Kdrq >= startDate &&
+ kdjlb.Kdrq <= endDate.AddDays(1) &&
+ kdjlb.IsEffective == 1 &&
+ jksyj.IsEffective == 1 &&
+ jksyj.ItemCategory == "医美" &&
+ !string.IsNullOrEmpty(jksyj.Jksyj))
+ .Select((jksyj, kdjlb) => new { kdjlb.Djmd, Jksyj = jksyj.Jksyj })
.ToListAsync();
var storeBillingDict = storeBillingList
.Where(x => !string.IsNullOrEmpty(x.Djmd))
.GroupBy(x => x.Djmd)
- .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj));
-
- // 退卡金额(从lq_hytk_hytk表统计,使用F_ActualRefundAmount,如果没有则使用tkje)
- var storeRefundList = await _db.Queryable()
- .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
- .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje })
+ .ToDictionary(g => g.Key, g => g.Sum(x => decimal.TryParse(x.Jksyj, out var val) ? val : 0m));
+
+ // 退卡金额(只统计医美类型的退卡,从lq_hytk_mx表统计医美品项的退卡金额)
+ var storeRefundList = await _db.Queryable(
+ (mx, hytk) => new JoinQueryInfos(
+ JoinType.Inner, mx.RefundInfoId == hytk.Id))
+ .Where((mx, hytk) =>
+ hytk.Tksj >= startDate &&
+ hytk.Tksj <= endDate.AddDays(1) &&
+ hytk.IsEffective == 1 &&
+ mx.IsEffective == 1 &&
+ mx.ItemCategory == "医美")
+ .Select((mx, hytk) => new { hytk.Md, mx.Tkje })
.ToListAsync();
var storeRefundDict = storeRefundList
.Where(x => !string.IsNullOrEmpty(x.Md))
.GroupBy(x => x.Md)
- .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0));
+ .ToDictionary(g => g.Key, g => g.Sum(x => x.Tkje ?? 0));
// 1.5 考勤数据 (lq_attendance_summary)
var attendanceList = await _db.Queryable()
@@ -212,10 +230,18 @@ namespace NCC.Extend
var teacherIds = assignmentList.Select(x => x.TeacherId).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
var userList = await _db.Queryable()
.Where(x => teacherIds.Contains(x.Id))
- .Select(x => new { x.Id, x.RealName, x.Account, x.IsOnJob })
+ .Select(x => new { x.Id, x.RealName, x.Account, x.IsOnJob, x.OrganizeId })
.ToListAsync();
var userDict = userList.ToDictionary(x => x.Id, x => x);
+ // 1.7 获取部门信息 (BASE_ORGANIZE)
+ var organizeIds = userList.Where(x => !string.IsNullOrEmpty(x.OrganizeId)).Select(x => x.OrganizeId).Distinct().ToList();
+ var organizeList = await _db.Queryable()
+ .Where(x => organizeIds.Contains(x.Id))
+ .Select(x => new { x.Id, x.FullName })
+ .ToListAsync();
+ var organizeDict = organizeList.ToDictionary(x => x.Id, x => x);
+
// 2. 按大项目部老师聚合数据
var teacherStats = new Dictionary();
@@ -268,35 +294,29 @@ namespace NCC.Extend
salary.EmployeeName = user.RealName ?? "";
salary.EmployeeAccount = user.Account ?? "";
salary.IsTerminated = user.IsOnJob == 0 ? 1 : 0;
- }
- // 2.3 填充门店信息
- if (storeDict.ContainsKey(storeId))
- {
- var store = storeDict[storeId];
- salary.StoreId = storeId;
- salary.StoreName = store.Dm ?? "";
- salary.StoreType = store.StoreType;
- salary.StoreCategory = store.StoreCategory;
+ // 2.3 填充部门信息(使用StoreId和StoreName字段存储部门信息)
+ if (!string.IsNullOrEmpty(user.OrganizeId) && organizeDict.ContainsKey(user.OrganizeId))
+ {
+ var organize = organizeDict[user.OrganizeId];
+ salary.StoreId = user.OrganizeId; // 存储部门ID
+ salary.StoreName = organize.FullName ?? ""; // 存储部门名称
+ }
+ else
+ {
+ salary.StoreId = user.OrganizeId ?? "";
+ salary.StoreName = "";
+ }
}
else
{
- salary.StoreId = storeId;
+ salary.StoreId = "";
salary.StoreName = "";
}
- // 2.4 新店保护信息
- if (!string.IsNullOrEmpty(salary.StoreId) && newStoreProtectionDict.ContainsKey(salary.StoreId))
- {
- var protection = newStoreProtectionDict[salary.StoreId];
- salary.IsNewStore = "是";
- salary.NewStoreProtectionStage = protection.Stage;
- }
- else
- {
- salary.IsNewStore = "否";
- salary.NewStoreProtectionStage = 0;
- }
+ // 2.4 新店保护信息(不再使用,因为现在存储的是部门信息)
+ salary.IsNewStore = "否";
+ salary.NewStoreProtectionStage = 0;
// 2.5 统计门店总业绩(开单业绩 - 退卡业绩)
var billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0;
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
index d59f08a..4a7b3a2 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs
@@ -106,9 +106,10 @@ namespace NCC.Extend
/// 从Excel导入健康师工资额外计算数据
///
/// Excel文件
+ /// 是否需要清理导入月份数据(默认:true,清理)
/// 导入结果
[HttpPost("ImportFromExcel")]
- public async Task ImportFromExcel(IFormFile file)
+ public async Task ImportFromExcel(IFormFile file, bool clearBeforeImport = true)
{
try
{
@@ -277,6 +278,23 @@ namespace NCC.Extend
throw NCCException.Oh("Excel文件中没有有效的数据行");
}
+ // 如果需要清理导入月份数据,先删除导入数据中所有涉及的年份+月份组合的数据
+ if (clearBeforeImport)
+ {
+ // 获取导入数据中所有唯一的年份+月份组合
+ var yearMonthPairs = importData
+ .Select(x => new { x.Year, x.Month })
+ .Distinct()
+ .ToList();
+
+ foreach (var pair in yearMonthPairs)
+ {
+ await _db.Deleteable()
+ .Where(x => x.Year == pair.Year && x.Month == pair.Month)
+ .ExecuteCommandAsync();
+ }
+ }
+
// 处理导入数据
return await ProcessImportData(importData);
}
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/LqShareStatisticsHqService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsHqService.cs
index 1addbd2..971f99e 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsHqService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsHqService.cs
@@ -8,6 +8,8 @@ using NCC.Extend.Entitys.lq_kd_kdjlb;
using NCC.Extend.Entitys.lq_hytk_hytk;
using NCC.Extend.Entitys.lq_kd_pxmx;
using NCC.Extend.Entitys.lq_contract_rent_detail;
+using NCC.Extend.Entitys.lq_contract;
+using NCC.Extend.Entitys;
using SqlSugar;
using System;
using System.Linq;
@@ -151,23 +153,64 @@ namespace NCC.Extend
///
private async Task CalculateCost(LqShareStatisticsHqEntity entity, DateTime startDate, DateTime endDate, string statisticsMonth)
{
- // 1. 成本-报销 (TODO: 需要确认总部报销的判定方式)
- entity.CostReimbursement = 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 == "已通过"
+ && app.ApplicationTime >= startDate
+ && app.ApplicationTime <= endDate.AddDays(1))
+ .SumAsync((app, purchase, category) => purchase.Amount);
+ entity.CostReimbursement = reimbursementAmount;
// 2. 成本-人工 (保留)
entity.CostLabor = 0;
// 3. 成本-教育部房租
- // TODO: 需要确认如何识别教育部合同
- entity.CostEducationRent = 0;
+ // 从合同月租明细表获取:门店是总部,分类是教育,统计月份匹配
+ // 使用月份字符串匹配,避免时区问题
+ var educationRent = await _db.Ado.SqlQuerySingleAsync(
+ $@"SELECT COALESCE(SUM(d.F_DueAmount), 0)
+ FROM lq_contract_rent_detail d
+ INNER JOIN lq_contract c ON d.F_ContractId = c.F_Id
+ WHERE c.F_StoreId = '1649328471923847168'
+ AND c.F_Category = '教育'
+ AND DATE_FORMAT(d.F_PaymentMonth, '%Y%m') = @StatisticsMonth
+ AND c.F_IsEffective = 1
+ AND d.F_IsEffective = 1",
+ new { StatisticsMonth = statisticsMonth });
+ entity.CostEducationRent = educationRent;
// 4. 成本-仓库房租
- // TODO: 需要确认如何识别仓库合同
- entity.CostWarehouseRent = 0;
+ // 从合同月租明细表获取:门店是总部,分类是仓库,统计月份匹配
+ // 使用月份字符串匹配,避免时区问题
+ var warehouseRent = await _db.Ado.SqlQuerySingleAsync(
+ $@"SELECT COALESCE(SUM(d.F_DueAmount), 0)
+ FROM lq_contract_rent_detail d
+ INNER JOIN lq_contract c ON d.F_ContractId = c.F_Id
+ WHERE c.F_StoreId = '1649328471923847168'
+ AND c.F_Category = '仓库'
+ AND DATE_FORMAT(d.F_PaymentMonth, '%Y%m') = @StatisticsMonth
+ AND c.F_IsEffective = 1
+ AND d.F_IsEffective = 1",
+ new { StatisticsMonth = statisticsMonth });
+ entity.CostWarehouseRent = warehouseRent;
// 5. 成本-总部房租
- // TODO: 需要确认如何识别总部合同
- entity.CostHQRent = 0;
+ // 从合同月租明细表获取:门店是总部,分类是总部,统计月份匹配
+ // 使用月份字符串匹配,避免时区问题
+ var hqRent = await _db.Ado.SqlQuerySingleAsync(
+ $@"SELECT COALESCE(SUM(d.F_DueAmount), 0)
+ FROM lq_contract_rent_detail d
+ INNER JOIN lq_contract c ON d.F_ContractId = c.F_Id
+ WHERE c.F_StoreId = '1649328471923847168'
+ AND c.F_Category = '总部'
+ AND DATE_FORMAT(d.F_PaymentMonth, '%Y%m') = @StatisticsMonth
+ AND c.F_IsEffective = 1
+ AND d.F_IsEffective = 1",
+ new { StatisticsMonth = statisticsMonth });
+ entity.CostHQRent = hqRent;
}
///
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/netcore/src/Modularity/Extend/NCC.Extend/Utils/WeChatBotService.cs b/netcore/src/Modularity/Extend/NCC.Extend/Utils/WeChatBotService.cs
index 8d62fe3..b61edc8 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/Utils/WeChatBotService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/Utils/WeChatBotService.cs
@@ -25,7 +25,7 @@ namespace NCC.Extend.Utils
_httpClient = httpClient;
// 从配置文件中读取企业微信机器人配置
- _botApiUrl = App.Configuration["WeChatBot:BotApiUrl"] ?? "http://wx.lvqianmeiye.com/api/Bot/send-text";
+ _botApiUrl = App.Configuration["WeChatBot:BotApiUrl"] ?? "https://wx.lvqianmeiye.com/api/Bot/send-text";
// 从配置文件中读取Webhook地址(正式或测试地址,通过配置文件切换)
_webhookUrl = App.Configuration["WeChatBot:WebhookUrl"];
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..80fc2a2 100644
--- a/sql/排查生美业绩统计差异-简化版.sql
+++ b/sql/排查生美业绩统计差异-简化版.sql
@@ -120,3 +120,9 @@ ORDER BY 生美业绩 DESC;
+
+
+
+
+
+
diff --git a/sql/排查生美业绩统计差异详细.sql b/sql/排查生美业绩统计差异详细.sql
index 6039721..92502d8 100644
--- a/sql/排查生美业绩统计差异详细.sql
+++ b/sql/排查生美业绩统计差异详细.sql
@@ -172,3 +172,9 @@ HAVING COUNT(*) > 1;
+
+
+
+
+
+
diff --git a/sql/检查生美业绩统计差异.sql b/sql/检查生美业绩统计差异.sql
index 40ef5d1..4e362bb 100644
--- a/sql/检查生美业绩统计差异.sql
+++ b/sql/检查生美业绩统计差异.sql
@@ -142,3 +142,9 @@ 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..e631863 100644
--- a/test_tianwang_api.py
+++ b/test_tianwang_api.py
@@ -75,3 +75,7 @@ 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人的规则来计算
+- "组员业绩"指除顾问外的其他成员业绩总和
+- 新店顾问不考核消耗
+
### 店助考核规则
店助底薪规则