diff --git a/antis-ncc-admin/src/views/wageManagement/director-detail-dialog.vue b/antis-ncc-admin/src/views/wageManagement/director-detail-dialog.vue
index 2774b45..e4645c5 100644
--- a/antis-ncc-admin/src/views/wageManagement/director-detail-dialog.vue
+++ b/antis-ncc-admin/src/views/wageManagement/director-detail-dialog.vue
@@ -288,7 +288,7 @@ export default {
getFieldsByCategory(category) {
if (!this.detailData) return []
- const excludeFields = ['StoreName', 'EmployeeName', 'Position', 'Id', 'CreateTime', 'UpdateTime', 'CreateUser', 'UpdateUser', 'StatisticsMonth', 'GoldTriangleTeam', 'IsNewStore', 'NewStoreProtectionStage', 'IsLocked']
+ const excludeFields = ['StoreName', 'EmployeeName', 'Position', 'Id', 'CreateTime', 'UpdateTime', 'CreateUser', 'UpdateUser', 'StatisticsMonth', 'GoldTriangleTeam', 'IsNewStore', 'NewStoreProtectionStage', 'IsLocked', 'StoreId', 'EmployeeId', 'SalesPerformance']
const categoryMap = {
performance: ['Performance', 'Lifeline', 'CompletionRate', 'Reached', 'HeadCount', 'Target', 'Consume'],
@@ -356,10 +356,11 @@ export default {
const moneyFields = ['performance', 'commission', 'salary', 'subsidy', 'deduction', 'amount', 'fee', 'bonus', 'deposit', 'supplement', 'payment', 'consumption', 'reward', 'handwork', 'gross', 'guaranteed', 'transportation', 'allowance', 'total']
const percentFields = ['point', 'rate', 'percentage', 'percent']
- if (moneyFields.some(field => lowerKey.includes(field)) && !lowerKey.includes('status')) {
- return 'money'
- } else if (percentFields.some(field => lowerKey.includes(field))) {
+ // 注意:先检查百分比字段,避免 CommissionRate 等字段被误识别为金额
+ if (percentFields.some(field => lowerKey.includes(field))) {
return 'percent'
+ } else if (moneyFields.some(field => lowerKey.includes(field)) && !lowerKey.includes('status')) {
+ return 'money'
}
return 'text'
},
@@ -371,6 +372,12 @@ export default {
'StoreTotalPerformance': '门店总业绩',
'StoreBillingPerformance': '门店开单业绩',
'StoreRefundPerformance': '门店退卡业绩',
+ 'SalesPerformance': '销售业绩',
+ 'ProductMaterial': '产品物料',
+ 'CooperationCost': '合作项目成本',
+ 'StoreExpense': '店内支出',
+ 'LaundryCost': '洗毛巾费用',
+ 'GrossProfit': '毛利',
'StoreLifeline': '门店生命线',
'PerformanceCompletionRate': '业绩完成率',
'TotalPerformance': '总业绩',
@@ -456,7 +463,13 @@ export default {
'StoreType': '门店类型',
'StoreCategory': '门店类别',
'IsNewStore': '是否新店',
- 'NewStoreProtectionStage': '新店保护阶段'
+ 'NewStoreProtectionStage': '新店保护阶段',
+ 'StoreId': '门店ID',
+ 'EmployeeId': '员工ID',
+ 'StatisticsMonth': '统计月份',
+ 'EmployeeConfirmStatus': '确认状态',
+ 'EmployeeConfirmTime': '员工确认时间',
+ 'EmployeeConfirmRemark': '员工确认备注'
}
// 如果映射中存在,直接返回中文
@@ -571,14 +584,24 @@ export default {
return highlightFields.includes(key)
},
// 获取字段顺序
+ // 注意:毛利(GrossProfit)需要放在生命线提成比例(CommissionRateBelowLifeline)前面
getFieldOrder(key) {
const orderMap = {
'TotalPerformance': 1,
'BasePerformance': 2,
'CooperationPerformance': 3,
- 'TotalCommission': 10,
- 'BasePerformanceCommission': 11,
- 'CooperationPerformanceCommission': 12,
+ 'StoreTotalPerformance': 4,
+ 'SalesPerformance': 5,
+ 'GrossProfit': 6, // 毛利放在生命线提成比例前面
+ 'StoreLifeline': 7,
+ 'CommissionRateBelowLifeline': 8, // 生命线提成比例
+ 'CommissionRateAboveLifeline': 9,
+ 'CommissionAmountBelowLifeline': 10,
+ 'CommissionAmountAboveLifeline': 11,
+ 'TotalCommissionAmount': 12,
+ 'TotalCommission': 13,
+ 'BasePerformanceCommission': 14,
+ 'CooperationPerformanceCommission': 15,
'FinalGrossSalary': 20,
'ActualSalary': 21,
'TotalSubsidy': 30,
diff --git a/antis-ncc-admin/src/views/wageManagement/director.vue b/antis-ncc-admin/src/views/wageManagement/director.vue
index 1cfaeae..a454ea1 100644
--- a/antis-ncc-admin/src/views/wageManagement/director.vue
+++ b/antis-ncc-admin/src/views/wageManagement/director.vue
@@ -418,7 +418,7 @@ export default {
}
const columns = {}
- const excludeFields = ['StoreName', 'EmployeeName', 'Position', 'Id', 'CreateTime', 'UpdateTime', 'CreateUser', 'UpdateUser', 'StatisticsMonth']
+ const excludeFields = ['StoreName', 'EmployeeName', 'Position', 'Id', 'CreateTime', 'UpdateTime', 'CreateUser', 'UpdateUser', 'StatisticsMonth', 'StoreId', 'EmployeeId', 'SalesPerformance']
// 金额字段关键词
const moneyFields = ['Performance', 'Commission', 'Salary', 'Subsidy', 'Deduction', 'Amount', 'Fee', 'Bonus', 'Deposit', 'Supplement', 'Payment', 'Consumption', 'Reward', 'Handwork', 'Gross', 'Guaranteed', 'Transportation', 'Allowance', 'Total']
@@ -426,8 +426,12 @@ export default {
const percentFields = ['Point', 'Rate', 'Percentage', 'Percent']
// 定义字段顺序(重要字段优先)
+ // 注意:毛利(GrossProfit)需要放在生命线提成比例(CommissionRateBelowLifeline)前面
const fieldOrder = [
'GoldTriangleTeam', 'TotalPerformance', 'BasePerformance', 'CooperationPerformance',
+ 'StoreTotalPerformance', 'GrossProfit',
+ 'StoreLifeline', 'CommissionRateBelowLifeline', 'CommissionRateAboveLifeline',
+ 'CommissionAmountBelowLifeline', 'CommissionAmountAboveLifeline', 'TotalCommissionAmount',
'TotalCommission', 'BasePerformanceCommission', 'CooperationPerformanceCommission',
'FinalGrossSalary', 'ActualSalary', 'TotalSubsidy', 'TotalDeduction',
'IsLocked', 'IsNewStore', 'NewStoreProtectionStage'
@@ -487,11 +491,12 @@ export default {
const moneyFields = ['performance', 'commission', 'salary', 'subsidy', 'deduction', 'amount', 'fee', 'bonus', 'deposit', 'supplement', 'payment', 'consumption', 'reward', 'handwork', 'gross', 'guaranteed', 'transportation', 'allowance', 'total']
const percentFields = ['point', 'rate', 'percentage', 'percent']
- // 注意:payment 需要排除 PaymentStatus 这种状态字段
- if (moneyFields.some(field => lowerKey.includes(field)) && !lowerKey.includes('status')) {
- return 'money'
- } else if (percentFields.some(field => lowerKey.includes(field))) {
+ // 注意:先检查百分比字段,避免 CommissionRate 等字段被误识别为金额
+ // payment 需要排除 PaymentStatus 这种状态字段
+ if (percentFields.some(field => lowerKey.includes(field))) {
return 'percent'
+ } else if (moneyFields.some(field => lowerKey.includes(field)) && !lowerKey.includes('status')) {
+ return 'money'
}
return 'text'
},
@@ -504,6 +509,12 @@ export default {
'StoreTotalPerformance': '门店总业绩',
'StoreBillingPerformance': '门店开单业绩',
'StoreRefundPerformance': '门店退卡业绩',
+ 'SalesPerformance': '销售业绩',
+ 'ProductMaterial': '产品物料',
+ 'CooperationCost': '合作项目成本',
+ 'StoreExpense': '店内支出',
+ 'LaundryCost': '洗毛巾费用',
+ 'GrossProfit': '毛利',
'StoreLifeline': '门店生命线',
'PerformanceCompletionRate': '业绩完成率',
'TotalPerformance': '总业绩',
@@ -584,7 +595,13 @@ export default {
'StoreType': '门店类型',
'StoreCategory': '门店类别',
'IsNewStore': '是否新店',
- 'NewStoreProtectionStage': '新店保护阶段'
+ 'NewStoreProtectionStage': '新店保护阶段',
+ 'StoreId': '门店ID',
+ 'EmployeeId': '员工ID',
+ 'StatisticsMonth': '统计月份',
+ 'EmployeeConfirmStatus': '确认状态',
+ 'EmployeeConfirmTime': '员工确认时间',
+ 'EmployeeConfirmRemark': '员工确认备注'
}
return labelMap[key] || key
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs
index b661810..3d50ab0 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDirectorSalary/DirectorSalaryOutput.cs
@@ -236,6 +236,121 @@ namespace NCC.Extend.Entitys.Dto.LqDirectorSalary
/// 新店保护阶段
///
public int NewStoreProtectionStage { get; set; }
+
+ ///
+ /// 门店ID
+ ///
+ public string StoreId { get; set; }
+
+ ///
+ /// 员工ID
+ ///
+ public string EmployeeId { get; set; }
+
+ ///
+ /// 统计月份(YYYYMM)
+ ///
+ public string StatisticsMonth { get; set; }
+
+ ///
+ /// 销售业绩(开单业绩-退款业绩)
+ ///
+ public decimal SalesPerformance { get; set; }
+
+ ///
+ /// 产品物料(仓库领用金额)
+ ///
+ public decimal ProductMaterial { get; set; }
+
+ ///
+ /// 合作项目成本
+ ///
+ public decimal CooperationCost { get; set; }
+
+ ///
+ /// 店内支出
+ ///
+ public decimal StoreExpense { get; set; }
+
+ ///
+ /// 洗毛巾费用
+ ///
+ public decimal LaundryCost { get; set; }
+
+ ///
+ /// 毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾)
+ ///
+ public decimal GrossProfit { get; set; }
+
+ ///
+ /// 缺卡扣款
+ ///
+ public decimal MissingCard { get; set; }
+
+ ///
+ /// 迟到扣款
+ ///
+ public decimal LateArrival { get; set; }
+
+ ///
+ /// 请假扣款
+ ///
+ public decimal LeaveDeduction { get; set; }
+
+ ///
+ /// 扣社保
+ ///
+ public decimal SocialInsuranceDeduction { get; set; }
+
+ ///
+ /// 扣除奖励
+ ///
+ public decimal RewardDeduction { get; set; }
+
+ ///
+ /// 扣住宿费
+ ///
+ public decimal AccommodationDeduction { get; set; }
+
+ ///
+ /// 扣学习期费用
+ ///
+ public decimal StudyPeriodDeduction { get; set; }
+
+ ///
+ /// 扣工作服费用
+ ///
+ public decimal WorkClothesDeduction { get; set; }
+
+ ///
+ /// 当月培训补贴
+ ///
+ public decimal MonthlyTrainingSubsidy { get; set; }
+
+ ///
+ /// 当月交通补贴
+ ///
+ public decimal MonthlyTransportSubsidy { get; set; }
+
+ ///
+ /// 上月培训补贴
+ ///
+ public decimal LastMonthTrainingSubsidy { get; set; }
+
+ ///
+ /// 上月交通补贴
+ ///
+ public decimal LastMonthTransportSubsidy { get; set; }
+
+ ///
+ /// 员工确认时间
+ ///
+ public DateTime? EmployeeConfirmTime { get; set; }
+
+ ///
+ /// 员工确认备注
+ ///
+ public string EmployeeConfirmRemark { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs
index 09e3973..d2f03c9 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDirectorSalaryService.cs
@@ -80,12 +80,24 @@ namespace NCC.Extend
var list = await query.Select(x => new DirectorSalaryOutput
{
Id = x.Id,
+ StoreId = x.StoreId,
StoreName = x.StoreName,
+ EmployeeId = x.EmployeeId,
EmployeeName = x.EmployeeName,
Position = x.Position,
+ StatisticsMonth = x.StatisticsMonth,
+ StoreType = x.StoreType,
+ StoreCategory = x.StoreCategory,
+ IsNewStore = x.IsNewStore,
+ NewStoreProtectionStage = x.NewStoreProtectionStage,
StoreTotalPerformance = x.StoreTotalPerformance,
StoreBillingPerformance = x.StoreBillingPerformance,
StoreRefundPerformance = x.StoreRefundPerformance,
+ ProductMaterial = x.ProductMaterial,
+ CooperationCost = x.CooperationCost,
+ StoreExpense = x.StoreExpense,
+ LaundryCost = x.LaundryCost,
+ GrossProfit = x.GrossProfit,
StoreLifeline = x.StoreLifeline,
PerformanceCompletionRate = x.PerformanceCompletionRate,
PerformanceReached = x.PerformanceReached,
@@ -108,7 +120,19 @@ namespace NCC.Extend
LeaveDays = x.LeaveDays,
GrossSalary = x.GrossSalary,
ActualSalary = x.ActualSalary,
+ MissingCard = x.MissingCard,
+ LateArrival = x.LateArrival,
+ LeaveDeduction = x.LeaveDeduction,
+ SocialInsuranceDeduction = x.SocialInsuranceDeduction,
+ RewardDeduction = x.RewardDeduction,
+ AccommodationDeduction = x.AccommodationDeduction,
+ StudyPeriodDeduction = x.StudyPeriodDeduction,
+ WorkClothesDeduction = x.WorkClothesDeduction,
TotalDeduction = x.TotalDeduction,
+ MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy,
+ MonthlyTransportSubsidy = x.MonthlyTransportSubsidy,
+ LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy,
+ LastMonthTransportSubsidy = x.LastMonthTransportSubsidy,
TotalSubsidy = x.TotalSubsidy,
Bonus = x.Bonus,
ReturnPhoneDeposit = x.ReturnPhoneDeposit,
@@ -120,11 +144,9 @@ namespace NCC.Extend
MonthlyTotalPayment = x.MonthlyTotalPayment,
IsLocked = x.IsLocked,
EmployeeConfirmStatus = x.EmployeeConfirmStatus,
- UpdateTime = x.UpdateTime,
- StoreType = x.StoreType,
- StoreCategory = x.StoreCategory,
- IsNewStore = x.IsNewStore,
- NewStoreProtectionStage = x.NewStoreProtectionStage
+ EmployeeConfirmTime = x.EmployeeConfirmTime,
+ EmployeeConfirmRemark = x.EmployeeConfirmRemark,
+ UpdateTime = x.UpdateTime
})
.ToPagedListAsync(input.currentPage, input.pageSize);
@@ -147,36 +169,72 @@ namespace NCC.Extend
.Select(x => new DirectorSalaryOutput
{
Id = x.Id,
+ StoreId = x.StoreId,
StoreName = x.StoreName,
+ EmployeeId = x.EmployeeId,
EmployeeName = x.EmployeeName,
Position = x.Position,
+ StatisticsMonth = x.StatisticsMonth,
+ StoreType = x.StoreType,
+ StoreCategory = x.StoreCategory,
+ IsNewStore = x.IsNewStore,
+ NewStoreProtectionStage = x.NewStoreProtectionStage,
StoreTotalPerformance = x.StoreTotalPerformance,
StoreBillingPerformance = x.StoreBillingPerformance,
StoreRefundPerformance = x.StoreRefundPerformance,
+ ProductMaterial = x.ProductMaterial,
+ CooperationCost = x.CooperationCost,
+ StoreExpense = x.StoreExpense,
+ LaundryCost = x.LaundryCost,
+ GrossProfit = x.GrossProfit,
StoreLifeline = x.StoreLifeline,
PerformanceCompletionRate = x.PerformanceCompletionRate,
+ PerformanceReached = x.PerformanceReached,
+ HeadCountReached = x.HeadCountReached,
+ ConsumeReached = x.ConsumeReached,
+ AssessmentDeduction = x.AssessmentDeduction,
+ UnreachedIndicatorCount = x.UnreachedIndicatorCount,
+ HeadCount = x.HeadCount,
+ TargetHeadCount = x.TargetHeadCount,
+ StoreConsume = x.StoreConsume,
+ TargetConsume = x.TargetConsume,
CommissionRateBelowLifeline = x.CommissionRateBelowLifeline,
CommissionRateAboveLifeline = x.CommissionRateAboveLifeline,
CommissionAmountBelowLifeline = x.CommissionAmountBelowLifeline,
CommissionAmountAboveLifeline = x.CommissionAmountAboveLifeline,
TotalCommissionAmount = x.TotalCommissionAmount,
BaseSalary = x.BaseSalary,
+ ActualBaseSalary = x.ActualBaseSalary,
WorkingDays = x.WorkingDays,
LeaveDays = x.LeaveDays,
GrossSalary = x.GrossSalary,
ActualSalary = x.ActualSalary,
+ MissingCard = x.MissingCard,
+ LateArrival = x.LateArrival,
+ LeaveDeduction = x.LeaveDeduction,
+ SocialInsuranceDeduction = x.SocialInsuranceDeduction,
+ RewardDeduction = x.RewardDeduction,
+ AccommodationDeduction = x.AccommodationDeduction,
+ StudyPeriodDeduction = x.StudyPeriodDeduction,
+ WorkClothesDeduction = x.WorkClothesDeduction,
TotalDeduction = x.TotalDeduction,
+ MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy,
+ MonthlyTransportSubsidy = x.MonthlyTransportSubsidy,
+ LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy,
+ LastMonthTransportSubsidy = x.LastMonthTransportSubsidy,
TotalSubsidy = x.TotalSubsidy,
Bonus = x.Bonus,
+ ReturnPhoneDeposit = x.ReturnPhoneDeposit,
+ ReturnAccommodationDeposit = x.ReturnAccommodationDeposit,
MonthlyPaymentStatus = x.MonthlyPaymentStatus,
PaidAmount = x.PaidAmount,
PendingAmount = x.PendingAmount,
+ LastMonthSupplement = x.LastMonthSupplement,
+ MonthlyTotalPayment = x.MonthlyTotalPayment,
IsLocked = x.IsLocked,
EmployeeConfirmStatus = x.EmployeeConfirmStatus,
- StoreType = x.StoreType,
- StoreCategory = x.StoreCategory,
- IsNewStore = x.IsNewStore,
- NewStoreProtectionStage = x.NewStoreProtectionStage,
+ EmployeeConfirmTime = x.EmployeeConfirmTime,
+ EmployeeConfirmRemark = x.EmployeeConfirmRemark,
UpdateTime = x.UpdateTime
})
.FirstAsync();
@@ -478,8 +536,8 @@ namespace NCC.Extend
// 毛利 = 销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾
salary.GrossProfit = salary.SalesPerformance - salary.ProductMaterial - salary.CooperationCost - salary.StoreExpense - salary.LaundryCost;
- // 2.7 将毛利赋值给StoreTotalPerformance(用于提成计算)
- salary.StoreTotalPerformance = salary.GrossProfit;
+ // 2.7 StoreTotalPerformance保存开单业绩-退卡业绩(销售业绩),提成计算使用毛利(GrossProfit)
+ salary.StoreTotalPerformance = salary.SalesPerformance;
// 2.8 计算业绩完成率(基于毛利与生命线比较)
if (salary.StoreLifeline > 0)
@@ -670,9 +728,10 @@ namespace NCC.Extend
return;
}
- // 提成计算基于毛利(StoreTotalPerformance存储的是毛利)
+ // 提成计算基于毛利(GrossProfit)
// 重要:提成基数使用毛利,不是销售业绩(开单-退卡)
- decimal grossProfit = salary.StoreTotalPerformance; // 这里已经是毛利了
+ // StoreTotalPerformance存储的是开单业绩-退卡业绩,而提成需要使用毛利
+ decimal grossProfit = salary.GrossProfit;
decimal lifeline = salary.StoreLifeline;
// 确定提成比例(根据新店/老店和门店分类)
@@ -1057,59 +1116,159 @@ namespace NCC.Extend
CreateUser = ""
};
- // Excel字段映射(主任工资43列,Excel顺序:门店名称,员工姓名,岗位,实发工资,补贴合计,扣款合计,是否锁定,是否新店,新店保护阶段,门店总业绩...)
+ // Excel字段映射
+ // 向后兼容:先按旧格式读取(43列格式)
+ // 如果Excel列数更多,则按新格式读取(支持新增字段)
entity.StoreName = storeName;
entity.EmployeeName = employeeName;
entity.Position = GetColumnValue(2 + offset);
- entity.ActualSalary = ParseDecimal(GetColumnValue(3 + offset));
- entity.TotalSubsidy = ParseDecimal(GetColumnValue(4 + offset));
- entity.TotalDeduction = ParseDecimal(GetColumnValue(5 + offset));
- // 跳过"是否锁定"字段(第7列),在最后处理
- entity.IsNewStore = GetColumnValue(7 + offset) == "是" ? "是" : "否";
- entity.NewStoreProtectionStage = ParseInt(GetColumnValue(8 + offset));
- entity.StoreTotalPerformance = ParseDecimal(GetColumnValue(9 + offset));
- entity.StoreBillingPerformance = ParseDecimal(GetColumnValue(10 + offset));
- entity.StoreRefundPerformance = ParseDecimal(GetColumnValue(11 + offset));
- entity.StoreLifeline = ParseDecimal(GetColumnValue(12 + offset));
- entity.PerformanceCompletionRate = ParseDecimal(GetColumnValue(13 + offset));
- entity.PerformanceReached = GetColumnValue(14 + offset);
- entity.HeadCountReached = GetColumnValue(15 + offset);
- entity.ConsumeReached = GetColumnValue(16 + offset);
- entity.AssessmentDeduction = ParseDecimal(GetColumnValue(17 + offset));
- entity.UnreachedIndicatorCount = ParseInt(GetColumnValue(18 + offset));
- entity.HeadCount = ParseInt(GetColumnValue(19 + offset));
- entity.TargetHeadCount = ParseDecimal(GetColumnValue(20 + offset));
- entity.StoreConsume = ParseDecimal(GetColumnValue(21 + offset));
- entity.TargetConsume = ParseDecimal(GetColumnValue(22 + offset));
- entity.CommissionRateBelowLifeline = ParseDecimal(GetColumnValue(23 + offset));
- entity.CommissionRateAboveLifeline = ParseDecimal(GetColumnValue(24 + offset));
- entity.CommissionAmountBelowLifeline = ParseDecimal(GetColumnValue(25 + offset));
- entity.CommissionAmountAboveLifeline = ParseDecimal(GetColumnValue(26 + offset));
- entity.TotalCommissionAmount = ParseDecimal(GetColumnValue(27 + offset));
- entity.BaseSalary = ParseDecimal(GetColumnValue(28 + offset));
- entity.ActualBaseSalary = ParseDecimal(GetColumnValue(29 + offset));
- entity.WorkingDays = ParseInt(GetColumnValue(30 + offset));
- entity.LeaveDays = ParseInt(GetColumnValue(31 + offset));
- entity.GrossSalary = ParseDecimal(GetColumnValue(32 + offset));
- entity.Bonus = ParseDecimal(GetColumnValue(33 + offset));
- entity.ReturnPhoneDeposit = ParseDecimal(GetColumnValue(34 + offset));
- entity.ReturnAccommodationDeposit = ParseDecimal(GetColumnValue(35 + offset));
- entity.MonthlyPaymentStatus = GetColumnValue(36 + offset);
- entity.PaidAmount = ParseDecimal(GetColumnValue(37 + offset));
- entity.PendingAmount = ParseDecimal(GetColumnValue(38 + offset));
- entity.LastMonthSupplement = ParseDecimal(GetColumnValue(39 + offset));
- entity.MonthlyTotalPayment = ParseDecimal(GetColumnValue(40 + offset));
- // 处理门店类型和类别
- var storeTypeStr = GetColumnValue(41 + offset);
- if (!string.IsNullOrWhiteSpace(storeTypeStr) && int.TryParse(storeTypeStr, out int storeType))
- entity.StoreType = storeType;
- var storeCategoryStr = GetColumnValue(42 + offset);
- if (!string.IsNullOrWhiteSpace(storeCategoryStr) && int.TryParse(storeCategoryStr, out int storeCategory))
- entity.StoreCategory = storeCategory;
- // 处理锁定状态(第7列)
- var isLockedStr = GetColumnValue(6 + offset);
- if (isLockedStr == "已锁定" || isLockedStr == "1" || isLockedStr == "锁定") entity.IsLocked = 1;
- else entity.IsLocked = 0;
+
+ // 旧格式兼容(保持向后兼容,Excel顺序:门店名称,员工姓名,岗位,实发工资,补贴合计,扣款合计,是否锁定,是否新店,新店保护阶段,门店总业绩...)
+ if (dataTable.Columns.Count <= 43 + offset)
+ {
+ // 旧格式(43列)
+ entity.ActualSalary = ParseDecimal(GetColumnValue(3 + offset));
+ entity.TotalSubsidy = ParseDecimal(GetColumnValue(4 + offset));
+ entity.TotalDeduction = ParseDecimal(GetColumnValue(5 + offset));
+ var isLockedStr = GetColumnValue(6 + offset);
+ if (isLockedStr == "已锁定" || isLockedStr == "1" || isLockedStr == "锁定") entity.IsLocked = 1;
+ else entity.IsLocked = 0;
+ entity.IsNewStore = GetColumnValue(7 + offset) == "是" ? "是" : "否";
+ entity.NewStoreProtectionStage = ParseInt(GetColumnValue(8 + offset));
+ entity.StoreTotalPerformance = ParseDecimal(GetColumnValue(9 + offset));
+ entity.StoreBillingPerformance = ParseDecimal(GetColumnValue(10 + offset));
+ entity.StoreRefundPerformance = ParseDecimal(GetColumnValue(11 + offset));
+ entity.StoreLifeline = ParseDecimal(GetColumnValue(12 + offset));
+ entity.PerformanceCompletionRate = ParseDecimal(GetColumnValue(13 + offset));
+ entity.PerformanceReached = GetColumnValue(14 + offset);
+ entity.HeadCountReached = GetColumnValue(15 + offset);
+ entity.ConsumeReached = GetColumnValue(16 + offset);
+ entity.AssessmentDeduction = ParseDecimal(GetColumnValue(17 + offset));
+ entity.UnreachedIndicatorCount = ParseInt(GetColumnValue(18 + offset));
+ entity.HeadCount = ParseInt(GetColumnValue(19 + offset));
+ entity.TargetHeadCount = ParseDecimal(GetColumnValue(20 + offset));
+ entity.StoreConsume = ParseDecimal(GetColumnValue(21 + offset));
+ entity.TargetConsume = ParseDecimal(GetColumnValue(22 + offset));
+ entity.CommissionRateBelowLifeline = ParseDecimal(GetColumnValue(23 + offset));
+ entity.CommissionRateAboveLifeline = ParseDecimal(GetColumnValue(24 + offset));
+ entity.CommissionAmountBelowLifeline = ParseDecimal(GetColumnValue(25 + offset));
+ entity.CommissionAmountAboveLifeline = ParseDecimal(GetColumnValue(26 + offset));
+ entity.TotalCommissionAmount = ParseDecimal(GetColumnValue(27 + offset));
+ entity.BaseSalary = ParseDecimal(GetColumnValue(28 + offset));
+ entity.ActualBaseSalary = ParseDecimal(GetColumnValue(29 + offset));
+ entity.WorkingDays = ParseInt(GetColumnValue(30 + offset));
+ entity.LeaveDays = ParseInt(GetColumnValue(31 + offset));
+ entity.GrossSalary = ParseDecimal(GetColumnValue(32 + offset));
+ entity.Bonus = ParseDecimal(GetColumnValue(33 + offset));
+ entity.ReturnPhoneDeposit = ParseDecimal(GetColumnValue(34 + offset));
+ entity.ReturnAccommodationDeposit = ParseDecimal(GetColumnValue(35 + offset));
+ entity.MonthlyPaymentStatus = GetColumnValue(36 + offset);
+ entity.PaidAmount = ParseDecimal(GetColumnValue(37 + offset));
+ entity.PendingAmount = ParseDecimal(GetColumnValue(38 + offset));
+ entity.LastMonthSupplement = ParseDecimal(GetColumnValue(39 + offset));
+ entity.MonthlyTotalPayment = ParseDecimal(GetColumnValue(40 + offset));
+ var storeTypeStr = GetColumnValue(41 + offset);
+ if (!string.IsNullOrWhiteSpace(storeTypeStr) && int.TryParse(storeTypeStr, out int storeType))
+ entity.StoreType = storeType;
+ var storeCategoryStr = GetColumnValue(42 + offset);
+ if (!string.IsNullOrWhiteSpace(storeCategoryStr) && int.TryParse(storeCategoryStr, out int storeCategory))
+ entity.StoreCategory = storeCategory;
+ }
+ else
+ {
+ // 新格式:按列名匹配(更灵活)
+ // 由于前端导出是基于 tableColumns 动态生成,列顺序可能变化
+ // 这里使用列名匹配方式,提高兼容性
+ var columnNameMap = new Dictionary();
+ for (int colIdx = 0; colIdx < dataTable.Columns.Count; colIdx++)
+ {
+ var colName = dataTable.Columns[colIdx].ColumnName?.Trim() ?? "";
+ if (!string.IsNullOrWhiteSpace(colName))
+ {
+ columnNameMap[colName] = colIdx;
+ }
+ }
+
+ // 辅助方法:根据列名获取值
+ Func GetValueByColumnName = (columnName) =>
+ {
+ if (columnNameMap.ContainsKey(columnName))
+ {
+ var colIdx = columnNameMap[columnName];
+ return GetColumnValue(colIdx);
+ }
+ return "";
+ };
+
+ // 按列名读取字段(支持新字段)
+ entity.StoreTotalPerformance = ParseDecimal(GetValueByColumnName("门店总业绩"));
+ entity.StoreBillingPerformance = ParseDecimal(GetValueByColumnName("门店开单业绩"));
+ entity.StoreRefundPerformance = ParseDecimal(GetValueByColumnName("门店退卡业绩"));
+ // 销售业绩字段不在导入中处理,仅在计算时使用
+ entity.ProductMaterial = ParseDecimal(GetValueByColumnName("产品物料"));
+ entity.CooperationCost = ParseDecimal(GetValueByColumnName("合作项目成本"));
+ entity.StoreExpense = ParseDecimal(GetValueByColumnName("店内支出"));
+ entity.LaundryCost = ParseDecimal(GetValueByColumnName("洗毛巾费用"));
+ entity.GrossProfit = ParseDecimal(GetValueByColumnName("毛利"));
+ entity.StoreLifeline = ParseDecimal(GetValueByColumnName("门店生命线"));
+ entity.PerformanceCompletionRate = ParseDecimal(GetValueByColumnName("业绩完成率"));
+ entity.PerformanceReached = GetValueByColumnName("业绩是否达标");
+ entity.HeadCountReached = GetValueByColumnName("人头是否达标");
+ entity.ConsumeReached = GetValueByColumnName("消耗是否达标");
+ entity.AssessmentDeduction = ParseDecimal(GetValueByColumnName("考核扣款金额"));
+ entity.UnreachedIndicatorCount = ParseInt(GetValueByColumnName("未达标指标数量"));
+ entity.HeadCount = ParseInt(GetValueByColumnName("进店消耗人数"));
+ entity.TargetHeadCount = ParseDecimal(GetValueByColumnName("目标人头数"));
+ entity.StoreConsume = ParseDecimal(GetValueByColumnName("门店消耗金额"));
+ entity.TargetConsume = ParseDecimal(GetValueByColumnName("目标消耗金额"));
+ entity.CommissionRateBelowLifeline = ParseDecimal(GetValueByColumnName("≤生命线部分提成比例"));
+ entity.CommissionRateAboveLifeline = ParseDecimal(GetValueByColumnName(">生命线部分提成比例"));
+ entity.CommissionAmountBelowLifeline = ParseDecimal(GetValueByColumnName("≤生命线部分提成金额"));
+ entity.CommissionAmountAboveLifeline = ParseDecimal(GetValueByColumnName(">生命线部分提成金额"));
+ entity.TotalCommissionAmount = ParseDecimal(GetValueByColumnName("提成总金额"));
+ entity.BaseSalary = ParseDecimal(GetValueByColumnName("底薪金额"));
+ entity.ActualBaseSalary = ParseDecimal(GetValueByColumnName("实际底薪"));
+ entity.WorkingDays = ParseInt(GetValueByColumnName("在店天数"));
+ entity.LeaveDays = ParseInt(GetValueByColumnName("请假天数"));
+ entity.GrossSalary = ParseDecimal(GetValueByColumnName("应发工资"));
+ entity.ActualSalary = ParseDecimal(GetValueByColumnName("实发工资"));
+ entity.MissingCard = ParseDecimal(GetValueByColumnName("缺卡扣款"));
+ entity.LateArrival = ParseDecimal(GetValueByColumnName("迟到扣款"));
+ entity.LeaveDeduction = ParseDecimal(GetValueByColumnName("请假扣款"));
+ entity.SocialInsuranceDeduction = ParseDecimal(GetValueByColumnName("扣社保"));
+ entity.RewardDeduction = ParseDecimal(GetValueByColumnName("扣除奖励"));
+ entity.AccommodationDeduction = ParseDecimal(GetValueByColumnName("扣住宿费"));
+ entity.StudyPeriodDeduction = ParseDecimal(GetValueByColumnName("扣学习期费用"));
+ entity.WorkClothesDeduction = ParseDecimal(GetValueByColumnName("扣工作服费用"));
+ entity.TotalDeduction = ParseDecimal(GetValueByColumnName("扣款合计"));
+ entity.MonthlyTrainingSubsidy = ParseDecimal(GetValueByColumnName("当月培训补贴"));
+ entity.MonthlyTransportSubsidy = ParseDecimal(GetValueByColumnName("当月交通补贴"));
+ entity.LastMonthTrainingSubsidy = ParseDecimal(GetValueByColumnName("上月培训补贴"));
+ entity.LastMonthTransportSubsidy = ParseDecimal(GetValueByColumnName("上月交通补贴"));
+ entity.TotalSubsidy = ParseDecimal(GetValueByColumnName("补贴合计"));
+ entity.Bonus = ParseDecimal(GetValueByColumnName("发奖金"));
+ entity.ReturnPhoneDeposit = ParseDecimal(GetValueByColumnName("退手机押金"));
+ entity.ReturnAccommodationDeposit = ParseDecimal(GetValueByColumnName("退住宿押金"));
+ entity.MonthlyPaymentStatus = GetValueByColumnName("当月是否发放");
+ entity.PaidAmount = ParseDecimal(GetValueByColumnName("支付金额"));
+ entity.PendingAmount = ParseDecimal(GetValueByColumnName("待支付金额"));
+ entity.LastMonthSupplement = ParseDecimal(GetValueByColumnName("补发上月"));
+ entity.MonthlyTotalPayment = ParseDecimal(GetValueByColumnName("当月支付总额"));
+ var isLockedStrNew = GetValueByColumnName("锁定状态");
+ if (isLockedStrNew == "已锁定" || isLockedStrNew == "1" || isLockedStrNew == "锁定") entity.IsLocked = 1;
+ else entity.IsLocked = 0;
+ var confirmStatusStr = GetValueByColumnName("确认状态");
+ if (confirmStatusStr == "已确认" || confirmStatusStr == "1") entity.EmployeeConfirmStatus = 1;
+ else entity.EmployeeConfirmStatus = 0;
+ var storeTypeStrNew = GetValueByColumnName("门店类型");
+ if (!string.IsNullOrWhiteSpace(storeTypeStrNew) && int.TryParse(storeTypeStrNew, out int storeTypeNew))
+ entity.StoreType = storeTypeNew;
+ var storeCategoryStrNew = GetValueByColumnName("门店类别");
+ if (!string.IsNullOrWhiteSpace(storeCategoryStrNew) && int.TryParse(storeCategoryStrNew, out int storeCategoryNew))
+ entity.StoreCategory = storeCategoryNew;
+ entity.IsNewStore = GetValueByColumnName("是否新店") == "是" ? "是" : "否";
+ entity.NewStoreProtectionStage = ParseInt(GetValueByColumnName("新店保护阶段"));
+ }
if (existing != null)
{
@@ -1159,10 +1318,12 @@ namespace NCC.Extend
if (recordsToInsert.Any()) await _db.Insertable(recordsToInsert).ExecuteCommandAsync();
if (recordsToUpdate.Any())
{
- // 使用IgnoreColumns排除CreateTime和CreateUser,确保其他所有字段都被更新
+ // 使用IgnoreColumns排除CreateTime、CreateUser、StoreId和EmployeeId,确保这些字段不会被更新
await _db.Updateable(recordsToUpdate)
.IgnoreColumns(x => x.CreateTime)
.IgnoreColumns(x => x.CreateUser)
+ .IgnoreColumns(x => x.StoreId)
+ .IgnoreColumns(x => x.EmployeeId)
.ExecuteCommandAsync();
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
index 68d1ed8..2fc079a 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
@@ -2152,10 +2152,121 @@ namespace NCC.Extend.LqKhxx
var itemDetailIds = itemDetails.Select(x => (string)x.Id).ToList();
- // 5. 批量查询消耗品项
+ // 4.5. 双向查询:直接查询会员的所有耗卡记录,确保不遗漏数据
+ // 通过耗卡记录反向查找可能遗漏的开单品项明细和开单记录
+ var allConsumedItemIds = new HashSet();
+ var additionalBillingItemIds = new HashSet();
+
+ if (memberIdsList.Any())
+ {
+ // 查询会员的所有耗卡记录
+ var allConsumedItemsFromConsume = await _db.Queryable(
+ (pxmx, hyhk) => new JoinQueryInfos(JoinType.Inner, pxmx.ConsumeInfoId == hyhk.Id))
+ .Where((pxmx, hyhk) => memberIdsList.Contains(hyhk.Hy)
+ && pxmx.IsEffective == StatusEnum.有效.GetHashCode()
+ && hyhk.IsEffective == StatusEnum.有效.GetHashCode())
+ .Where((pxmx, hyhk) => !string.IsNullOrEmpty(pxmx.BillingItemId))
+ .Select((pxmx, hyhk) => new
+ {
+ pxmx.BillingItemId,
+ pxmx.Px,
+ pxmx.Pxmc,
+ pxmx.Pxjg,
+ pxmx.ProjectNumber,
+ pxmx.TotalPrice
+ })
+ .ToListAsync();
+
+ // 收集所有耗卡记录关联的开单品项明细ID
+ foreach (var consumedItem in allConsumedItemsFromConsume)
+ {
+ if (!string.IsNullOrEmpty(consumedItem.BillingItemId))
+ {
+ allConsumedItemIds.Add(consumedItem.BillingItemId);
+ // 如果这个BillingItemId不在当前的itemDetailIds中,需要额外查询
+ if (!itemDetailIds.Contains(consumedItem.BillingItemId))
+ {
+ additionalBillingItemIds.Add(consumedItem.BillingItemId);
+ }
+ }
+ }
+
+ // 如果有额外的开单品项明细ID,查询这些品项明细对应的开单记录
+ if (additionalBillingItemIds.Any())
+ {
+ // 查询这些开单品项明细
+ var additionalItemDetailsData = await _db.Queryable()
+ .Where(x => additionalBillingItemIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .Select(x => new
+ {
+ x.Id,
+ x.Glkdbh,
+ x.Px,
+ x.Pxmc,
+ x.Pxjg,
+ x.SourceType,
+ x.ProjectNumber,
+ x.TotalPrice
+ })
+ .ToListAsync();
+
+ // 将额外的品项明细添加到itemDetails中
+ itemDetails.AddRange(additionalItemDetailsData.Cast());
+ itemDetailIds.AddRange(additionalItemDetailsData.Select(x => x.Id));
+
+ // 查询这些品项明细对应的开单记录(如果不在当前的billingIds中)
+ var additionalBillingIds = additionalItemDetailsData
+ .Select(x => x.Glkdbh)
+ .Where(x => !string.IsNullOrEmpty(x) && !billingIds.Contains(x))
+ .Distinct()
+ .ToList();
+
+ if (additionalBillingIds.Any())
+ {
+ var additionalBillingRecords = await _db.Queryable()
+ .Where(x => additionalBillingIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .Select(x => new
+ {
+ x.Id,
+ x.Kdhy,
+ x.Kdrq,
+ x.Djmd,
+ x.CreateUser
+ })
+ .ToListAsync();
+
+ // 将额外的开单记录添加到billingRecords中(转换为dynamic以便后续使用)
+ foreach (var record in additionalBillingRecords)
+ {
+ billingRecords.Add(record);
+ }
+ billingIds.AddRange(additionalBillingIds);
+ }
+ }
+ }
+
+ // 5. 批量查询消耗品项(包括所有通过耗卡找到的BillingItemId)
var consumedItems = new List();
- if (itemDetailIds.Any())
+ if (allConsumedItemIds.Any())
{
+ // 使用allConsumedItemIds(来自耗卡记录的所有BillingItemId)查询消耗品项
+ var consumedItemsData = await _db.Queryable()
+ .Where(x => allConsumedItemIds.Contains(x.BillingItemId) && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .Select(x => new
+ {
+ x.BillingItemId,
+ x.Px,
+ x.Pxmc,
+ x.Pxjg,
+ x.ProjectNumber,
+ x.TotalPrice
+ })
+ .ToListAsync();
+ consumedItems = consumedItemsData.Cast().ToList();
+ }
+ else if (itemDetailIds.Any())
+ {
+ // 兼容原有逻辑:如果没有耗卡记录,使用itemDetailIds查询
var consumedItemsData = await _db.Queryable()
.Where(x => itemDetailIds.Contains(x.BillingItemId) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new
@@ -2171,7 +2282,7 @@ namespace NCC.Extend.LqKhxx
consumedItems = consumedItemsData.Cast().ToList();
}
- // 6. 批量查询退卡品项
+ // 6. 批量查询退卡品项(使用itemDetailIds,因为退卡也是关联开单品项明细)
var refundedItems = new List();
if (itemDetailIds.Any())
{
@@ -2190,6 +2301,113 @@ namespace NCC.Extend.LqKhxx
refundedItems = refundedItemsData.Cast().ToList();
}
+ // 6.5. 双向查询退卡品项:直接从会员的退卡记录查询,确保不遗漏
+ if (memberIdsList.Any())
+ {
+ var allRefundedItemsFromRefund = await _db.Queryable(
+ (mx, hytk) => new JoinQueryInfos(JoinType.Inner, mx.RefundInfoId == hytk.Id))
+ .Where((mx, hytk) => memberIdsList.Contains(hytk.Hy)
+ && mx.IsEffective == StatusEnum.有效.GetHashCode()
+ && hytk.IsEffective == StatusEnum.有效.GetHashCode())
+ .Where((mx, hytk) => !string.IsNullOrEmpty(mx.BillingItemId))
+ .Select((mx, hytk) => new
+ {
+ mx.BillingItemId,
+ mx.Px,
+ mx.Pxmc,
+ mx.Pxjg,
+ mx.ProjectNumber,
+ mx.Tkje
+ })
+ .ToListAsync();
+
+ // 收集退卡记录关联的开单品项明细ID
+ var refundedBillingItemIds = allRefundedItemsFromRefund
+ .Select(x => x.BillingItemId)
+ .Where(x => !string.IsNullOrEmpty(x))
+ .Distinct()
+ .ToList();
+
+ // 如果退卡关联的开单品项明细不在当前的itemDetailIds中,也需要查询对应的开单记录
+ var missingRefundedBillingItemIds = refundedBillingItemIds
+ .Where(x => !itemDetailIds.Contains(x))
+ .ToList();
+
+ if (missingRefundedBillingItemIds.Any())
+ {
+ // 查询这些开单品项明细
+ var refundedAdditionalItemDetailsData = await _db.Queryable()
+ .Where(x => missingRefundedBillingItemIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .Select(x => new
+ {
+ x.Id,
+ x.Glkdbh,
+ x.Px,
+ x.Pxmc,
+ x.Pxjg,
+ x.SourceType,
+ x.ProjectNumber,
+ x.TotalPrice
+ })
+ .ToListAsync();
+
+ // 将额外的品项明细添加到itemDetails中
+ itemDetails.AddRange(refundedAdditionalItemDetailsData.Cast());
+ itemDetailIds.AddRange(refundedAdditionalItemDetailsData.Select(x => x.Id));
+
+ // 查询这些品项明细对应的开单记录(如果不在当前的billingIds中)
+ var refundedAdditionalBillingIds = refundedAdditionalItemDetailsData
+ .Select(x => x.Glkdbh)
+ .Where(x => !string.IsNullOrEmpty(x) && !billingIds.Contains(x))
+ .Distinct()
+ .ToList();
+
+ if (refundedAdditionalBillingIds.Any())
+ {
+ var refundedAdditionalBillingRecords = await _db.Queryable()
+ .Where(x => refundedAdditionalBillingIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .Select(x => new
+ {
+ x.Id,
+ x.Kdhy,
+ x.Kdrq,
+ x.Djmd,
+ x.CreateUser
+ })
+ .ToListAsync();
+
+ // 将额外的开单记录添加到billingRecords中(转换为dynamic以便后续使用)
+ foreach (var record in refundedAdditionalBillingRecords)
+ {
+ billingRecords.Add(record);
+ }
+ billingIds.AddRange(refundedAdditionalBillingIds);
+ }
+
+ // 将退卡数据添加到refundedItems中(去重)
+ foreach (var refundedItem in allRefundedItemsFromRefund)
+ {
+ var refundedItemDynamic = refundedItem as dynamic;
+ if (!refundedItems.Any(x => x.BillingItemId?.ToString() == refundedItemDynamic.BillingItemId?.ToString()))
+ {
+ refundedItems.Add(refundedItemDynamic);
+ }
+ }
+ }
+ else
+ {
+ // 如果所有退卡记录关联的开单品项明细都在itemDetailIds中,只需要合并退卡数据(去重)
+ foreach (var refundedItem in allRefundedItemsFromRefund)
+ {
+ var refundedItemDynamic = refundedItem as dynamic;
+ if (!refundedItems.Any(x => x.BillingItemId?.ToString() == refundedItemDynamic.BillingItemId?.ToString()))
+ {
+ refundedItems.Add(refundedItemDynamic);
+ }
+ }
+ }
+ }
+
// 7. 批量查询储扣品项
var deductedItems = new List();
if (billingIds.Any())
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
index db2e3c0..cf54677 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
@@ -262,6 +262,44 @@ namespace NCC.Extend.LqReimbursementApplication
List queryCompletionTime = input.completionTime != null ? input.completionTime.Split(',').ToObeject>() : null;
DateTime? startCompletionTime = queryCompletionTime != null ? Ext.GetDateTime(queryCompletionTime.First()) : null;
DateTime? endCompletionTime = queryCompletionTime != null ? Ext.GetDateTime(queryCompletionTime.Last()) : null;
+ // 如果提供了完成时间筛选,需要先查询符合条件的申请ID,然后在主查询中过滤
+ List filteredApplicationIdsByCompletionTime = null;
+ if (queryCompletionTime != null)
+ {
+ var startDate = new DateTime(startCompletionTime.ToDate().Year, startCompletionTime.ToDate().Month, startCompletionTime.ToDate().Day, 0, 0, 0);
+ var endDate = new DateTime(endCompletionTime.ToDate().Year, endCompletionTime.ToDate().Month, endCompletionTime.ToDate().Day, 23, 59, 59);
+
+ // 先查询实体类中有CompletionTime字段且符合条件的申请ID
+ var entitiesWithCompletionTime = await _db.Queryable()
+ .Where(x => x.CompletionTime.HasValue
+ && x.CompletionTime.Value >= startDate
+ && x.CompletionTime.Value <= endDate)
+ .Select(x => x.Id)
+ .ToListAsync();
+
+ // 查询实体类中没有CompletionTime字段的申请,需要从审批记录中获取完成时间
+ var entitiesWithoutCompletionTime = await _db.Queryable()
+ .Where(x => !x.CompletionTime.HasValue)
+ .Select(x => x.Id)
+ .ToListAsync();
+
+ var applicationIdsFromRecords = new List();
+ if (entitiesWithoutCompletionTime.Any())
+ {
+ // 从审批记录中查询符合条件的申请ID
+ applicationIdsFromRecords = await _db.Queryable()
+ .Where(x => entitiesWithoutCompletionTime.Contains(x.ApplicationId) && x.ApprovalResult == "通过")
+ .GroupBy(x => x.ApplicationId)
+ .Having(x => SqlFunc.AggregateMax(x.ApprovalTime) >= startDate &&
+ SqlFunc.AggregateMax(x.ApprovalTime) <= endDate)
+ .Select(x => x.ApplicationId)
+ .ToListAsync();
+ }
+
+ // 合并两种情况的申请ID
+ filteredApplicationIdsByCompletionTime = entitiesWithCompletionTime.Union(applicationIdsFromRecords).ToList();
+ }
+
var query = _db.Queryable()
.WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
.WhereIF(!string.IsNullOrEmpty(input.applicationUserId), p => p.ApplicationUserId.Contains(input.applicationUserId))
@@ -274,7 +312,11 @@ namespace NCC.Extend.LqReimbursementApplication
.WhereIF(!string.IsNullOrEmpty(input.approveStatus), p => (p.ApprovalStatus ?? p.ApproveStatus).Contains(input.approveStatus))
// .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0))
//.WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59))
- .WhereIF(!string.IsNullOrEmpty(input.purchaseRecordsId), p => p.PurchaseRecordsId.Contains(input.purchaseRecordsId));
+ .WhereIF(!string.IsNullOrEmpty(input.purchaseRecordsId), p => p.PurchaseRecordsId.Contains(input.purchaseRecordsId))
+ // 如果提供了完成时间筛选,在主查询中过滤
+ .WhereIF(filteredApplicationIdsByCompletionTime != null && filteredApplicationIdsByCompletionTime.Any(), p => filteredApplicationIdsByCompletionTime.Contains(p.Id))
+ // 如果没有符合条件的申请,返回空结果
+ .WhereIF(filteredApplicationIdsByCompletionTime != null && !filteredApplicationIdsByCompletionTime.Any(), p => false);
// 处理排序(兼容前端传入的字段名)
if (string.IsNullOrEmpty(input.sidx))
@@ -343,31 +385,6 @@ namespace NCC.Extend.LqReimbursementApplication
.GroupBy(x => (string)x.applicationId)
.ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => (string)x.approverName)));
- // 如果提供了完成时间筛选,需要先查询完成时间,然后过滤
- if (queryCompletionTime != null && applicationIds.Any())
- {
- var completionTimeRecords = await _db.Queryable()
- .Where(x => applicationIds.Contains(x.ApplicationId) && x.ApprovalResult == "通过")
- .GroupBy(x => x.ApplicationId)
- .Select(x => new
- {
- ApplicationId = x.ApplicationId,
- MaxApprovalTime = SqlFunc.AggregateMax(x.ApprovalTime)
- })
- .ToListAsync();
-
- var filteredApplicationIds = completionTimeRecords
- .Where(x => x.MaxApprovalTime.HasValue &&
- x.MaxApprovalTime.Value >= new DateTime(startCompletionTime.ToDate().Year, startCompletionTime.ToDate().Month, startCompletionTime.ToDate().Day, 0, 0, 0) &&
- x.MaxApprovalTime.Value <= new DateTime(endCompletionTime.ToDate().Year, endCompletionTime.ToDate().Month, endCompletionTime.ToDate().Day, 23, 59, 59))
- .Select(x => x.ApplicationId)
- .ToList();
-
- entities = entities.Where(x => filteredApplicationIds.Contains(x.Id)).ToList();
- total = entities.Count;
- applicationIds = entities.Select(x => x.Id).ToList();
- }
-
// 获取门店名称
var storeIds = entities.Where(x => !string.IsNullOrEmpty(x.ApplicationStoreId)).Select(x => x.ApplicationStoreId).Distinct().ToList();
var storeDict = new Dictionary();
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.cs
index cb2905f..8765b4d 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTechGeneralManagerSalaryService.cs
@@ -201,6 +201,24 @@ namespace NCC.Extend
///
/// 计算科技部总经理工资
///
+ ///
+ /// 计算科技部总经理的工资,包括底薪、溯源金额提成、Cell金额提成等。
+ ///
+ /// 计算规则:
+ /// - 底薪:固定4000元
+ /// - 溯源金额提成:根据管理的所有门店的溯源金额总和分段累进计算
+ /// - Cell金额提成:根据管理的所有门店的Cell金额总和分段累进计算
+ ///
+ /// 数据统计说明:
+ /// - 时间范围:严格按照当月范围(startDate 到 endDate 23:59:59),不包含下个月的数据
+ /// - 数据查询:使用原生SQL在数据库层面转换和求和,确保与科技部驾驶舱接口的计算方式一致
+ /// - 门店范围:通过门店的kjb字段确定科技部总经理管理的门店
+ ///
+ /// 溯源金额和Cell金额的计算方式:
+ /// - 开单金额:从 lq_kd_jksyj 表统计(使用原生SQL CAST转换)
+ /// - 退卡金额:从 lq_hytk_jksyj 表统计(使用SqlSugar聚合查询)
+ /// - 净金额 = 开单金额 - 退卡金额
+ ///
/// 年份
/// 月份
///
@@ -210,6 +228,12 @@ namespace NCC.Extend
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var monthStr = $"{year}{month:D2}";
+ // 使用与科技部驾驶舱接口相同的时间范围处理方式
+ var endDateTime = monthStr == DateTime.Now.ToString("yyyyMM")
+ ? DateTime.Now
+ : endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
+
+ _logger.LogInformation($"[科技部总经理工资计算] 开始计算,月份: {monthStr}, 时间范围: {startDate.ToString("yyyy-MM-dd HH:mm:ss")} 到 {endDateTime.ToString("yyyy-MM-dd HH:mm:ss")}");
// 1. 获取基础数据
@@ -229,9 +253,9 @@ namespace NCC.Extend
var techOrganizeIds = techOrganizeList.Select(x => x.Id).ToList();
var techOrganizeDict = techOrganizeList.ToDictionary(x => x.Id, x => x.FullName);
- // 1.2 从BASE_USER表查询岗位为"总经理"且组织ID在科技一部或科技二部的员工
+ // 1.2 从BASE_USER表查询岗位为"总经理"或"科技部总经理"且组织ID在科技一部或科技二部的员工
var techGeneralManagerUserList = await _db.Queryable()
- .Where(x => x.Gw == "总经理"
+ .Where(x => (x.Gw == "总经理" || x.Gw == "科技部总经理")
&& techOrganizeIds.Contains(x.OrganizeId)
&& x.DeleteMark == null && x.EnabledMark == 1)
.Select(x => new { x.Id, x.RealName, x.Account, x.Gw, x.OrganizeId, x.IsOnJob })
@@ -239,13 +263,8 @@ namespace NCC.Extend
if (!techGeneralManagerUserList.Any())
{
- // 如果没有科技部总经理员工,直接返回
- return;
- }
-
- if (!techGeneralManagerUserList.Any())
- {
- // 如果没有科技部总经理员工,直接返回
+ // 如果没有科技部总经理员工,记录日志并返回
+ _logger.LogWarning($"[科技部总经理工资计算] 未找到科技部总经理员工,科技部组织ID: {string.Join(",", techOrganizeIds)}");
return;
}
@@ -301,51 +320,110 @@ namespace NCC.Extend
var storeDetailDict = new Dictionary>();
// 按门店统计溯源和Cell金额(如果有管理的门店)
+ // 使用与科技部驾驶舱接口完全相同的查询方式:批量查询所有门店,然后按门店分组
if (allManagedStoreIds.Any())
{
+ // 时间格式化字符串(使用与科技部驾驶舱接口相同的格式化方式)
+ var startDateStr = startDate.ToString("yyyy-MM-dd HH:mm:ss");
+ var endDateTimeStr = endDateTime.ToString("yyyy-MM-dd HH:mm:ss");
+
+ // 批量查询所有门店的开单溯源金额(与科技部驾驶舱接口保持一致)
+ var allStoreTraceabilityBillingSql = $@"
+ SELECT F_StoreId, COALESCE(SUM(CAST(jksyj AS DECIMAL(18,2))), 0) as Amount
+ FROM lq_kd_jksyj
+ WHERE F_IsEffective = 1
+ AND F_StoreId IN ('{string.Join("','", allManagedStoreIds)}')
+ AND (F_BeautyType = '溯源系统' OR F_BeautyType = '溯源')
+ AND yjsj >= '{startDateStr}'
+ AND yjsj <= '{endDateTimeStr}'
+ GROUP BY F_StoreId";
+ var allStoreTraceabilityBillingResult = await _db.Ado.SqlQueryAsync(allStoreTraceabilityBillingSql);
+ var storeTraceabilityBillingDict = new Dictionary();
+ if (allStoreTraceabilityBillingResult != null)
+ {
+ foreach (var item in allStoreTraceabilityBillingResult)
+ {
+ var storeId = item.F_StoreId?.ToString() ?? "";
+ var amount = Convert.ToDecimal(item.Amount ?? 0);
+ storeTraceabilityBillingDict[storeId] = amount;
+ }
+ }
+
+ // 批量查询所有门店的开单Cell金额(与科技部驾驶舱接口保持一致)
+ var allStoreCellBillingSql = $@"
+ SELECT F_StoreId, COALESCE(SUM(CAST(jksyj AS DECIMAL(18,2))), 0) as Amount
+ FROM lq_kd_jksyj
+ WHERE F_IsEffective = 1
+ AND F_StoreId IN ('{string.Join("','", allManagedStoreIds)}')
+ AND (F_BeautyType = 'cell' OR F_BeautyType = 'Cell')
+ AND yjsj >= '{startDateStr}'
+ AND yjsj <= '{endDateTimeStr}'
+ GROUP BY F_StoreId";
+ var allStoreCellBillingResult = await _db.Ado.SqlQueryAsync(allStoreCellBillingSql);
+ var storeCellBillingDict = new Dictionary();
+ if (allStoreCellBillingResult != null)
+ {
+ foreach (var item in allStoreCellBillingResult)
+ {
+ var storeId = item.F_StoreId?.ToString() ?? "";
+ var amount = Convert.ToDecimal(item.Amount ?? 0);
+ storeCellBillingDict[storeId] = amount;
+ }
+ }
+
+ // 批量查询所有门店的退卡溯源金额
+ var allStoreTraceabilityRefundList = await _db.Queryable()
+ .Where(x => x.IsEffective == 1)
+ .Where(x => allManagedStoreIds.Contains(x.StoreId))
+ .Where(x => (x.BeautyType == "溯源系统" || x.BeautyType == "溯源"))
+ .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
+ .GroupBy(x => x.StoreId)
+ .Select(x => new { StoreId = x.StoreId, Amount = SqlFunc.AggregateSum(x.Jksyj) })
+ .ToListAsync();
+ var storeTraceabilityRefundDict = new Dictionary();
+ if (allStoreTraceabilityRefundList != null)
+ {
+ foreach (var item in allStoreTraceabilityRefundList)
+ {
+ var storeId = item.StoreId ?? "";
+ var amount = Convert.ToDecimal(item.Amount ?? 0);
+ storeTraceabilityRefundDict[storeId] = amount;
+ }
+ }
+
+ // 批量查询所有门店的退卡Cell金额
+ var allStoreCellRefundList = await _db.Queryable()
+ .Where(x => x.IsEffective == 1)
+ .Where(x => allManagedStoreIds.Contains(x.StoreId))
+ .Where(x => (x.BeautyType == "cell" || x.BeautyType == "Cell"))
+ .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
+ .GroupBy(x => x.StoreId)
+ .Select(x => new { StoreId = x.StoreId, Amount = SqlFunc.AggregateSum(x.Jksyj) })
+ .ToListAsync();
+ var storeCellRefundDict = new Dictionary();
+ if (allStoreCellRefundList != null)
+ {
+ foreach (var item in allStoreCellRefundList)
+ {
+ var storeId = item.StoreId ?? "";
+ var amount = Convert.ToDecimal(item.Amount ?? 0);
+ storeCellRefundDict[storeId] = amount;
+ }
+ }
+
+ // 遍历所有门店,构建门店明细
foreach (var storeId in allManagedStoreIds)
{
- // 该门店的开单溯源金额(从健康师业绩表统计)
- var storeTraceabilityBillingList = await _db.Queryable()
- .Where(x => x.IsEffective == 1
- && x.StoreId == storeId
- && (x.BeautyType == "溯源系统" || x.BeautyType == "溯源")
- && x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1))
- .Select(x => x.Jksyj)
- .ToListAsync();
-
- var storeTraceabilityBilling = storeTraceabilityBillingList
- .Where(x => !string.IsNullOrEmpty(x))
- .Sum(x => decimal.TryParse(x, out var val) ? val : 0m);
-
- // 该门店的退卡溯源金额(从退卡健康师业绩表统计)
- var storeTraceabilityRefund = await _db.Queryable()
- .Where(x => x.IsEffective == 1
- && x.StoreId == storeId
- && (x.BeautyType == "溯源系统" || x.BeautyType == "溯源")
- && x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1))
- .SumAsync(x => (decimal?)x.Jksyj) ?? 0m;
-
- // 该门店的开单Cell金额(从健康师业绩表统计)
- var storeCellBillingList = await _db.Queryable()
- .Where(x => x.IsEffective == 1
- && x.StoreId == storeId
- && (x.BeautyType == "cell" || x.BeautyType == "Cell")
- && x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1))
- .Select(x => x.Jksyj)
- .ToListAsync();
-
- var storeCellBilling = storeCellBillingList
- .Where(x => !string.IsNullOrEmpty(x))
- .Sum(x => decimal.TryParse(x, out var val) ? val : 0m);
-
- // 该门店的退卡Cell金额(从退卡健康师业绩表统计)
- var storeCellRefund = await _db.Queryable()
- .Where(x => x.IsEffective == 1
- && x.StoreId == storeId
- && (x.BeautyType == "cell" || x.BeautyType == "Cell")
- && x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1))
- .SumAsync(x => (decimal?)x.Jksyj) ?? 0m;
+ var storeTraceabilityBilling = storeTraceabilityBillingDict.ContainsKey(storeId) ? storeTraceabilityBillingDict[storeId] : 0m;
+ var storeTraceabilityRefund = storeTraceabilityRefundDict.ContainsKey(storeId) ? storeTraceabilityRefundDict[storeId] : 0m;
+ var storeCellBilling = storeCellBillingDict.ContainsKey(storeId) ? storeCellBillingDict[storeId] : 0m;
+ var storeCellRefund = storeCellRefundDict.ContainsKey(storeId) ? storeCellRefundDict[storeId] : 0m;
+
+ // 调试日志:记录关键门店的计算结果
+ if (storeId == "1649328471923847173" || storeId == "1649328471923847175" || storeId == "1649328471923847187")
+ {
+ _logger.LogInformation($"[科技部总经理工资计算] 门店ID: {storeId}, 开单Cell金额: {storeCellBilling}, 退卡Cell金额: {storeCellRefund}, Cell金额: {storeCellBilling - storeCellRefund}");
+ }
// 获取该门店属于哪些科技部总经理
// 通过门店的kjb字段确定:如果门店的kjb等于科技一部的组织ID,则该门店属于科技一部总经理
@@ -445,6 +523,8 @@ namespace NCC.Extend
salary.TraceabilityAmount = totalTraceabilityAmount;
salary.CellAmount = totalCellAmount;
+ _logger.LogInformation($"[科技部总经理工资计算] 员工: {salary.EmployeeName}, 溯源金额: {totalTraceabilityAmount}, Cell金额: {totalCellAmount}, 门店数: {storeDetails.Count}");
+
// 2.5 保存门店明细(JSON格式)
salary.StoreDetail = JsonConvert.SerializeObject(storeDetails);
@@ -493,6 +573,8 @@ namespace NCC.Extend
managerStats[managerId] = salary;
}
+ _logger.LogInformation($"[科技部总经理工资计算] 共计算了 {managerStats.Count} 个科技部总经理的工资数据");
+
// 3. 保存数据
if (managerStats.Any())
{
@@ -508,7 +590,12 @@ namespace NCC.Extend
if (existingDict.ContainsKey(salary.EmployeeId))
{
var existing = existingDict[salary.EmployeeId];
- if (existing.IsLocked == 1 || existing.EmployeeConfirmStatus == 1) { skippedCount++; continue; }
+ if (existing.IsLocked == 1 || existing.EmployeeConfirmStatus == 1)
+ {
+ _logger.LogWarning($"[科技部总经理工资计算] 跳过更新,员工: {salary.EmployeeName}, IsLocked: {existing.IsLocked}, EmployeeConfirmStatus: {existing.EmployeeConfirmStatus}");
+ skippedCount++;
+ continue;
+ }
salary.Id = existing.Id;
salary.EmployeeConfirmStatus = existing.EmployeeConfirmStatus;
salary.EmployeeConfirmTime = existing.EmployeeConfirmTime;
@@ -516,6 +603,8 @@ namespace NCC.Extend
salary.IsLocked = existing.IsLocked;
salary.CreateTime = existing.CreateTime;
salary.CreateUser = existing.CreateUser;
+ salary.UpdateTime = DateTime.Now; // 强制更新UpdateTime
+ _logger.LogInformation($"[科技部总经理工资计算] 准备更新,员工: {salary.EmployeeName}, 旧Cell金额: {existing.CellAmount}, 新Cell金额: {salary.CellAmount}");
recordsToUpdate.Add(salary);
}
else
@@ -529,7 +618,15 @@ namespace NCC.Extend
}
}
if (recordsToInsert.Any()) await _db.Insertable(recordsToInsert).ExecuteCommandAsync();
- if (recordsToUpdate.Any()) await _db.Updateable(recordsToUpdate).ExecuteCommandAsync();
+ if (recordsToUpdate.Any())
+ {
+ // 使用IgnoreColumns排除CreateTime和CreateUser,确保其他所有字段都被更新
+ await _db.Updateable(recordsToUpdate)
+ .IgnoreColumns(x => x.CreateTime)
+ .IgnoreColumns(x => x.CreateUser)
+ .ExecuteCommandAsync();
+ _logger.LogInformation($"已更新 {recordsToUpdate.Count} 条科技部总经理工资记录(月份:{monthStr})");
+ }
if (skippedCount > 0) _logger.LogWarning($"计算工资时跳过了 {skippedCount} 条已锁定或已确认的记录(月份:{monthStr})");
}
}
@@ -626,7 +723,7 @@ namespace NCC.Extend
public decimal TraceabilityAmount { get; set; }
public decimal CellBillingAmount { get; set; }
public decimal CellRefundAmount { get; set; }
- public decimal CellAmount { get; set; }
+ public decimal CellAmount { get; set; }
}
#region 员工工资确认
@@ -793,11 +890,11 @@ namespace NCC.Extend
if (lockedCount > 0 || unlockedCount > 0)
{
- var salariesToUpdate = salaries.Where(s =>
- (input.IsLocked && s.IsLocked == 0) ||
+ var salariesToUpdate = salaries.Where(s =>
+ (input.IsLocked && s.IsLocked == 0) ||
(!input.IsLocked && s.IsLocked == 1 && s.EmployeeConfirmStatus != 1)
).ToList();
-
+
if (salariesToUpdate.Any())
{
await _db.Updateable(salariesToUpdate)
@@ -809,10 +906,10 @@ namespace NCC.Extend
var action = input.IsLocked ? "锁定" : "解锁";
var count = input.IsLocked ? lockedCount : unlockedCount;
var message = $"{action}成功:{count}条";
-
+
if (alreadyLockedCount > 0)
message += $",跳过{alreadyLockedCount}条(已是{action}状态)";
-
+
if (skippedCount > 0)
message += $",跳过{skippedCount}条(已确认的记录不能解锁)";
@@ -851,7 +948,7 @@ namespace NCC.Extend
{
if (file == null || file.Length == 0)
throw NCCException.Oh("请选择要上传的Excel文件");
-
+
var allowedExtensions = new[] { ".xlsx", ".xls" };
var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(fileExtension))
@@ -901,7 +998,7 @@ namespace NCC.Extend
var firstColumnValue = GetColumnValue(0);
bool isOldFormat = !string.IsNullOrWhiteSpace(firstColumnValue) && (firstColumnValue == "员工姓名" || (!long.TryParse(firstColumnValue, out _) && firstColumnValue.Length > 20));
-
+
int employeeNameIndex = isOldFormat ? 0 : 1;
int offset = isOldFormat ? 0 : 1;
@@ -932,7 +1029,7 @@ namespace NCC.Extend
{
existing = await _db.Queryable()
.Where(x => x.Id == id).FirstAsync();
-
+
if (existing != null && (existing.IsLocked == 1 || existing.EmployeeConfirmStatus == 1))
{
skippedCount++;
@@ -1008,7 +1105,7 @@ namespace NCC.Extend
if (user != null) entity.EmployeeId = user.Id;
}
}
-
+
entity.UpdateTime = DateTime.Now;
if (existing != null) recordsToUpdate.Add(entity);
else recordsToInsert.Add(entity);
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs
index e8b6d95..b0273ed 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs
@@ -416,7 +416,7 @@ namespace NCC.Extend
// 总业绩 > 30,000元:只计算2%提成
salary.BaseSalary = 0;
salary.BaseSalaryLevel = 0;
- salary.PerformanceCommissionRate = 2m;
+ salary.PerformanceCommissionRate = 0.02m; // 保存为小数形式,0.02表示2%
salary.PerformanceCommissionAmount = salary.TotalPerformance * 0.02m;
salary.ConsumeCommissionRate = 0;
salary.ConsumeCommissionAmount = 0;
@@ -438,23 +438,45 @@ namespace NCC.Extend
{
// 在职员工正常计算
- // 3.1 计算底薪(根据项目数和总业绩)
- var baseSalaryResult = CalculateBaseSalary(salary.ProjectCount, salary.TotalPerformance);
- salary.BaseSalary = baseSalaryResult.BaseSalary;
- salary.BaseSalaryLevel = baseSalaryResult.Level;
+ // 判断是否为T区员工(员工姓名包含"T区")
+ bool isTZoneEmployee = !string.IsNullOrEmpty(salary.EmployeeName) && salary.EmployeeName.Contains("T区");
- // 3.2 计算业绩提成(分段累进)
- var performanceCommissionResult = CalculatePerformanceCommission(salary.TotalPerformance);
- salary.PerformanceCommissionRate = performanceCommissionResult.Rate;
- salary.PerformanceCommissionAmount = performanceCommissionResult.Amount;
+ if (isTZoneEmployee)
+ {
+ // T区员工:按照开单减去退款之后的业绩统一按照2%提成
+ // 总业绩 = 开单业绩 - 退卡业绩(已在前面计算)
+ salary.PerformanceCommissionRate = 0.02m; // 保存为小数形式,0.02表示2%
+ salary.PerformanceCommissionAmount = salary.TotalPerformance * 0.02m;
+ salary.ConsumeCommissionRate = 0;
+ salary.ConsumeCommissionAmount = 0;
+ salary.TotalCommission = salary.PerformanceCommissionAmount;
+ // T区员工也计算底薪(根据项目数和总业绩)
+ var baseSalaryResult = CalculateBaseSalary(salary.ProjectCount, salary.TotalPerformance);
+ salary.BaseSalary = baseSalaryResult.BaseSalary;
+ salary.BaseSalaryLevel = baseSalaryResult.Level;
+ }
+ else
+ {
+ // 非T区员工正常计算
+
+ // 3.1 计算底薪(根据项目数和总业绩)
+ var baseSalaryResult = CalculateBaseSalary(salary.ProjectCount, salary.TotalPerformance);
+ salary.BaseSalary = baseSalaryResult.BaseSalary;
+ salary.BaseSalaryLevel = baseSalaryResult.Level;
- // 3.3 计算消耗提成(分段累进,可能为负数)
- var consumeCommissionResult = CalculateConsumeCommission(salary.ConsumeAchievement);
- salary.ConsumeCommissionRate = consumeCommissionResult.Rate;
- salary.ConsumeCommissionAmount = consumeCommissionResult.Amount;
+ // 3.2 计算业绩提成(分段累进,门槛改为3万)
+ var performanceCommissionResult = CalculatePerformanceCommission(salary.TotalPerformance);
+ salary.PerformanceCommissionRate = performanceCommissionResult.Rate;
+ salary.PerformanceCommissionAmount = performanceCommissionResult.Amount;
- // 3.4 提成合计
- salary.TotalCommission = salary.PerformanceCommissionAmount + salary.ConsumeCommissionAmount;
+ // 3.3 计算消耗提成(新规则:10万门槛,阶梯式)
+ var consumeCommissionResult = CalculateConsumeCommission(salary.ConsumeAchievement);
+ salary.ConsumeCommissionRate = consumeCommissionResult.Rate;
+ salary.ConsumeCommissionAmount = consumeCommissionResult.Amount;
+
+ // 3.4 提成合计
+ salary.TotalCommission = salary.PerformanceCommissionAmount + salary.ConsumeCommissionAmount;
+ }
}
// 3.5 初始化其他字段(默认值为0)
@@ -603,30 +625,30 @@ namespace NCC.Extend
/// 提成比例和金额
///
/// 提成规则(分段累进式):
- /// 1. 前提条件:业绩必须大于1万才能进行提成
+ /// 1. 前提条件:整月业绩必须大于等于3万才能进行提成(门槛从1万提高到3万)
/// 2. 如果有提成资格后,分段计算:
/// - 0-7万部分:2%(整个0-7万部分都按2%计算)
/// - 7万-15万部分:2.5%
/// - 15万以上部分:3%
///
/// 计算公式(分段累进):
- /// - 如果业绩 ≤ 1万:提成 = 0(无提成资格)
- /// - 如果 1万 < 业绩 ≤ 7万:提成 = 业绩 × 2%
+ /// - 如果业绩 < 3万:提成 = 0(无提成资格)
+ /// - 如果 3万 ≤ 业绩 ≤ 7万:提成 = 业绩 × 2%
/// - 如果 7万 < 业绩 ≤ 15万:提成 = 7万 × 2% + (业绩 - 7万) × 2.5%
/// - 如果业绩 > 15万:提成 = 7万 × 2% + (15万 - 7万) × 2.5% + (业绩 - 15万) × 3%
///
/// 示例:
- /// - 总业绩 = 5,000元 → 提成 = 0(无提成资格)
+ /// - 总业绩 = 25,000元 → 提成 = 0(无提成资格,未达到3万门槛)
/// - 总业绩 = 50,000元 → 提成 = 50,000 × 2% = 1,000元
/// - 总业绩 = 100,000元 → 提成 = 70,000 × 2% + (100,000 - 70,000) × 2.5% = 1,400 + 750 = 2,150元
/// - 总业绩 = 200,000元 → 提成 = 70,000 × 2% + (150,000 - 70,000) × 2.5% + (200,000 - 150,000) × 3% = 1,400 + 2,000 + 1,500 = 4,900元
///
private (decimal Rate, decimal Amount) CalculatePerformanceCommission(decimal totalPerformance)
{
- // 提成前提:业绩必须大于1万才能进行提成
- if (totalPerformance <= 10000m)
+ // 提成前提:整月业绩必须大于等于3万才能进行提成
+ if (totalPerformance < 30000m)
{
- // ≤ 10,000元 → 0%(无提成资格)
+ // < 30,000元 → 0%(无提成资格)
return (0m, 0m);
}
@@ -655,43 +677,61 @@ namespace NCC.Extend
}
else
{
- // 业绩 > 1万 且 ≤ 7万:整个业绩按2%计算
+ // 业绩 ≥ 3万 且 ≤ 7万:整个业绩按2%计算
totalCommission = totalPerformance * 0.02m;
}
- // 计算平均提成比例(用于显示)
- decimal averageRate = totalCommission > 0 && totalPerformance > 0 ? (totalCommission / totalPerformance) * 100m : 0m;
+ // 计算平均提成比例(保存为小数形式,如0.02表示2%,前端会乘以100显示)
+ decimal averageRate = totalCommission > 0 && totalPerformance > 0 ? (totalCommission / totalPerformance) : 0m;
return (averageRate, totalCommission);
}
///
- /// 计算消耗提成(分段累进,可能为负数)
+ /// 计算消耗提成(阶梯式,可能为负数)
///
/// 消耗业绩
/// 提成比例和金额(金额可能为负数,比例用于显示)
+ ///
+ /// 消耗提成规则:
+ /// 1. 未完成10万底标:负激励300元(扣除300元)
+ /// 2. 达到10万条件后,按照阶梯式提成:
+ /// - 1-20万部分:0.5%
+ /// - 超过20万部分:1%
+ ///
+ /// 计算公式(阶梯式):
+ /// - 如果消耗业绩 < 10万:提成 = -300元(扣除300元)
+ /// - 如果消耗业绩 ≥ 10万 且 ≤ 20万:提成 = 消耗业绩 × 0.5%
+ /// - 如果消耗业绩 > 20万:提成 = 20万 × 0.5% + (消耗业绩 - 20万) × 1%
+ ///
+ /// 示例:
+ /// - 消耗业绩 = 50,000元 → 提成 = -300元(未完成10万底标)
+ /// - 消耗业绩 = 100,000元 → 提成 = 100,000 × 0.5% = 500元
+ /// - 消耗业绩 = 150,000元 → 提成 = 150,000 × 0.5% = 750元
+ /// - 消耗业绩 = 250,000元 → 提成 = 200,000 × 0.5% + (250,000 - 200,000) × 1% = 1,000 + 500 = 1,500元
+ ///
private (decimal Rate, decimal Amount) CalculateConsumeCommission(decimal consumeAchievement)
{
- if (consumeAchievement < 80000m)
+ if (consumeAchievement < 100000m)
{
- // < 80,000元 → 扣除300元(负数)
+ // < 100,000元(未完成10万底标)→ 扣除300元(负数)
// 比例显示为0,金额为-300
return (0m, -300m);
}
- else if (consumeAchievement < 100000m)
- {
- // 80,000-100,000元 → 0.5%
- return (0.5m, consumeAchievement * 0.005m);
- }
- else if (consumeAchievement < 200000m)
+ else if (consumeAchievement <= 200000m)
{
- // 100,000-200,000元 → 0.5%
- return (0.5m, consumeAchievement * 0.005m);
+ // ≥ 100,000元 且 ≤ 200,000元 → 1-20万部分按0.5%
+ return (0.005m, consumeAchievement * 0.005m); // 保存为小数形式,0.005表示0.5%
}
else
{
- // > 200,000元 → 1%
- return (1m, consumeAchievement * 0.01m);
+ // > 200,000元 → 阶梯式:1-20万部分0.5%,超过20万部分1%
+ decimal part1 = 200000m * 0.005m; // 20万 × 0.5% = 1,000元
+ decimal part2 = (consumeAchievement - 200000m) * 0.01m; // 超过20万部分 × 1%
+ decimal totalCommission = part1 + part2;
+ // 计算平均比例(保存为小数形式,前端会乘以100显示)
+ decimal averageRate = totalCommission > 0 && consumeAchievement > 0 ? (totalCommission / consumeAchievement) : 0m;
+ return (averageRate, totalCommission);
}
}
diff --git a/netcore/src/Modularity/System/NCC.System.Entitys/Mapper/SystemMapper.cs b/netcore/src/Modularity/System/NCC.System.Entitys/Mapper/SystemMapper.cs
index 0dbcc45..27eb11c 100644
--- a/netcore/src/Modularity/System/NCC.System.Entitys/Mapper/SystemMapper.cs
+++ b/netcore/src/Modularity/System/NCC.System.Entitys/Mapper/SystemMapper.cs
@@ -2,6 +2,7 @@
using NCC.System.Entitys.Dto.System.DbBackup;
using NCC.System.Entitys.Dto.System.Province;
using NCC.System.Entitys.Model.System.DataBase;
+using NCC.System.Entitys.Model.Permission.UsersCurrent;
using NCC.System.Entitys.System;
using Mapster;
using SqlSugar;
@@ -51,6 +52,8 @@ namespace NCC.System.Entitys.Mapper
.Map(dest => dest.dataLength, src => src.Length.ToString())
.Map(dest => dest.primaryKey, src => src.IsPrimarykey ? 1 : 0)
.Map(dest => dest.allowNull, src => src.IsNullable ? 1 : 0);
+ config.ForType()
+ .Map(dest => dest.description, src => src.Description);
}
}
}
diff --git a/netcore/src/Modularity/System/NCC.System.Entitys/Model/Permission/UsersCurrent/UsersCurrentAuthorizeMoldel.cs b/netcore/src/Modularity/System/NCC.System.Entitys/Model/Permission/UsersCurrent/UsersCurrentAuthorizeMoldel.cs
index 3fff1cc..c12396e 100644
--- a/netcore/src/Modularity/System/NCC.System.Entitys/Model/Permission/UsersCurrent/UsersCurrentAuthorizeMoldel.cs
+++ b/netcore/src/Modularity/System/NCC.System.Entitys/Model/Permission/UsersCurrent/UsersCurrentAuthorizeMoldel.cs
@@ -30,5 +30,10 @@ namespace NCC.System.Entitys.Model.Permission.UsersCurrent
///
[JsonIgnore]
public string moduleId { get; set; }
+
+ ///
+ /// 备注
+ ///
+ public string description { get; set; }
}
}