From 8b44d4bd9ad36ec5c24615ba9a16d10c1cbc4f1c Mon Sep 17 00:00:00 2001
From: “wangming” <“wangming@antissoft.com”>
Date: Fri, 19 Dec 2025 14:08:51 +0800
Subject: [PATCH] feat: 添加健康师额外工资和合作成本导入的清理功能
---
antis-ncc-admin/.env.development | 4 ++--
antis-ncc-admin/src/api/extend/healthCoachSalary.js | 3 ++-
antis-ncc-admin/src/views/lqCooperationCost/import-dialog.vue | 37 ++++++++++++++++++++++++++++++++++++-
antis-ncc-admin/src/views/wageManagement/extra-data-dialog.vue | 9 ++++++++-
excel/健康师额外数据模板_2025年11月_20251219120434.xlsx | Bin 0 -> 26051 bytes
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMajorProjectTeacherSalary/MajorProjectTeacherSalaryOutput.cs | 4 ++--
netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs | 77 +++++++++++++++++++++--------------------------------------------------------
netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------
netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectDirectorSalaryService.cs | 43 ++++++++++++++++++++++++++++++-------------
netcore/src/Modularity/Extend/NCC.Extend/LqMajorProjectTeacherSalaryService.cs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------
netcore/src/Modularity/Extend/NCC.Extend/LqSalaryExtraCalculationService.cs | 20 +++++++++++++++++++-
netcore/src/Modularity/Extend/NCC.Extend/LqShareStatisticsHqService.cs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++--------
netcore/src/Modularity/Extend/NCC.Extend/Utils/WeChatBotService.cs | 2 +-
sql/排查生美业绩统计差异-简化版.sql | 2 ++
sql/排查生美业绩统计差异详细.sql | 2 ++
sql/检查生美业绩统计差异.sql | 2 ++
test_tianwang_api.py | 2 ++
17 files changed, 303 insertions(+), 159 deletions(-)
create mode 100644 excel/健康师额外数据模板_2025年11月_20251219120434.xlsx
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
Binary files /dev/null and b/excel/健康师额外数据模板_2025年11月_20251219120434.xlsx differ
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/LqAssistantSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs
index c46ff4e..a4d1f61 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqAssistantSalaryService.cs
@@ -334,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;
}
}
@@ -467,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;
@@ -650,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/LqCooperationCostService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs
index f59cf88..d4c88ab 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs
@@ -409,11 +409,12 @@ namespace NCC.Extend.LqCooperationCost
/// 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
{
@@ -433,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));
@@ -517,44 +519,14 @@ namespace NCC.Extend.LqCooperationCost
continue;
}
- // 检查是否已存在相同门店、年份、月份的记录
- 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,
- CostType = costType,
- 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)
{
@@ -572,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)
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/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/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/排查生美业绩统计差异-简化版.sql b/sql/排查生美业绩统计差异-简化版.sql
index ab20c6b..80fc2a2 100644
--- a/sql/排查生美业绩统计差异-简化版.sql
+++ b/sql/排查生美业绩统计差异-简化版.sql
@@ -124,3 +124,5 @@ ORDER BY 生美业绩 DESC;
+
+
diff --git a/sql/排查生美业绩统计差异详细.sql b/sql/排查生美业绩统计差异详细.sql
index 2342e33..92502d8 100644
--- a/sql/排查生美业绩统计差异详细.sql
+++ b/sql/排查生美业绩统计差异详细.sql
@@ -176,3 +176,5 @@ HAVING COUNT(*) > 1;
+
+
diff --git a/sql/检查生美业绩统计差异.sql b/sql/检查生美业绩统计差异.sql
index b6b0e02..4e362bb 100644
--- a/sql/检查生美业绩统计差异.sql
+++ b/sql/检查生美业绩统计差异.sql
@@ -146,3 +146,5 @@ ORDER BY 生美业绩 DESC;
+
+
diff --git a/test_tianwang_api.py b/test_tianwang_api.py
index a6c559b..e631863 100644
--- a/test_tianwang_api.py
+++ b/test_tianwang_api.py
@@ -77,3 +77,5 @@ print(f"\n教育一部+教育二部合计 BillingPerformance: {total2}")
+
+
--
libgit2 0.21.4