diff --git a/netcore/src/Infrastructure/NCC/SpecificationDocument/Builders/SpecificationDocumentBuilder.cs b/netcore/src/Infrastructure/NCC/SpecificationDocument/Builders/SpecificationDocumentBuilder.cs index 3bee836..bca08c4 100644 --- a/netcore/src/Infrastructure/NCC/SpecificationDocument/Builders/SpecificationDocumentBuilder.cs +++ b/netcore/src/Infrastructure/NCC/SpecificationDocument/Builders/SpecificationDocumentBuilder.cs @@ -152,6 +152,9 @@ namespace NCC.SpecificationDocument //使得 Swagger 能够正确地显示 Enum 的对应关系 if (_specificationDocumentSettings.EnableEnumSchemaFilter == true) swaggerGenOptions.SchemaFilter(); + // 使得 Swagger 能够显示实体类的描述(从 XML 注释中读取) + swaggerGenOptions.SchemaFilter(); + // 支持控制器排序操作 if (_specificationDocumentSettings.EnableTagsOrderDocumentFilter == true) swaggerGenOptions.DocumentFilter(); diff --git a/netcore/src/Infrastructure/NCC/SpecificationDocument/Filters/EntityDescriptionSchemaFilter.cs b/netcore/src/Infrastructure/NCC/SpecificationDocument/Filters/EntityDescriptionSchemaFilter.cs new file mode 100644 index 0000000..ea5902c --- /dev/null +++ b/netcore/src/Infrastructure/NCC/SpecificationDocument/Filters/EntityDescriptionSchemaFilter.cs @@ -0,0 +1,117 @@ +using NCC.Dependency; +using NCC.ConfigurableOptions; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace NCC.SpecificationDocument +{ + /// + /// 修正 规范化文档实体类描述提示 + /// + [SuppressSniffer] + public class EntityDescriptionSchemaFilter : ISchemaFilter + { + /// + /// 实现过滤器方法 + /// + /// OpenAPI Schema + /// Schema 过滤器上下文 + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + var type = context.Type; + + // 只处理项目程序集中的类型 + if (!App.Assemblies.Contains(type.Assembly)) + return; + + // 尝试从 XML 注释中获取类型描述 + try + { + // 直接使用程序集名称查找 XML 文件 + var assemblyName = type.Assembly.GetName().Name; + var xmlFileName = $"{assemblyName}.xml"; + var xmlFilePath = Path.Combine(AppContext.BaseDirectory, xmlFileName); + + if (File.Exists(xmlFilePath)) + { + try + { + var xmlDoc = XDocument.Load(xmlFilePath); + var memberName = $"T:{type.FullName}"; + var memberElement = xmlDoc.Descendants("member") + .FirstOrDefault(m => m.Attribute("name")?.Value == memberName); + + if (memberElement != null) + { + var summaryElement = memberElement.Element("summary"); + if (summaryElement != null) + { + var description = summaryElement.Value.Trim(); + if (!string.IsNullOrEmpty(description)) + { + schema.Description = description; + return; + } + } + } + } + catch + { + // 忽略 XML 解析错误 + } + } + + // 如果直接查找失败,尝试从配置的 XML 注释列表中查找 + var specificationDocumentSettings = App.GetOptions(); + var xmlComments = specificationDocumentSettings?.XmlComments; + if (xmlComments != null && xmlComments.Any()) + { + foreach (var xmlComment in xmlComments) + { + var assemblyXmlName = xmlComment.EndsWith(".xml") ? xmlComment : $"{xmlComment}.xml"; + var assemblyXmlPath = Path.Combine(AppContext.BaseDirectory, assemblyXmlName); + + if (File.Exists(assemblyXmlPath) && assemblyXmlPath != xmlFilePath) + { + try + { + var xmlDoc = XDocument.Load(assemblyXmlPath); + var memberName = $"T:{type.FullName}"; + var memberElement = xmlDoc.Descendants("member") + .FirstOrDefault(m => m.Attribute("name")?.Value == memberName); + + if (memberElement != null) + { + var summaryElement = memberElement.Element("summary"); + if (summaryElement != null) + { + var description = summaryElement.Value.Trim(); + if (!string.IsNullOrEmpty(description)) + { + schema.Description = description; + break; + } + } + } + } + catch + { + // 忽略 XML 解析错误 + } + } + } + } + } + catch + { + // 忽略配置获取错误 + } + } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs index 76e94d3..235f3c5 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs @@ -66,14 +66,24 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb public decimal consumeAmount { get; set; } /// - /// 人头 - 统计该健康师在指定时间周期内服务的唯一客户数量(按客户去重) + /// 有效人头 - 统计该健康师在指定时间周期内服务的唯一客户数量(按客户去重,F_HasBilling=1,支持小数) /// - public int headCount { get; set; } + public decimal headCount { get; set; } /// - /// 人次 - 统计该健康师在指定时间周期内服务的客户人次(按客户+日期去重,同一客户不同天算多次) + /// 有效人次 - 统计该健康师在指定时间周期内服务的客户人次(按客户+日期去重,F_HasBilling=1,同一客户不同天算多次,支持小数) /// - public int personCount { get; set; } + public decimal personCount { get; set; } + + /// + /// 无效人头 - 统计该健康师在指定时间周期内服务的唯一客户数量(按客户去重,F_HasBilling=0,支持小数) + /// + public decimal invalidHeadCount { get; set; } + + /// + /// 无效人次 - 统计该健康师在指定时间周期内服务的客户人次(按客户+日期去重,F_HasBilling=0,同一客户不同天算多次,支持小数) + /// + public decimal invalidPersonCount { get; set; } /// /// 消耗项目数 - 统计该健康师在指定时间周期内消耗的项目总次数 diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductCrInput.cs index 242d116..d41cf7d 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductCrInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductCrInput.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using NCC.Dependency; namespace NCC.Extend.Entitys.Dto.LqProduct { diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/NCC.Extend.Entitys.csproj b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/NCC.Extend.Entitys.csproj index ca2ef87..ba66805 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/NCC.Extend.Entitys.csproj +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/NCC.Extend.Entitys.csproj @@ -2,9 +2,12 @@ net6.0 - + + bin\Debug\$(AssemblyName).xml + + - bin\Release\NCC.Extend.Entitys.xml + bin\Release\$(AssemblyName).xml diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/NCC.Extend.Interfaces.csproj b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/NCC.Extend.Interfaces.csproj index dd7192e..68cbb7d 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/NCC.Extend.Interfaces.csproj +++ b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/NCC.Extend.Interfaces.csproj @@ -3,6 +3,12 @@ net6.0 + + bin\Debug\$(AssemblyName).xml + + + bin\Release\$(AssemblyName).xml + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs index 1079d21..72eb8b1 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs @@ -399,9 +399,7 @@ namespace NCC.Extend throw NCCException.Oh("库存ID不能为空"); } - var inventory = await _db.Queryable() - .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) - .FirstAsync(); + var inventory = await _db.Queryable().Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync(); if (inventory == null) { diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs index fdcdfd3..17b220f 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs @@ -166,71 +166,59 @@ namespace NCC.Extend } // 生成批次ID(如果未提供) - var batchId = string.IsNullOrWhiteSpace(input.BatchId) - ? YitIdHelper.NextId().ToString() - : input.BatchId; - + var batchId = string.IsNullOrWhiteSpace(input.BatchId) ? YitIdHelper.NextId().ToString() : input.BatchId; var successIds = new List(); - var failItems = new List(); - _db.Ado.BeginTran(); + // 按产品ID分组,批量验证库存(在事务外先检查,避免不必要的回滚) + var productGroups = input.UsageItems.Select((item, index) => new { Item = item, Index = index }).GroupBy(x => x.Item.ProductId).ToList(); - try - { - // 按产品ID分组,批量验证库存 - var productGroups = input.UsageItems - .Select((item, index) => new { Item = item, Index = index }) - .GroupBy(x => x.Item.ProductId) - .ToList(); - - // 计算每个产品的总需求并检查库存 - foreach (var productGroup in productGroups) - { - var productId = productGroup.Key; - var totalRequired = productGroup.Sum(x => x.Item.UsageQuantity); + // 获取所有需要检查的产品ID + var productIds = productGroups.Select(x => x.Key).Distinct().ToList(); - // 计算该产品的总库存数量 - var totalInventory = await _db.Queryable() - .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) - .SumAsync(x => (int?)x.Quantity) ?? 0; + // 批量查询所有产品的库存信息(总库存) + var inventoryList = await _db.Queryable().Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()).GroupBy(x => x.ProductId).Select(x => new { ProductId = x.ProductId, TotalInventory = SqlFunc.AggregateSum(x.Quantity) }).ToListAsync(); + var inventoryMap = inventoryList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalInventory)); - // 计算该产品的已使用数量 - var totalUsage = await _db.Queryable() - .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) - .SumAsync(x => (int?)x.UsageQuantity) ?? 0; + // 批量查询所有产品的已使用数量 + var usageList = await _db.Queryable().Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()).GroupBy(x => x.ProductId).Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum(x.UsageQuantity) }).ToListAsync(); + var usageMap = usageList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalUsage)); - // 计算可用库存 - var availableInventory = totalInventory - totalUsage; + // 批量查询所有产品的名称 + var productDict = await _db.Queryable() + .Where(x => productIds.Contains(x.Id)) + .Select(x => new { x.Id, x.ProductName }) + .ToListAsync(); + var productNameMap = productDict.ToDictionary(x => x.Id, x => x.ProductName ?? "未知产品"); - // 检查库存是否足够 - if (availableInventory < totalRequired) - { - var failIndices = productGroup.Select(x => x.Index).ToList(); - - foreach (var index in failIndices) - { - failItems.Add(new BatchCreateFailItem - { - Index = index, - ProductId = productId, - Reason = $"库存不足,当前可用库存:{availableInventory},需要数量:{totalRequired}" - }); - } - } + // 检查每个产品的库存 + foreach (var productGroup in productGroups) + { + var productId = productGroup.Key; + var totalRequired = productGroup.Sum(x => x.Item.UsageQuantity); + + // 从字典中获取库存信息 + var totalInventory = inventoryMap.GetValueOrDefault(productId, 0); + var totalUsage = usageMap.GetValueOrDefault(productId, 0); + var availableInventory = totalInventory - totalUsage; + + // 检查库存是否足够,如果不足则直接抛出异常 + if (availableInventory < totalRequired) + { + var productName = productNameMap.GetValueOrDefault(productId, "未知产品"); + throw NCCException.Oh($"产品【{productName}】(ID: {productId}) 库存不足,当前可用库存:{availableInventory},需要数量:{totalRequired}"); } + } + + _db.Ado.BeginTran(); - // 创建成功的使用记录 + try + { + // 创建使用记录 var entitiesToInsert = new List(); for (int i = 0; i < input.UsageItems.Count; i++) { var item = input.UsageItems[i]; - // 跳过失败项 - if (failItems.Any(x => x.Index == i)) - { - continue; - } - var usageEntity = new LqInventoryUsageEntity { Id = YitIdHelper.NextId().ToString(), @@ -265,9 +253,9 @@ namespace NCC.Extend { BatchId = batchId, SuccessCount = successIds.Count, - FailCount = failItems.Count, + FailCount = 0, SuccessIds = successIds, - FailItems = failItems + FailItems = new List() }; } catch @@ -346,35 +334,34 @@ namespace NCC.Extend var sidx = input.sidx == null ? "id" : input.sidx; // 查询使用记录信息,关联产品表 - var data = await _db.Queryable( - (usage, product) => usage.ProductId == product.Id) - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId) - .WhereIF(input.UsageStartTime.HasValue, (usage, product) => usage.UsageTime >= input.UsageStartTime.Value) - .WhereIF(input.UsageEndTime.HasValue, (usage, product) => usage.UsageTime <= input.UsageEndTime.Value) - .WhereIF(!string.IsNullOrWhiteSpace(input.RelatedConsumeId), (usage, product) => usage.RelatedConsumeId == input.RelatedConsumeId) - .WhereIF(!string.IsNullOrWhiteSpace(input.UsageBatchId), (usage, product) => usage.UsageBatchId == input.UsageBatchId) - .WhereIF(input.IsEffective.HasValue, (usage, product) => usage.IsEffective == input.IsEffective.Value) - .Select((usage, product) => new LqInventoryUsageListOutput + var data = await _db.Queryable((u, product) => u.ProductId == product.Id) + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product) => u.ProductId == input.ProductId) + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product) => u.StoreId == input.StoreId) + .WhereIF(input.UsageStartTime.HasValue, (u, product) => u.UsageTime >= input.UsageStartTime.Value) + .WhereIF(input.UsageEndTime.HasValue, (u, product) => u.UsageTime <= input.UsageEndTime.Value) + .WhereIF(!string.IsNullOrWhiteSpace(input.RelatedConsumeId), (u, product) => u.RelatedConsumeId == input.RelatedConsumeId) + .WhereIF(!string.IsNullOrWhiteSpace(input.UsageBatchId), (u, product) => u.UsageBatchId == input.UsageBatchId) + .WhereIF(input.IsEffective.HasValue, (u, product) => u.IsEffective == input.IsEffective.Value) + .Select((u, product) => new LqInventoryUsageListOutput { - id = usage.Id, - productId = usage.ProductId, + id = u.Id, + productId = u.ProductId, productName = product.ProductName, productCategory = product.ProductCategory, productPrice = product.Price, - storeId = usage.StoreId, - storeName = SqlFunc.Subqueryable().Where(u => u.Id == usage.StoreId).Select(u => u.Dm), - usageTime = usage.UsageTime, - usageQuantity = usage.UsageQuantity, - relatedConsumeId = usage.RelatedConsumeId, - usageBatchId = usage.UsageBatchId, - createUser = usage.CreateUser, + storeId = u.StoreId, + storeName = SqlFunc.Subqueryable().Where(store => store.Id == u.StoreId).Select(store => store.Dm), + usageTime = u.UsageTime, + usageQuantity = u.UsageQuantity, + relatedConsumeId = u.RelatedConsumeId, + usageBatchId = u.UsageBatchId, + createUser = u.CreateUser, createUserName = "", - createTime = usage.CreateTime, - updateUser = usage.UpdateUser, + createTime = u.CreateTime, + updateUser = u.UpdateUser, updateUserName = "", - updateTime = usage.UpdateTime, - isEffective = usage.IsEffective + updateTime = u.UpdateTime, + isEffective = u.IsEffective }) .MergeTable() .OrderBy(sidx + " " + input.sort) @@ -460,29 +447,29 @@ namespace NCC.Extend // 查询该批次的所有使用记录 var usageRecords = await _db.Queryable( - (usage, product) => usage.ProductId == product.Id) - .LeftJoin((usage, product, store) => usage.StoreId == store.Id) - .Where((usage, product, store) => usage.UsageBatchId == batchId) - .Select((usage, product, store) => new LqInventoryUsageListOutput + (u, product) => u.ProductId == product.Id) + .LeftJoin((u, product, store) => u.StoreId == store.Id) + .Where((u, product, store) => u.UsageBatchId == batchId) + .Select((u, product, store) => new LqInventoryUsageListOutput { - id = usage.Id, - productId = usage.ProductId, + id = u.Id, + productId = u.ProductId, productName = product.ProductName, productCategory = product.ProductCategory, productPrice = product.Price, - storeId = usage.StoreId, + storeId = u.StoreId, storeName = store.Dm, - usageTime = usage.UsageTime, - usageQuantity = usage.UsageQuantity, - relatedConsumeId = usage.RelatedConsumeId, - usageBatchId = usage.UsageBatchId, - createUser = usage.CreateUser, + usageTime = u.UsageTime, + usageQuantity = u.UsageQuantity, + relatedConsumeId = u.RelatedConsumeId, + usageBatchId = u.UsageBatchId, + createUser = u.CreateUser, createUserName = "", - createTime = usage.CreateTime, - updateUser = usage.UpdateUser, + createTime = u.CreateTime, + updateUser = u.UpdateUser, updateUserName = "", - updateTime = usage.UpdateTime, - isEffective = usage.IsEffective + updateTime = u.UpdateTime, + isEffective = u.IsEffective }) .MergeTable() .OrderBy("createTime") @@ -563,23 +550,23 @@ namespace NCC.Extend try { var data = await _db.Queryable() - .LeftJoin((usage, product) => usage.ProductId == product.Id) - .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) - .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId) - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product) => product.ProductCategory == input.ProductCategory) - .GroupBy((usage, product) => new { usage.ProductId, product.ProductName, product.ProductCategory, product.Price }) - .Select((usage, product) => new ProductUsageStatisticsOutput + .LeftJoin((u, product) => u.ProductId == product.Id) + .Where((u, product) => u.UsageTime >= input.StartTime && u.UsageTime <= input.EndTime) + .Where((u, product) => u.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product) => u.ProductId == input.ProductId) + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product) => u.StoreId == input.StoreId) + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (u, product) => product.ProductCategory == input.ProductCategory) + .GroupBy((u, product) => new { u.ProductId, product.ProductName, product.ProductCategory, product.Price }) + .Select((u, product) => new ProductUsageStatisticsOutput { - ProductId = usage.ProductId, + ProductId = u.ProductId, ProductName = product.ProductName, ProductCategory = product.ProductCategory, ProductPrice = product.Price, - TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity), - TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price), - UsageCount = SqlFunc.AggregateCount(usage.Id), - AverageUsageQuantity = SqlFunc.AggregateAvg(usage.UsageQuantity) + TotalUsageQuantity = SqlFunc.AggregateSum(u.UsageQuantity), + TotalUsageAmount = SqlFunc.AggregateSum(u.UsageQuantity * product.Price), + UsageCount = SqlFunc.AggregateCount(u.Id), + AverageUsageQuantity = SqlFunc.AggregateAvg(u.UsageQuantity) }) .ToListAsync(); @@ -611,22 +598,22 @@ namespace NCC.Extend try { var data = await _db.Queryable() - .LeftJoin((usage, product) => usage.ProductId == product.Id) - .LeftJoin((usage, product, store) => usage.StoreId == store.Id) - .Where((usage, product, store) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) - .Where((usage, product, store) => usage.IsEffective == StatusEnum.有效.GetHashCode()) - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product, store) => usage.ProductId == input.ProductId) - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product, store) => usage.StoreId == input.StoreId) - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product, store) => product.ProductCategory == input.ProductCategory) - .GroupBy((usage, product, store) => new { usage.StoreId, store.Dm }) - .Select((usage, product, store) => new StoreUsageStatisticsOutput + .LeftJoin((u, product) => u.ProductId == product.Id) + .LeftJoin((u, product, store) => u.StoreId == store.Id) + .Where((u, product, store) => u.UsageTime >= input.StartTime && u.UsageTime <= input.EndTime) + .Where((u, product, store) => u.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product, store) => u.ProductId == input.ProductId) + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product, store) => u.StoreId == input.StoreId) + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (u, product, store) => product.ProductCategory == input.ProductCategory) + .GroupBy((u, product, store) => new { u.StoreId, store.Dm }) + .Select((u, product, store) => new StoreUsageStatisticsOutput { - StoreId = usage.StoreId, + StoreId = u.StoreId, StoreName = store.Dm, - TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity), - TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price), - UsageCount = SqlFunc.AggregateCount(usage.Id), - ProductVarietyCount = SqlFunc.AggregateCount(usage.ProductId) + TotalUsageQuantity = SqlFunc.AggregateSum(u.UsageQuantity), + TotalUsageAmount = SqlFunc.AggregateSum(u.UsageQuantity * product.Price), + UsageCount = SqlFunc.AggregateCount(u.Id), + ProductVarietyCount = SqlFunc.AggregateCount(u.ProductId) }) .ToListAsync(); @@ -659,18 +646,18 @@ namespace NCC.Extend { // 先获取基础数据 var baseData = await _db.Queryable() - .LeftJoin((usage, product) => usage.ProductId == product.Id) - .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) - .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId) - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product) => product.ProductCategory == input.ProductCategory) - .Select((usage, product) => new + .LeftJoin((u, product) => u.ProductId == product.Id) + .Where((u, product) => u.UsageTime >= input.StartTime && u.UsageTime <= input.EndTime) + .Where((u, product) => u.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product) => u.ProductId == input.ProductId) + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product) => u.StoreId == input.StoreId) + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (u, product) => product.ProductCategory == input.ProductCategory) + .Select((u, product) => new { - UsageTime = usage.UsageTime, - UsageQuantity = usage.UsageQuantity, - UsageAmount = usage.UsageQuantity * product.Price, - ProductId = usage.ProductId + UsageTime = u.UsageTime, + UsageQuantity = u.UsageQuantity, + UsageAmount = u.UsageQuantity * product.Price, + ProductId = u.ProductId }) .ToListAsync(); @@ -740,22 +727,22 @@ namespace NCC.Extend try { var data = await _db.Queryable() - .LeftJoin((usage, product) => usage.ProductId == product.Id) - .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) - .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId) - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product) => product.ProductCategory == input.ProductCategory) - .GroupBy((usage, product) => new { usage.ProductId, product.ProductName, product.ProductCategory }) - .Select((usage, product) => new ProductUsageRankingOutput + .LeftJoin((u, product) => u.ProductId == product.Id) + .Where((u, product) => u.UsageTime >= input.StartTime && u.UsageTime <= input.EndTime) + .Where((u, product) => u.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product) => u.ProductId == input.ProductId) + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product) => u.StoreId == input.StoreId) + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (u, product) => product.ProductCategory == input.ProductCategory) + .GroupBy((u, product) => new { u.ProductId, product.ProductName, product.ProductCategory }) + .Select((u, product) => new ProductUsageRankingOutput { - ProductId = usage.ProductId, + ProductId = u.ProductId, ProductName = product.ProductName, ProductCategory = product.ProductCategory, - TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity), - TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price), - UsageCount = SqlFunc.AggregateCount(usage.Id), - StoreCount = SqlFunc.AggregateCount(usage.StoreId) + TotalUsageQuantity = SqlFunc.AggregateSum(u.UsageQuantity), + TotalUsageAmount = SqlFunc.AggregateSum(u.UsageQuantity * product.Price), + UsageCount = SqlFunc.AggregateCount(u.Id), + StoreCount = SqlFunc.AggregateCount(u.StoreId) }) .OrderBy("TotalUsageQuantity desc") .Take(input.RankingCount) diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs index cc72fb8..53b99e0 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs @@ -2549,7 +2549,8 @@ namespace NCC.Extend.LqKdKdjlb totalPrice = it.TotalPrice, actualPrice = it.ActualPrice, projectNumber = it.ProjectNumber, - remark = it.Remark + remark = it.Remark, + itemCategory = it.ItemCategory, }) .ToListAsync(); @@ -2596,7 +2597,8 @@ namespace NCC.Extend.LqKdKdjlb totalPrice = x.totalPrice, actualPrice = x.actualPrice, projectNumber = x.projectNumber, - remark = x.remark + remark = x.remark, + itemCategory = x.itemCategory, }).ToList(), giftedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "赠送").Select(x => new @@ -2608,7 +2610,8 @@ namespace NCC.Extend.LqKdKdjlb totalPrice = x.totalPrice, actualPrice = x.actualPrice, projectNumber = x.projectNumber, - remark = x.remark + remark = x.remark, + itemCategory = x.itemCategory, }).ToList(), experienceItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "体验").Select(x => new @@ -2620,7 +2623,8 @@ namespace NCC.Extend.LqKdKdjlb totalPrice = x.totalPrice, actualPrice = x.actualPrice, projectNumber = x.projectNumber, - remark = x.remark + remark = x.remark, + itemCategory = x.itemCategory, }).ToList(), // 金额信息 @@ -2670,7 +2674,6 @@ namespace NCC.Extend.LqKdKdjlb }, message = "获取开单记录汇总信息成功" }; - return result; } catch (Exception ex) @@ -3210,8 +3213,10 @@ namespace NCC.Extend.LqKdKdjlb -- 消耗相关统计 COALESCE(consume_stats.ConsumeAmount, 0) as ConsumeAmount, - COALESCE(consume_stats.HeadCount, 0) as HeadCount, - COALESCE(consume_stats.PersonCount, 0) as PersonCount, + CAST(COALESCE(headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as HeadCount, + CAST(COALESCE(personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as PersonCount, + CAST(COALESCE(invalid_headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as InvalidHeadCount, + CAST(COALESCE(invalid_personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as InvalidPersonCount, CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount FROM BASE_USER u @@ -3274,8 +3279,6 @@ namespace NCC.Extend.LqKdKdjlb SELECT jksyj.jkszh as EmployeeId, SUM(jksyj.jksyj) as ConsumeAmount, - COUNT(DISTINCT hyhk.hy) as HeadCount, - COUNT(DISTINCT CONCAT(jksyj.jkszh, '_', hyhk.hy, '_', DATE(hyhk.hksj))) as PersonCount, CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount FROM lq_xh_jksyj jksyj INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id @@ -3287,6 +3290,98 @@ namespace NCC.Extend.LqKdKdjlb GROUP BY jksyj.jkszh ) consume_stats ON u.F_Id = consume_stats.EmployeeId + -- 有效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=1) + LEFT JOIN ( + SELECT + F_PersonId as EmployeeId, + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount + FROM ( + SELECT + F_PersonId, + F_WorkMonth, + F_MemberId, + F_Quantity + FROM lq_person_times_record + WHERE F_PersonId IS NOT NULL + AND F_IsEffective = 1 + AND F_PersonType = '健康师' + AND F_HasBilling = 1 + AND F_WorkDate >= DATE(@startTime) + AND F_WorkDate <= DATE(@endTime) + GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity + ) as distinct_headcount + GROUP BY F_PersonId + ) headcount_stats ON u.F_Id = headcount_stats.EmployeeId + + -- 有效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=1) + LEFT JOIN ( + SELECT + F_PersonId as EmployeeId, + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount + FROM ( + SELECT + F_PersonId, + F_WorkDate, + F_MemberId, + F_Quantity + FROM lq_person_times_record + WHERE F_PersonId IS NOT NULL + AND F_IsEffective = 1 + AND F_PersonType = '健康师' + AND F_HasBilling = 1 + AND F_WorkDate >= DATE(@startTime) + AND F_WorkDate <= DATE(@endTime) + GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity + ) as distinct_personcount + GROUP BY F_PersonId + ) personcount_stats ON u.F_Id = personcount_stats.EmployeeId + + -- 无效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=0) + LEFT JOIN ( + SELECT + F_PersonId as EmployeeId, + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount + FROM ( + SELECT + F_PersonId, + F_WorkMonth, + F_MemberId, + F_Quantity + FROM lq_person_times_record + WHERE F_PersonId IS NOT NULL + AND F_IsEffective = 1 + AND F_PersonType = '健康师' + AND F_HasBilling = 0 + AND F_WorkDate >= DATE(@startTime) + AND F_WorkDate <= DATE(@endTime) + GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity + ) as distinct_headcount + GROUP BY F_PersonId + ) invalid_headcount_stats ON u.F_Id = invalid_headcount_stats.EmployeeId + + -- 无效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=0) + LEFT JOIN ( + SELECT + F_PersonId as EmployeeId, + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount + FROM ( + SELECT + F_PersonId, + F_WorkDate, + F_MemberId, + F_Quantity + FROM lq_person_times_record + WHERE F_PersonId IS NOT NULL + AND F_IsEffective = 1 + AND F_PersonType = '健康师' + AND F_HasBilling = 0 + AND F_WorkDate >= DATE(@startTime) + AND F_WorkDate <= DATE(@endTime) + GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity + ) as distinct_personcount + GROUP BY F_PersonId + ) invalid_personcount_stats ON u.F_Id = invalid_personcount_stats.EmployeeId + WHERE u.F_GW = '健康师' "; diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs index 0f188de..fad69fd 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs @@ -228,7 +228,7 @@ namespace NCC.Extend price = x.Price, productCategory = x.ProductCategory, departmentId = x.DepartmentId, - departmentName = "", + departmentName = SqlFunc.Subqueryable().Where(y => y.Id == x.DepartmentId).Select(y => y.FullName), standardUnit = x.StandardUnit, onShelfStatus = x.OnShelfStatus, statisticsCategory = x.StatisticsCategory, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs index 2734be1..9683234 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs @@ -28,7 +28,7 @@ namespace NCC.Extend.LqPurchaseRecords /// /// 购买记录表服务 /// - [ApiDescriptionSettings(Tag = "Extend",Name = "LqPurchaseRecords", Order = 200)] + [ApiDescriptionSettings(Tag = "Extend", Name = "LqPurchaseRecords", Order = 200)] [Route("api/Extend/[controller]")] public class LqPurchaseRecordsService : ILqPurchaseRecordsService, IDynamicApiController, ITransient { @@ -43,7 +43,7 @@ namespace NCC.Extend.LqPurchaseRecords ISqlSugarRepository lqPurchaseRecordsRepository, IUserManager userManager) { - _lqPurchaseRecordsRepository = lqPurchaseRecordsRepository; + _lqPurchaseRecordsRepository = lqPurchaseRecordsRepository; _db = _lqPurchaseRecordsRepository.Context; _userManager = userManager; } @@ -98,25 +98,25 @@ namespace NCC.Extend.LqPurchaseRecords .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.applicationId), p => p.ApplicationId.Contains(input.applicationId)) - .Select(it=> new LqPurchaseRecordsListOutput + .Select(it => new LqPurchaseRecordsListOutput { id = it.Id, - reimbursementCategoryId=it.ReimbursementCategoryId, - reimbursementCategoryName=it.ReimbursementCategoryName, - unitPrice=it.UnitPrice, - quantity=it.Quantity, - amount=it.Amount, - memo=it.Memo, - purchaseTime=it.PurchaseTime, - createTime=it.CreateTime, - createUser=it.CreateUser, - createUserStoreId=it.CreateUserStoreId, - approveStatus=it.ApproveStatus, - approveUser=it.ApproveUser, - approveTime=it.ApproveTime, - applicationId=it.ApplicationId, - }).MergeTable().OrderBy(sidx+" "+input.sort).ToPagedListAsync(input.currentPage, input.pageSize); - return PageResult.SqlSugarPageResult(data); + reimbursementCategoryId = it.ReimbursementCategoryId, + reimbursementCategoryName = it.ReimbursementCategoryName, + unitPrice = it.UnitPrice, + quantity = it.Quantity, + amount = it.Amount, + memo = it.Memo, + purchaseTime = it.PurchaseTime, + createTime = it.CreateTime, + createUser = it.CreateUser, + createUserStoreId = it.CreateUserStoreId, + approveStatus = it.ApproveStatus, + approveUser = it.ApproveUser, + approveTime = it.ApproveTime, + applicationId = it.ApplicationId, + }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize); + return PageResult.SqlSugarPageResult(data); } /// @@ -171,25 +171,25 @@ namespace NCC.Extend.LqPurchaseRecords .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.applicationId), p => p.ApplicationId.Contains(input.applicationId)) - .Select(it=> new LqPurchaseRecordsListOutput + .Select(it => new LqPurchaseRecordsListOutput { id = it.Id, - reimbursementCategoryId=it.ReimbursementCategoryId, - reimbursementCategoryName=it.ReimbursementCategoryName, - unitPrice=it.UnitPrice, - quantity=it.Quantity, - amount=it.Amount, - memo=it.Memo, - purchaseTime=it.PurchaseTime, - createTime=it.CreateTime, - createUser=it.CreateUser, - createUserStoreId=it.CreateUserStoreId, - approveStatus=it.ApproveStatus, - approveUser=it.ApproveUser, - approveTime=it.ApproveTime, - applicationId=it.ApplicationId, - }).MergeTable().OrderBy(sidx+" "+input.sort).ToListAsync(); - return data; + reimbursementCategoryId = it.ReimbursementCategoryId, + reimbursementCategoryName = it.ReimbursementCategoryName, + unitPrice = it.UnitPrice, + quantity = it.Quantity, + amount = it.Amount, + memo = it.Memo, + purchaseTime = it.PurchaseTime, + createTime = it.CreateTime, + createUser = it.CreateUser, + createUserStoreId = it.CreateUserStoreId, + approveStatus = it.ApproveStatus, + approveUser = it.ApproveUser, + approveTime = it.ApproveTime, + applicationId = it.ApplicationId, + }).MergeTable().OrderBy(sidx + " " + input.sort).ToListAsync(); + return data; } /// @@ -211,7 +211,7 @@ namespace NCC.Extend.LqPurchaseRecords { exportData = await this.GetNoPagingList(input); } - List paramList = "[{\"value\":\"记录编号\",\"field\":\"id\"},{\"value\":\"购买物品编号\",\"field\":\"reimbursementCategoryId\"},{\"value\":\"购买物品名称\",\"field\":\"reimbursementCategoryName\"},{\"value\":\"单价\",\"field\":\"unitPrice\"},{\"value\":\"数量\",\"field\":\"quantity\"},{\"value\":\"总金额\",\"field\":\"amount\"},{\"value\":\"备注说明\",\"field\":\"memo\"},{\"value\":\"购买时间\",\"field\":\"purchaseTime\"},{\"value\":\"创建时间\",\"field\":\"createTime\"},{\"value\":\"创建人\",\"field\":\"createUser\"},{\"value\":\"创建人门店\",\"field\":\"createUserStoreId\"},{\"value\":\"审批状态\",\"field\":\"approveStatus\"},{\"value\":\"审批人\",\"field\":\"approveUser\"},{\"value\":\"审批时间\",\"field\":\"approveTime\"},{\"value\":\"审批单编号\",\"field\":\"applicationId\"},]".ToList(); + List paramList = "[{\"value\":\"记录编号\",\"field\":\"id\"},{\"value\":\"购买物品编号\",\"field\":\"reimbursementCategoryId\"},{\"value\":\"购买物品名称\",\"field\":\"reimbursementCategoryName\"},{\"value\":\"单价\",\"field\":\"unitPrice\"},{\"value\":\"数量\",\"field\":\"quantity\"},{\"value\":\"总金额\",\"field\":\"amount\"},{\"value\":\"备注说明\",\"field\":\"memo\"},{\"value\":\"购买时间\",\"field\":\"purchaseTime\"},{\"value\":\"创建时间\",\"field\":\"createTime\"},{\"value\":\"创建人\",\"field\":\"createUser\"},{\"value\":\"创建人门店\",\"field\":\"createUserStoreId\"},{\"value\":\"审批状态\",\"field\":\"approveStatus\"},{\"value\":\"审批人\",\"field\":\"approveUser\"},{\"value\":\"审批时间\",\"field\":\"approveTime\"},{\"value\":\"审批单编号\",\"field\":\"applicationId\"},]".ToList(); ExcelConfig excelconfig = new ExcelConfig(); excelconfig.FileName = "购买记录表.xls"; excelconfig.HeadFont = "微软雅黑"; @@ -254,7 +254,7 @@ namespace NCC.Extend.LqPurchaseRecords //开启事务 _db.BeginTran(); //批量删除购买记录表 - await _db.Deleteable().In(d => d.Id,ids).ExecuteCommandAsync(); + await _db.Deleteable().In(d => d.Id, ids).ExecuteCommandAsync(); //关闭事务 _db.CommitTran(); } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs index 77942db..4f08fac 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs @@ -3504,6 +3504,7 @@ namespace NCC.Extend.LqStatistics AND F_PersonType = '健康师' AND F_WorkMonth = '{month}' AND F_IsEffective = 1 + AND F_HasBilling = 1 GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity ) as distinct_records"; @@ -3531,7 +3532,8 @@ namespace NCC.Extend.LqStatistics WHERE F_PersonId = '{userId}' AND F_PersonType = '健康师' AND F_WorkMonth = '{month}' - AND F_IsEffective = 1 + AND F_IsEffective = 1 + AND F_HasBilling = 1 GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity ) as distinct_records"; diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs index 781dce3..e13d609 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs @@ -1757,11 +1757,20 @@ namespace NCC.Extend.LqXhHyhk var kjbsyjList = await _db.Queryable().Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync(); // 查询已存在的人次记录(按BusinessId去重,避免重复添加) - var existingBusinessIds = await _db.Queryable() - .Where(x => consumeIds.Contains(x.BusinessId) && x.BusinessType == "耗卡" && x.IsEffective == StatusEnum.有效.GetHashCode()) - .Select(x => x.BusinessId) - .Distinct() - .ToListAsync(); + var existingBusinessIds = await _db.Queryable().Where(x => consumeIds.Contains(x.BusinessId) && x.BusinessType == "耗卡" && x.IsEffective == StatusEnum.有效.GetHashCode()).Select(x => x.BusinessId).Distinct().ToListAsync(); + + // 批量查询所有会员是否有开单记录(优化:避免在循环中执行N次查询) + var memberIds = consumeList.Where(x => !string.IsNullOrEmpty(x.Hy)).Select(x => x.Hy).Distinct().ToList(); + var membersWithBilling = new HashSet(); + if (memberIds.Any()) + { + var billingMemberIds = await _db.Queryable() + .Where(x => memberIds.Contains(x.Kdhy) && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Sfyj > 0) + .Select(x => x.Kdhy) + .Distinct() + .ToListAsync(); + membersWithBilling = new HashSet(billingMemberIds); + } // 3. 构建人次记录列表 var personTimesRecords = new List(); @@ -1782,19 +1791,14 @@ namespace NCC.Extend.LqXhHyhk } var workDate = consume.Hksj.Value.Date; // 工作日期(用于人次统计) var workMonth = consume.Hksj.Value.ToString("yyyyMM"); // 工作月份(用于人头统计) - + //查看该会员是否在开单记录表中存在开单记录(从批量查询结果中查找) + var billingRecord = !string.IsNullOrEmpty(consume.Hy) && membersWithBilling.Contains(consume.Hy); // 处理健康师业绩:去重后计算人次数量(剔除T区健康师) - var consumeJksyjList = jksyjList.Where(x => x.Glkdbh == consume.Id - && !string.IsNullOrEmpty(x.Jks) - && !string.IsNullOrEmpty(x.Jksxm) - && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList(); + var consumeJksyjList = jksyjList.Where(x => x.Glkdbh == consume.Id && !string.IsNullOrEmpty(x.Jks) && !string.IsNullOrEmpty(x.Jksxm) && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList(); if (consumeJksyjList.Any()) { // 按健康师ID去重 - var distinctJksyjList = consumeJksyjList - .GroupBy(x => x.Jks) - .Select(g => g.First()) - .ToList(); + var distinctJksyjList = consumeJksyjList.GroupBy(x => x.Jks).Select(g => g.First()).ToList(); // 计算人次数量:1 / 健康师数量 var jksQuantity = distinctJksyjList.Count > 0 ? 1.0m / distinctJksyjList.Count : 0; @@ -1815,23 +1819,18 @@ namespace NCC.Extend.LqXhHyhk WorkMonth = workMonth, Quantity = jksQuantity, CreateTime = DateTime.Now, - IsEffective = StatusEnum.有效.GetHashCode() + IsEffective = StatusEnum.有效.GetHashCode(), + HasBilling = billingRecord ? 1 : 0 }); } } // 处理科技老师业绩:去重后计算人次数量(剔除T区科技老师) - var consumeKjbsyjList = kjbsyjList.Where(x => x.Glkdbh == consume.Id - && !string.IsNullOrEmpty(x.Kjbls) - && !string.IsNullOrEmpty(x.Kjblsxm) - && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList(); + var consumeKjbsyjList = kjbsyjList.Where(x => x.Glkdbh == consume.Id && !string.IsNullOrEmpty(x.Kjbls) && !string.IsNullOrEmpty(x.Kjblsxm) && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList(); if (consumeKjbsyjList.Any()) { // 按科技老师ID去重 - var distinctKjbsyjList = consumeKjbsyjList - .GroupBy(x => x.Kjbls) - .Select(g => g.First()) - .ToList(); + var distinctKjbsyjList = consumeKjbsyjList.GroupBy(x => x.Kjbls).Select(g => g.First()).ToList(); // 计算人次数量:1 / 科技老师数量 var kjbsQuantity = distinctKjbsyjList.Count > 0 ? 1.0m / distinctKjbsyjList.Count : 0; @@ -1852,7 +1851,8 @@ namespace NCC.Extend.LqXhHyhk WorkMonth = workMonth, Quantity = kjbsQuantity, CreateTime = DateTime.Now, - IsEffective = StatusEnum.有效.GetHashCode() + IsEffective = StatusEnum.有效.GetHashCode(), + HasBilling = billingRecord ? 1 : 0 }); } } @@ -1865,9 +1865,7 @@ namespace NCC.Extend.LqXhHyhk // 如果没有指定耗卡ID,不删除任何记录(因为已经在构建记录列表时跳过了已存在的记录) if (!string.IsNullOrEmpty(consumeId)) { - await _db.Deleteable() - .Where(x => x.BusinessId == consumeId && x.BusinessType == "耗卡") - .ExecuteCommandAsync(); + await _db.Deleteable().Where(x => x.BusinessId == consumeId && x.BusinessType == "耗卡").ExecuteCommandAsync(); } // 批量插入新记录 diff --git a/sql/分析健康师消耗项目数差异.sql b/sql/分析健康师消耗项目数差异.sql new file mode 100644 index 0000000..d3ca8f0 --- /dev/null +++ b/sql/分析健康师消耗项目数差异.sql @@ -0,0 +1,93 @@ +-- ============================================ +-- 分析健康师消耗项目数差异 +-- 员工ID: 18566028067 (李芳) +-- ============================================ + +-- 1. 接口统计逻辑(使用耗卡时间,时间范围:2025-11-01 到 2025-11-25 11:55:32) +SELECT + jksyj.jkszh as EmployeeId, + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount_接口逻辑 +FROM lq_xh_jksyj jksyj +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id +WHERE jksyj.jkszh = '18566028067' + AND jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND hyhk.hksj >= '2025-11-01 00:00:00' + AND hyhk.hksj <= '2025-11-25 11:55:32' +GROUP BY jksyj.jkszh; + +-- 2. 整个11月的统计(使用业绩时间) +SELECT + jksyj.jkszh as EmployeeId, + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount_整个11月 +FROM lq_xh_jksyj jksyj +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') + AND jksyj.F_IsEffective = 1 + AND DATE_FORMAT(jksyj.yjsj, '%Y%m') = '202511' +GROUP BY jksyj.jkszh; + +-- 3. 查看11月25日之后的记录(可能导致差异的原因) +SELECT + jksyj.jkszh as EmployeeId, + jksyj.F_kdpxNumber as 项目数, + jksyj.yjsj as 业绩时间, + hyhk.hksj as 耗卡时间, + hyhk.F_IsEffective as 耗卡记录是否有效, + jksyj.F_IsEffective as 业绩记录是否有效 +FROM lq_xh_jksyj jksyj +LEFT JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') + AND jksyj.F_IsEffective = 1 + AND ( + (jksyj.yjsj >= '2025-11-25 11:55:32' AND jksyj.yjsj < '2025-12-01') + OR (hyhk.hksj >= '2025-11-25 11:55:32' AND hyhk.hksj < '2025-12-01') + ) +ORDER BY jksyj.yjsj; + +-- 4. 查看耗卡记录无效但业绩记录有效的记录(可能导致差异的原因) +SELECT + jksyj.jkszh as EmployeeId, + jksyj.F_kdpxNumber as 项目数, + jksyj.yjsj as 业绩时间, + hyhk.hksj as 耗卡时间, + hyhk.F_IsEffective as 耗卡记录是否有效, + jksyj.F_IsEffective as 业绩记录是否有效 +FROM lq_xh_jksyj jksyj +LEFT JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') + AND jksyj.F_IsEffective = 1 + AND (hyhk.F_IsEffective != 1 OR hyhk.F_IsEffective IS NULL) + AND jksyj.yjsj >= '2025-11-01' + AND jksyj.yjsj < '2025-12-01' +ORDER BY jksyj.yjsj; + +-- 5. 查看耗卡时间和业绩时间不一致的记录(可能导致差异的原因) +SELECT + jksyj.jkszh as EmployeeId, + jksyj.F_kdpxNumber as 项目数, + jksyj.yjsj as 业绩时间, + hyhk.hksj as 耗卡时间, + DATEDIFF(jksyj.yjsj, hyhk.hksj) as 时间差_天, + CASE + WHEN hyhk.hksj >= '2025-11-01 00:00:00' AND hyhk.hksj <= '2025-11-25 11:55:32' THEN '在接口时间范围内' + ELSE '不在接口时间范围内' + END as 是否在接口时间范围 +FROM lq_xh_jksyj jksyj +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') + AND jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND ( + (jksyj.yjsj >= '2025-11-01' AND jksyj.yjsj < '2025-12-01') + OR (hyhk.hksj >= '2025-11-01' AND hyhk.hksj < '2025-12-01') + ) + AND ( + jksyj.yjsj < '2025-11-01' + OR jksyj.yjsj >= '2025-12-01' + OR hyhk.hksj < '2025-11-01' + OR hyhk.hksj >= '2025-12-01' + OR DATEDIFF(jksyj.yjsj, hyhk.hksj) != 0 + ) +ORDER BY jksyj.yjsj; + + diff --git a/sql/查询员工11月25日之后的记录.sql b/sql/查询员工11月25日之后的记录.sql new file mode 100644 index 0000000..35558af --- /dev/null +++ b/sql/查询员工11月25日之后的记录.sql @@ -0,0 +1,56 @@ +-- ============================================ +-- 查询员工在2025-11-25 11:55:32之后的记录 +-- 员工ID: 18566028067 (李芳) +-- ============================================ + +-- 1. 查询11月25日11:55:32之后的业绩记录(使用业绩时间) +SELECT + jksyj.jkszh as 健康师账号, + jksyj.jksxm as 健康师姓名, + jksyj.F_kdpxNumber as 项目数, + jksyj.yjsj as 业绩时间, + hyhk.hksj as 耗卡时间, + hyhk.F_IsEffective as 耗卡记录是否有效, + jksyj.F_IsEffective as 业绩记录是否有效, + hyhk.F_Id as 耗卡记录ID +FROM lq_xh_jksyj jksyj +LEFT JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') + AND jksyj.F_IsEffective = 1 + AND jksyj.yjsj > '2025-11-25 11:55:32' + AND jksyj.yjsj < '2025-12-01' +ORDER BY jksyj.yjsj; + +-- 2. 查询11月25日11:55:32之后的耗卡记录(使用耗卡时间,这是接口统计使用的条件) +SELECT + jksyj.jkszh as 健康师账号, + jksyj.jksxm as 健康师姓名, + jksyj.F_kdpxNumber as 项目数, + jksyj.yjsj as 业绩时间, + hyhk.hksj as 耗卡时间, + hyhk.F_IsEffective as 耗卡记录是否有效, + jksyj.F_IsEffective as 业绩记录是否有效, + hyhk.F_Id as 耗卡记录ID +FROM lq_xh_jksyj jksyj +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') + AND jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND hyhk.hksj > '2025-11-25 11:55:32' + AND hyhk.hksj < '2025-12-01' +ORDER BY hyhk.hksj; + +-- 3. 统计11月25日11:55:32之后的项目数总和(使用耗卡时间,接口统计逻辑) +SELECT + jksyj.jkszh as 健康师账号, + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as 项目数总和 +FROM lq_xh_jksyj jksyj +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') + AND jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND hyhk.hksj > '2025-11-25 11:55:32' + AND hyhk.hksj < '2025-12-01' +GROUP BY jksyj.jkszh; + + diff --git a/sql/查询员工消耗项目数.sql b/sql/查询员工消耗项目数.sql new file mode 100644 index 0000000..2423ada --- /dev/null +++ b/sql/查询员工消耗项目数.sql @@ -0,0 +1,39 @@ +-- ============================================ +-- 查询员工在指定月份的消耗项目数 +-- ============================================ +-- 员工ID: 18566028067 +-- 查询月份: 2025年11月 (202511) +-- ============================================ + +-- 方式1:从健康师业绩表统计(推荐,使用F_kdpxNumber字段,包含原始+加班+陪同项目数) +SELECT + jksyj.jks as 健康师ID, + jksyj.jksxm as 健康师姓名, + jksyj.jkszh as 健康师账号, + COALESCE(SUM(jksyj.F_kdpxNumber), 0) as 消耗项目总数, + COALESCE(SUM(COALESCE(jksyj.F_OriginalKdpxNumber, jksyj.F_kdpxNumber)), 0) as 原始项目数, + COALESCE(SUM(COALESCE(jksyj.F_OvertimeKdpxNumber, 0)), 0) as 加班项目数, + COALESCE(SUM(COALESCE(jksyj.F_AccompaniedProjectNumber, 0)), 0) as 陪同项目数 +FROM lq_xh_jksyj jksyj +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') + AND jksyj.F_IsEffective = 1 + AND DATE_FORMAT(jksyj.yjsj, '%Y%m') = '202511' +GROUP BY jksyj.jks, jksyj.jksxm, jksyj.jkszh; + +-- 方式2:从品项明细表统计(备用方式,统计F_ProjectNumber字段) +SELECT + jksyj.jks as 健康师ID, + jksyj.jksxm as 健康师姓名, + jksyj.jkszh as 健康师账号, + COALESCE(SUM(pxmx.F_ProjectNumber), 0) as 消耗项目数 +FROM lq_xh_jksyj jksyj +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id +INNER JOIN lq_xh_pxmx pxmx ON pxmx.F_ConsumeInfoId = hyhk.F_Id +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') + AND jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND pxmx.F_IsEffective = 1 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '202511' +GROUP BY jksyj.jks, jksyj.jksxm, jksyj.jkszh; + + diff --git a/sql/添加人次记录表字段.sql b/sql/添加人次记录表字段.sql index fdbe586..0f49ae0 100644 --- a/sql/添加人次记录表字段.sql +++ b/sql/添加人次记录表字段.sql @@ -16,3 +16,5 @@ ADD COLUMN `F_HasBilling` INT(11) DEFAULT 0 COMMENT '是否有开单(0-否,1-是 -- WHERE TABLE_NAME = 'lq_person_times_record' -- AND COLUMN_NAME = 'F_HasBilling'; + +