Commit 887a79d6320132f8e4cf830613dd489f1c6cf100
Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP
Showing
33 changed files
with
1872 additions
and
231 deletions
netcore/src/Infrastructure/NCC/SpecificationDocument/Builders/SpecificationDocumentBuilder.cs
| ... | ... | @@ -152,6 +152,9 @@ namespace NCC.SpecificationDocument |
| 152 | 152 | //使得 Swagger 能够正确地显示 Enum 的对应关系 |
| 153 | 153 | if (_specificationDocumentSettings.EnableEnumSchemaFilter == true) swaggerGenOptions.SchemaFilter<EnumSchemaFilter>(); |
| 154 | 154 | |
| 155 | + // 使得 Swagger 能够显示实体类的描述(从 XML 注释中读取) | |
| 156 | + swaggerGenOptions.SchemaFilter<EntityDescriptionSchemaFilter>(); | |
| 157 | + | |
| 155 | 158 | // 支持控制器排序操作 |
| 156 | 159 | if (_specificationDocumentSettings.EnableTagsOrderDocumentFilter == true) swaggerGenOptions.DocumentFilter<TagsOrderDocumentFilter>(); |
| 157 | 160 | ... | ... |
netcore/src/Infrastructure/NCC/SpecificationDocument/Filters/EntityDescriptionSchemaFilter.cs
0 → 100644
| 1 | +using NCC.Dependency; | |
| 2 | +using NCC.ConfigurableOptions; | |
| 3 | +using Microsoft.OpenApi.Models; | |
| 4 | +using Swashbuckle.AspNetCore.SwaggerGen; | |
| 5 | +using System; | |
| 6 | +using System.Collections.Generic; | |
| 7 | +using System.IO; | |
| 8 | +using System.Linq; | |
| 9 | +using System.Xml.Linq; | |
| 10 | + | |
| 11 | +namespace NCC.SpecificationDocument | |
| 12 | +{ | |
| 13 | + /// <summary> | |
| 14 | + /// 修正 规范化文档实体类描述提示 | |
| 15 | + /// </summary> | |
| 16 | + [SuppressSniffer] | |
| 17 | + public class EntityDescriptionSchemaFilter : ISchemaFilter | |
| 18 | + { | |
| 19 | + /// <summary> | |
| 20 | + /// 实现过滤器方法 | |
| 21 | + /// </summary> | |
| 22 | + /// <param name="schema">OpenAPI Schema</param> | |
| 23 | + /// <param name="context">Schema 过滤器上下文</param> | |
| 24 | + public void Apply(OpenApiSchema schema, SchemaFilterContext context) | |
| 25 | + { | |
| 26 | + var type = context.Type; | |
| 27 | + | |
| 28 | + // 只处理项目程序集中的类型 | |
| 29 | + if (!App.Assemblies.Contains(type.Assembly)) | |
| 30 | + return; | |
| 31 | + | |
| 32 | + // 尝试从 XML 注释中获取类型描述 | |
| 33 | + try | |
| 34 | + { | |
| 35 | + // 直接使用程序集名称查找 XML 文件 | |
| 36 | + var assemblyName = type.Assembly.GetName().Name; | |
| 37 | + var xmlFileName = $"{assemblyName}.xml"; | |
| 38 | + var xmlFilePath = Path.Combine(AppContext.BaseDirectory, xmlFileName); | |
| 39 | + | |
| 40 | + if (File.Exists(xmlFilePath)) | |
| 41 | + { | |
| 42 | + try | |
| 43 | + { | |
| 44 | + var xmlDoc = XDocument.Load(xmlFilePath); | |
| 45 | + var memberName = $"T:{type.FullName}"; | |
| 46 | + var memberElement = xmlDoc.Descendants("member") | |
| 47 | + .FirstOrDefault(m => m.Attribute("name")?.Value == memberName); | |
| 48 | + | |
| 49 | + if (memberElement != null) | |
| 50 | + { | |
| 51 | + var summaryElement = memberElement.Element("summary"); | |
| 52 | + if (summaryElement != null) | |
| 53 | + { | |
| 54 | + var description = summaryElement.Value.Trim(); | |
| 55 | + if (!string.IsNullOrEmpty(description)) | |
| 56 | + { | |
| 57 | + schema.Description = description; | |
| 58 | + return; | |
| 59 | + } | |
| 60 | + } | |
| 61 | + } | |
| 62 | + } | |
| 63 | + catch | |
| 64 | + { | |
| 65 | + // 忽略 XML 解析错误 | |
| 66 | + } | |
| 67 | + } | |
| 68 | + | |
| 69 | + // 如果直接查找失败,尝试从配置的 XML 注释列表中查找 | |
| 70 | + var specificationDocumentSettings = App.GetOptions<SpecificationDocumentSettingsOptions>(); | |
| 71 | + var xmlComments = specificationDocumentSettings?.XmlComments; | |
| 72 | + if (xmlComments != null && xmlComments.Any()) | |
| 73 | + { | |
| 74 | + foreach (var xmlComment in xmlComments) | |
| 75 | + { | |
| 76 | + var assemblyXmlName = xmlComment.EndsWith(".xml") ? xmlComment : $"{xmlComment}.xml"; | |
| 77 | + var assemblyXmlPath = Path.Combine(AppContext.BaseDirectory, assemblyXmlName); | |
| 78 | + | |
| 79 | + if (File.Exists(assemblyXmlPath) && assemblyXmlPath != xmlFilePath) | |
| 80 | + { | |
| 81 | + try | |
| 82 | + { | |
| 83 | + var xmlDoc = XDocument.Load(assemblyXmlPath); | |
| 84 | + var memberName = $"T:{type.FullName}"; | |
| 85 | + var memberElement = xmlDoc.Descendants("member") | |
| 86 | + .FirstOrDefault(m => m.Attribute("name")?.Value == memberName); | |
| 87 | + | |
| 88 | + if (memberElement != null) | |
| 89 | + { | |
| 90 | + var summaryElement = memberElement.Element("summary"); | |
| 91 | + if (summaryElement != null) | |
| 92 | + { | |
| 93 | + var description = summaryElement.Value.Trim(); | |
| 94 | + if (!string.IsNullOrEmpty(description)) | |
| 95 | + { | |
| 96 | + schema.Description = description; | |
| 97 | + break; | |
| 98 | + } | |
| 99 | + } | |
| 100 | + } | |
| 101 | + } | |
| 102 | + catch | |
| 103 | + { | |
| 104 | + // 忽略 XML 解析错误 | |
| 105 | + } | |
| 106 | + } | |
| 107 | + } | |
| 108 | + } | |
| 109 | + } | |
| 110 | + catch | |
| 111 | + { | |
| 112 | + // 忽略配置获取错误 | |
| 113 | + } | |
| 114 | + } | |
| 115 | + } | |
| 116 | +} | |
| 117 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs
| ... | ... | @@ -66,14 +66,24 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb |
| 66 | 66 | public decimal consumeAmount { get; set; } |
| 67 | 67 | |
| 68 | 68 | /// <summary> |
| 69 | - /// 人头 - 统计该健康师在指定时间周期内服务的唯一客户数量(按客户去重) | |
| 69 | + /// 有效人头 - 统计该健康师在指定时间周期内服务的唯一客户数量(按客户去重,F_HasBilling=1,支持小数) | |
| 70 | 70 | /// </summary> |
| 71 | - public int headCount { get; set; } | |
| 71 | + public decimal headCount { get; set; } | |
| 72 | 72 | |
| 73 | 73 | /// <summary> |
| 74 | - /// 人次 - 统计该健康师在指定时间周期内服务的客户人次(按客户+日期去重,同一客户不同天算多次) | |
| 74 | + /// 有效人次 - 统计该健康师在指定时间周期内服务的客户人次(按客户+日期去重,F_HasBilling=1,同一客户不同天算多次,支持小数) | |
| 75 | 75 | /// </summary> |
| 76 | - public int personCount { get; set; } | |
| 76 | + public decimal personCount { get; set; } | |
| 77 | + | |
| 78 | + /// <summary> | |
| 79 | + /// 无效人头 - 统计该健康师在指定时间周期内服务的唯一客户数量(按客户去重,F_HasBilling=0,支持小数) | |
| 80 | + /// </summary> | |
| 81 | + public decimal invalidHeadCount { get; set; } | |
| 82 | + | |
| 83 | + /// <summary> | |
| 84 | + /// 无效人次 - 统计该健康师在指定时间周期内服务的客户人次(按客户+日期去重,F_HasBilling=0,同一客户不同天算多次,支持小数) | |
| 85 | + /// </summary> | |
| 86 | + public decimal invalidPersonCount { get; set; } | |
| 77 | 87 | |
| 78 | 88 | /// <summary> |
| 79 | 89 | /// 消耗项目数 - 统计该健康师在指定时间周期内消耗的项目总次数 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductCrInput.cs
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LeadCustomerStatisticsListOutput.cs
0 → 100644
| 1 | +using System; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Dto.LqStatistics | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 线索池客户统计报表输出 | |
| 7 | + /// </summary> | |
| 8 | + public class LeadCustomerStatisticsListOutput | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 线索池客户(拓客编号) | |
| 12 | + /// </summary> | |
| 13 | + public string LeadCustomerId { get; set; } | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 客户姓名 | |
| 17 | + /// </summary> | |
| 18 | + public string CustomerName { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 拓客时间 | |
| 22 | + /// </summary> | |
| 23 | + public DateTime? ExpansionTime { get; set; } | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 是否邀约(是/否) | |
| 27 | + /// </summary> | |
| 28 | + public string HasInvite { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 是否预约(是/否) | |
| 32 | + /// </summary> | |
| 33 | + public string HasAppointment { get; set; } | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 是否有消耗(是/否) | |
| 37 | + /// </summary> | |
| 38 | + public string HasConsume { get; set; } | |
| 39 | + | |
| 40 | + /// <summary> | |
| 41 | + /// 是否开单(是/否) | |
| 42 | + /// </summary> | |
| 43 | + public string HasBilling { get; set; } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 未开单原因 | |
| 47 | + /// </summary> | |
| 48 | + public string NoBillingReason { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 开卡金额 | |
| 52 | + /// </summary> | |
| 53 | + public decimal BillingAmount { get; set; } | |
| 54 | + | |
| 55 | + /// <summary> | |
| 56 | + /// 开卡卡项(多个卡项用顿号分隔) | |
| 57 | + /// </summary> | |
| 58 | + public string BillingItems { get; set; } | |
| 59 | + | |
| 60 | + /// <summary> | |
| 61 | + /// 实际预约记录数(不管是否通过邀约产生) | |
| 62 | + /// </summary> | |
| 63 | + public int ActualAppointmentCount { get; set; } | |
| 64 | + | |
| 65 | + /// <summary> | |
| 66 | + /// 实际消耗记录数(不管是否通过预约产生) | |
| 67 | + /// </summary> | |
| 68 | + public int ActualConsumeCount { get; set; } | |
| 69 | + | |
| 70 | + /// <summary> | |
| 71 | + /// 实际开单记录数(不管是否通过预约产生) | |
| 72 | + /// </summary> | |
| 73 | + public int ActualBillingCount { get; set; } | |
| 74 | + | |
| 75 | + /// <summary> | |
| 76 | + /// 问题分析说明 | |
| 77 | + /// </summary> | |
| 78 | + public string Analysis { get; set; } | |
| 79 | + } | |
| 80 | +} | |
| 81 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LeadCustomerStatisticsListQueryInput.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using System.Collections.Generic; | |
| 3 | +using System.ComponentModel.DataAnnotations; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.Dto.LqStatistics | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 线索池客户统计报表查询输入 | |
| 9 | + /// </summary> | |
| 10 | + public class LeadCustomerStatisticsListQueryInput | |
| 11 | + { | |
| 12 | + /// <summary> | |
| 13 | + /// 页码 | |
| 14 | + /// </summary> | |
| 15 | + [Required] | |
| 16 | + public int PageIndex { get; set; } = 1; | |
| 17 | + | |
| 18 | + /// <summary> | |
| 19 | + /// 页大小 | |
| 20 | + /// </summary> | |
| 21 | + [Required] | |
| 22 | + public int PageSize { get; set; } = 20; | |
| 23 | + | |
| 24 | + /// <summary> | |
| 25 | + /// 开始时间(拓客时间范围) | |
| 26 | + /// </summary> | |
| 27 | + public DateTime? StartTime { get; set; } | |
| 28 | + | |
| 29 | + /// <summary> | |
| 30 | + /// 结束时间(拓客时间范围) | |
| 31 | + /// </summary> | |
| 32 | + public DateTime? EndTime { get; set; } | |
| 33 | + | |
| 34 | + /// <summary> | |
| 35 | + /// 门店ID列表(可以多个门店) | |
| 36 | + /// </summary> | |
| 37 | + public List<string> StoreIds { get; set; } | |
| 38 | + | |
| 39 | + /// <summary> | |
| 40 | + /// 拓客活动ID | |
| 41 | + /// </summary> | |
| 42 | + public string EventId { get; set; } | |
| 43 | + } | |
| 44 | +} | |
| 45 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/MemberUpgradeStatisticsListOutput.cs
0 → 100644
| 1 | +namespace NCC.Extend.Entitys.Dto.LqStatistics | |
| 2 | +{ | |
| 3 | + /// <summary> | |
| 4 | + /// 会员升单统计输出 | |
| 5 | + /// </summary> | |
| 6 | + public class MemberUpgradeStatisticsListOutput | |
| 7 | + { | |
| 8 | + /// <summary> | |
| 9 | + /// 会员ID | |
| 10 | + /// </summary> | |
| 11 | + public string MemberId { get; set; } | |
| 12 | + | |
| 13 | + /// <summary> | |
| 14 | + /// 会员姓名 | |
| 15 | + /// </summary> | |
| 16 | + public string MemberName { get; set; } | |
| 17 | + | |
| 18 | + /// <summary> | |
| 19 | + /// 会员手机号 | |
| 20 | + /// </summary> | |
| 21 | + public string MemberPhone { get; set; } | |
| 22 | + | |
| 23 | + /// <summary> | |
| 24 | + /// 前4单中是否有升医美(是/否) | |
| 25 | + /// </summary> | |
| 26 | + public string HasUpgradeMedicalBeauty { get; set; } | |
| 27 | + | |
| 28 | + /// <summary> | |
| 29 | + /// 前4单中是否有升科美(是/否) | |
| 30 | + /// </summary> | |
| 31 | + public string HasUpgradeTechBeauty { get; set; } | |
| 32 | + | |
| 33 | + /// <summary> | |
| 34 | + /// 前4单中是否有升生美(是/否) | |
| 35 | + /// </summary> | |
| 36 | + public string HasUpgradeLifeBeauty { get; set; } | |
| 37 | + } | |
| 38 | +} | |
| 39 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/MemberUpgradeStatisticsListQueryInput.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using System.Collections.Generic; | |
| 3 | + | |
| 4 | +namespace NCC.Extend.Entitys.Dto.LqStatistics | |
| 5 | +{ | |
| 6 | + /// <summary> | |
| 7 | + /// 会员升单统计查询输入 | |
| 8 | + /// </summary> | |
| 9 | + public class MemberUpgradeStatisticsListQueryInput | |
| 10 | + { | |
| 11 | + /// <summary> | |
| 12 | + /// 页码 | |
| 13 | + /// </summary> | |
| 14 | + public int PageIndex { get; set; } = 1; | |
| 15 | + | |
| 16 | + /// <summary> | |
| 17 | + /// 每页数量 | |
| 18 | + /// </summary> | |
| 19 | + public int PageSize { get; set; } = 20; | |
| 20 | + | |
| 21 | + /// <summary> | |
| 22 | + /// 会员ID列表(可选,不传则查询所有会员) | |
| 23 | + /// </summary> | |
| 24 | + public List<string> MemberIds { get; set; } | |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 是否升医美(true-是,false-否,null-不筛选) | |
| 28 | + /// </summary> | |
| 29 | + public bool? HasUpgradeMedicalBeauty { get; set; } | |
| 30 | + | |
| 31 | + /// <summary> | |
| 32 | + /// 是否升科美(true-是,false-否,null-不筛选) | |
| 33 | + /// </summary> | |
| 34 | + public bool? HasUpgradeTechBeauty { get; set; } | |
| 35 | + | |
| 36 | + /// <summary> | |
| 37 | + /// 是否升生美(true-是,false-否,null-不筛选) | |
| 38 | + /// </summary> | |
| 39 | + public bool? HasUpgradeLifeBeauty { get; set; } | |
| 40 | + } | |
| 41 | +} | |
| 42 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/StoreStatisticsListOutput.cs
0 → 100644
| 1 | +namespace NCC.Extend.Entitys.Dto.LqStatistics | |
| 2 | +{ | |
| 3 | + /// <summary> | |
| 4 | + /// 门店统计报表输出 | |
| 5 | + /// </summary> | |
| 6 | + public class StoreStatisticsListOutput | |
| 7 | + { | |
| 8 | + /// <summary> | |
| 9 | + /// 门店ID | |
| 10 | + /// </summary> | |
| 11 | + public string StoreId { get; set; } | |
| 12 | + | |
| 13 | + /// <summary> | |
| 14 | + /// 门店名称 | |
| 15 | + /// </summary> | |
| 16 | + public string StoreName { get; set; } | |
| 17 | + | |
| 18 | + /// <summary> | |
| 19 | + /// 总人数(拓客记录数) | |
| 20 | + /// </summary> | |
| 21 | + public int TotalCount { get; set; } | |
| 22 | + | |
| 23 | + /// <summary> | |
| 24 | + /// 拓客人数(去重的会员数) | |
| 25 | + /// </summary> | |
| 26 | + public int TkMemberCount { get; set; } | |
| 27 | + | |
| 28 | + /// <summary> | |
| 29 | + /// 邀约数(通过拓客编号关联的邀约记录数) | |
| 30 | + /// </summary> | |
| 31 | + public int InviteCount { get; set; } | |
| 32 | + | |
| 33 | + /// <summary> | |
| 34 | + /// 预约数(通过邀约ID关联的预约记录数) | |
| 35 | + /// </summary> | |
| 36 | + public int AppointmentCount { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 耗卡数(通过预约ID关联的耗卡记录数) | |
| 40 | + /// </summary> | |
| 41 | + public int ConsumeCount { get; set; } | |
| 42 | + | |
| 43 | + /// <summary> | |
| 44 | + /// 开单数(通过预约ID关联的开单记录数) | |
| 45 | + /// </summary> | |
| 46 | + public int BillingCount { get; set; } | |
| 47 | + | |
| 48 | + /// <summary> | |
| 49 | + /// 开单金额(通过预约ID关联的开单记录金额汇总) | |
| 50 | + /// </summary> | |
| 51 | + public decimal BillingAmount { get; set; } | |
| 52 | + } | |
| 53 | +} | |
| 54 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/StoreStatisticsListQueryInput.cs
0 → 100644
| 1 | +using System; | |
| 2 | +using System.Collections.Generic; | |
| 3 | +using System.ComponentModel.DataAnnotations; | |
| 4 | + | |
| 5 | +namespace NCC.Extend.Entitys.Dto.LqStatistics | |
| 6 | +{ | |
| 7 | + /// <summary> | |
| 8 | + /// 门店统计报表查询输入 | |
| 9 | + /// </summary> | |
| 10 | + public class StoreStatisticsListQueryInput | |
| 11 | + { | |
| 12 | + /// <summary> | |
| 13 | + /// 开始时间(拓客时间范围) | |
| 14 | + /// </summary> | |
| 15 | + public DateTime? StartTime { get; set; } | |
| 16 | + | |
| 17 | + /// <summary> | |
| 18 | + /// 结束时间(拓客时间范围) | |
| 19 | + /// </summary> | |
| 20 | + public DateTime? EndTime { get; set; } | |
| 21 | + | |
| 22 | + /// <summary> | |
| 23 | + /// 门店ID列表(可以多个门店) | |
| 24 | + /// </summary> | |
| 25 | + public List<string> StoreIds { get; set; } | |
| 26 | + | |
| 27 | + /// <summary> | |
| 28 | + /// 拓客活动ID(可选,不传则查询所有活动) | |
| 29 | + /// </summary> | |
| 30 | + public string EventId { get; set; } | |
| 31 | + } | |
| 32 | +} | |
| 33 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_jksyj/LqHytkJksyjEntity.cs
| ... | ... | @@ -113,5 +113,17 @@ namespace NCC.Extend.Entitys.lq_hytk_jksyj |
| 113 | 113 | /// </summary> |
| 114 | 114 | [SugarColumn(ColumnName = "F_IsEffective")] |
| 115 | 115 | public int IsEffective { get; set; } = StatusEnum.有效.GetHashCode(); |
| 116 | + | |
| 117 | + /// <summary> | |
| 118 | + /// 品项分类 | |
| 119 | + /// </summary> | |
| 120 | + [SugarColumn(ColumnName = "F_ItemCategory")] | |
| 121 | + public string ItemCategory { get; set; } | |
| 122 | + | |
| 123 | + /// <summary> | |
| 124 | + /// 品项ID | |
| 125 | + /// </summary> | |
| 126 | + [SugarColumn(ColumnName = "F_ItemId")] | |
| 127 | + public string ItemId { get; set; } | |
| 116 | 128 | } |
| 117 | 129 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_kjbsyj/LqHytkKjbsyjEntity.cs
| ... | ... | @@ -108,5 +108,17 @@ namespace NCC.Extend.Entitys.lq_hytk_kjbsyj |
| 108 | 108 | /// </summary> |
| 109 | 109 | [SugarColumn(ColumnName = "F_IsEffective")] |
| 110 | 110 | public int IsEffective { get; set; } = StatusEnum.有效.GetHashCode(); |
| 111 | + | |
| 112 | + /// <summary> | |
| 113 | + /// 品项分类 | |
| 114 | + /// </summary> | |
| 115 | + [SugarColumn(ColumnName = "F_ItemCategory")] | |
| 116 | + public string ItemCategory { get; set; } | |
| 117 | + | |
| 118 | + /// <summary> | |
| 119 | + /// 品项ID | |
| 120 | + /// </summary> | |
| 121 | + [SugarColumn(ColumnName = "F_ItemId")] | |
| 122 | + public string ItemId { get; set; } | |
| 111 | 123 | } |
| 112 | 124 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_jksyj/LqKdJksyjEntity.cs
| ... | ... | @@ -76,5 +76,17 @@ namespace NCC.Extend.Entitys.lq_kd_jksyj |
| 76 | 76 | /// </summary> |
| 77 | 77 | [SugarColumn(ColumnName = "F_ActivityId")] |
| 78 | 78 | public string ActivityId { get; set; } |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 品项分类 | |
| 82 | + /// </summary> | |
| 83 | + [SugarColumn(ColumnName = "F_ItemCategory")] | |
| 84 | + public string ItemCategory { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 品项ID | |
| 88 | + /// </summary> | |
| 89 | + [SugarColumn(ColumnName = "F_ItemId")] | |
| 90 | + public string ItemId { get; set; } | |
| 79 | 91 | } |
| 80 | 92 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_kjbsyj/LqKdKjbsyjEntity.cs
| ... | ... | @@ -76,5 +76,17 @@ namespace NCC.Extend.Entitys.lq_kd_kjbsyj |
| 76 | 76 | /// </summary> |
| 77 | 77 | [SugarColumn(ColumnName = "F_ActivityId")] |
| 78 | 78 | public string ActivityId { get; set; } |
| 79 | + | |
| 80 | + /// <summary> | |
| 81 | + /// 品项分类 | |
| 82 | + /// </summary> | |
| 83 | + [SugarColumn(ColumnName = "F_ItemCategory")] | |
| 84 | + public string ItemCategory { get; set; } | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 品项ID | |
| 88 | + /// </summary> | |
| 89 | + [SugarColumn(ColumnName = "F_ItemId")] | |
| 90 | + public string ItemId { get; set; } | |
| 79 | 91 | } |
| 80 | 92 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_jksyj/LqXhJksyjEntity.cs
| ... | ... | @@ -124,5 +124,17 @@ namespace NCC.Extend.Entitys.lq_xh_jksyj |
| 124 | 124 | /// </summary> |
| 125 | 125 | [SugarColumn(ColumnName = "F_AccompaniedProjectNumber")] |
| 126 | 126 | public decimal? AccompaniedProjectNumber { get; set; } |
| 127 | + | |
| 128 | + /// <summary> | |
| 129 | + /// 品项分类 | |
| 130 | + /// </summary> | |
| 131 | + [SugarColumn(ColumnName = "F_ItemCategory")] | |
| 132 | + public string ItemCategory { get; set; } | |
| 133 | + | |
| 134 | + /// <summary> | |
| 135 | + /// 品项ID | |
| 136 | + /// </summary> | |
| 137 | + [SugarColumn(ColumnName = "F_ItemId")] | |
| 138 | + public string ItemId { get; set; } | |
| 127 | 139 | } |
| 128 | 140 | } |
| 129 | 141 | \ No newline at end of file | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_kjbsyj/LqXhKjbsyjEntity.cs
| ... | ... | @@ -106,5 +106,17 @@ namespace NCC.Extend.Entitys.lq_xh_kjbsyj |
| 106 | 106 | /// </summary> |
| 107 | 107 | [SugarColumn(ColumnName = "F_IsEffective")] |
| 108 | 108 | public int? IsEffective { get; set; } = 1; |
| 109 | + | |
| 110 | + /// <summary> | |
| 111 | + /// 品项分类 | |
| 112 | + /// </summary> | |
| 113 | + [SugarColumn(ColumnName = "F_ItemCategory")] | |
| 114 | + public string ItemCategory { get; set; } | |
| 115 | + | |
| 116 | + /// <summary> | |
| 117 | + /// 品项ID | |
| 118 | + /// </summary> | |
| 119 | + [SugarColumn(ColumnName = "F_ItemId")] | |
| 120 | + public string ItemId { get; set; } | |
| 109 | 121 | } |
| 110 | 122 | } |
| 111 | 123 | \ No newline at end of file | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/NCC.Extend.Entitys.csproj
| ... | ... | @@ -2,9 +2,12 @@ |
| 2 | 2 | <PropertyGroup> |
| 3 | 3 | <TargetFramework>net6.0</TargetFramework> |
| 4 | 4 | </PropertyGroup> |
| 5 | - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | |
| 5 | + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |
| 6 | + <DocumentationFile>bin\Debug\$(AssemblyName).xml</DocumentationFile> | |
| 7 | + </PropertyGroup> | |
| 8 | + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | |
| 6 | 9 | <OutputPath /> |
| 7 | - <DocumentationFile>bin\Release\NCC.Extend.Entitys.xml</DocumentationFile> | |
| 10 | + <DocumentationFile>bin\Release\$(AssemblyName).xml</DocumentationFile> | |
| 8 | 11 | </PropertyGroup> |
| 9 | 12 | <ItemGroup> |
| 10 | 13 | <ProjectReference Include="..\..\..\Infrastructure\NCC.Expand.Thirdparty\NCC.Expand.Thirdparty.csproj" /> | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/NCC.Extend.Interfaces.csproj
| ... | ... | @@ -3,6 +3,12 @@ |
| 3 | 3 | <PropertyGroup> |
| 4 | 4 | <TargetFramework>net6.0</TargetFramework> |
| 5 | 5 | </PropertyGroup> |
| 6 | + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |
| 7 | + <DocumentationFile>bin\Debug\$(AssemblyName).xml</DocumentationFile> | |
| 8 | + </PropertyGroup> | |
| 9 | + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | |
| 10 | + <DocumentationFile>bin\Release\$(AssemblyName).xml</DocumentationFile> | |
| 11 | + </PropertyGroup> | |
| 6 | 12 | |
| 7 | 13 | <ItemGroup> |
| 8 | 14 | <ProjectReference Include="..\NCC.Extend.Entitys\NCC.Extend.Entitys.csproj" /> | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
| ... | ... | @@ -399,9 +399,7 @@ namespace NCC.Extend |
| 399 | 399 | throw NCCException.Oh("库存ID不能为空"); |
| 400 | 400 | } |
| 401 | 401 | |
| 402 | - var inventory = await _db.Queryable<LqInventoryEntity>() | |
| 403 | - .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 404 | - .FirstAsync(); | |
| 402 | + var inventory = await _db.Queryable<LqInventoryEntity>().Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync(); | |
| 405 | 403 | |
| 406 | 404 | if (inventory == null) |
| 407 | 405 | { | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
| ... | ... | @@ -166,71 +166,59 @@ namespace NCC.Extend |
| 166 | 166 | } |
| 167 | 167 | |
| 168 | 168 | // 生成批次ID(如果未提供) |
| 169 | - var batchId = string.IsNullOrWhiteSpace(input.BatchId) | |
| 170 | - ? YitIdHelper.NextId().ToString() | |
| 171 | - : input.BatchId; | |
| 172 | - | |
| 169 | + var batchId = string.IsNullOrWhiteSpace(input.BatchId) ? YitIdHelper.NextId().ToString() : input.BatchId; | |
| 173 | 170 | var successIds = new List<string>(); |
| 174 | - var failItems = new List<BatchCreateFailItem>(); | |
| 175 | 171 | |
| 176 | - _db.Ado.BeginTran(); | |
| 172 | + // 按产品ID分组,批量验证库存(在事务外先检查,避免不必要的回滚) | |
| 173 | + var productGroups = input.UsageItems.Select((item, index) => new { Item = item, Index = index }).GroupBy(x => x.Item.ProductId).ToList(); | |
| 177 | 174 | |
| 178 | - try | |
| 179 | - { | |
| 180 | - // 按产品ID分组,批量验证库存 | |
| 181 | - var productGroups = input.UsageItems | |
| 182 | - .Select((item, index) => new { Item = item, Index = index }) | |
| 183 | - .GroupBy(x => x.Item.ProductId) | |
| 184 | - .ToList(); | |
| 185 | - | |
| 186 | - // 计算每个产品的总需求并检查库存 | |
| 187 | - foreach (var productGroup in productGroups) | |
| 188 | - { | |
| 189 | - var productId = productGroup.Key; | |
| 190 | - var totalRequired = productGroup.Sum(x => x.Item.UsageQuantity); | |
| 175 | + // 获取所有需要检查的产品ID | |
| 176 | + var productIds = productGroups.Select(x => x.Key).Distinct().ToList(); | |
| 191 | 177 | |
| 192 | - // 计算该产品的总库存数量 | |
| 193 | - var totalInventory = await _db.Queryable<LqInventoryEntity>() | |
| 194 | - .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 195 | - .SumAsync(x => (int?)x.Quantity) ?? 0; | |
| 178 | + // 批量查询所有产品的库存信息(总库存) | |
| 179 | + var inventoryList = await _db.Queryable<LqInventoryEntity>().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(); | |
| 180 | + var inventoryMap = inventoryList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalInventory)); | |
| 196 | 181 | |
| 197 | - // 计算该产品的已使用数量 | |
| 198 | - var totalUsage = await _db.Queryable<LqInventoryUsageEntity>() | |
| 199 | - .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 200 | - .SumAsync(x => (int?)x.UsageQuantity) ?? 0; | |
| 182 | + // 批量查询所有产品的已使用数量 | |
| 183 | + var usageList = await _db.Queryable<LqInventoryUsageEntity>().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(); | |
| 184 | + var usageMap = usageList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalUsage)); | |
| 201 | 185 | |
| 202 | - // 计算可用库存 | |
| 203 | - var availableInventory = totalInventory - totalUsage; | |
| 186 | + // 批量查询所有产品的名称 | |
| 187 | + var productDict = await _db.Queryable<LqProductEntity>() | |
| 188 | + .Where(x => productIds.Contains(x.Id)) | |
| 189 | + .Select(x => new { x.Id, x.ProductName }) | |
| 190 | + .ToListAsync(); | |
| 191 | + var productNameMap = productDict.ToDictionary(x => x.Id, x => x.ProductName ?? "未知产品"); | |
| 204 | 192 | |
| 205 | - // 检查库存是否足够 | |
| 206 | - if (availableInventory < totalRequired) | |
| 207 | - { | |
| 208 | - var failIndices = productGroup.Select(x => x.Index).ToList(); | |
| 209 | - | |
| 210 | - foreach (var index in failIndices) | |
| 211 | - { | |
| 212 | - failItems.Add(new BatchCreateFailItem | |
| 213 | - { | |
| 214 | - Index = index, | |
| 215 | - ProductId = productId, | |
| 216 | - Reason = $"库存不足,当前可用库存:{availableInventory},需要数量:{totalRequired}" | |
| 217 | - }); | |
| 218 | - } | |
| 219 | - } | |
| 193 | + // 检查每个产品的库存 | |
| 194 | + foreach (var productGroup in productGroups) | |
| 195 | + { | |
| 196 | + var productId = productGroup.Key; | |
| 197 | + var totalRequired = productGroup.Sum(x => x.Item.UsageQuantity); | |
| 198 | + | |
| 199 | + // 从字典中获取库存信息 | |
| 200 | + var totalInventory = inventoryMap.GetValueOrDefault(productId, 0); | |
| 201 | + var totalUsage = usageMap.GetValueOrDefault(productId, 0); | |
| 202 | + var availableInventory = totalInventory - totalUsage; | |
| 203 | + | |
| 204 | + // 检查库存是否足够,如果不足则直接抛出异常 | |
| 205 | + if (availableInventory < totalRequired) | |
| 206 | + { | |
| 207 | + var productName = productNameMap.GetValueOrDefault(productId, "未知产品"); | |
| 208 | + throw NCCException.Oh($"产品【{productName}】(ID: {productId}) 库存不足,当前可用库存:{availableInventory},需要数量:{totalRequired}"); | |
| 220 | 209 | } |
| 210 | + } | |
| 211 | + | |
| 212 | + _db.Ado.BeginTran(); | |
| 221 | 213 | |
| 222 | - // 创建成功的使用记录 | |
| 214 | + try | |
| 215 | + { | |
| 216 | + // 创建使用记录 | |
| 223 | 217 | var entitiesToInsert = new List<LqInventoryUsageEntity>(); |
| 224 | 218 | for (int i = 0; i < input.UsageItems.Count; i++) |
| 225 | 219 | { |
| 226 | 220 | var item = input.UsageItems[i]; |
| 227 | 221 | |
| 228 | - // 跳过失败项 | |
| 229 | - if (failItems.Any(x => x.Index == i)) | |
| 230 | - { | |
| 231 | - continue; | |
| 232 | - } | |
| 233 | - | |
| 234 | 222 | var usageEntity = new LqInventoryUsageEntity |
| 235 | 223 | { |
| 236 | 224 | Id = YitIdHelper.NextId().ToString(), |
| ... | ... | @@ -265,9 +253,9 @@ namespace NCC.Extend |
| 265 | 253 | { |
| 266 | 254 | BatchId = batchId, |
| 267 | 255 | SuccessCount = successIds.Count, |
| 268 | - FailCount = failItems.Count, | |
| 256 | + FailCount = 0, | |
| 269 | 257 | SuccessIds = successIds, |
| 270 | - FailItems = failItems | |
| 258 | + FailItems = new List<BatchCreateFailItem>() | |
| 271 | 259 | }; |
| 272 | 260 | } |
| 273 | 261 | catch |
| ... | ... | @@ -346,35 +334,34 @@ namespace NCC.Extend |
| 346 | 334 | var sidx = input.sidx == null ? "id" : input.sidx; |
| 347 | 335 | |
| 348 | 336 | // 查询使用记录信息,关联产品表 |
| 349 | - var data = await _db.Queryable<LqInventoryUsageEntity, LqProductEntity>( | |
| 350 | - (usage, product) => usage.ProductId == product.Id) | |
| 351 | - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) | |
| 352 | - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId) | |
| 353 | - .WhereIF(input.UsageStartTime.HasValue, (usage, product) => usage.UsageTime >= input.UsageStartTime.Value) | |
| 354 | - .WhereIF(input.UsageEndTime.HasValue, (usage, product) => usage.UsageTime <= input.UsageEndTime.Value) | |
| 355 | - .WhereIF(!string.IsNullOrWhiteSpace(input.RelatedConsumeId), (usage, product) => usage.RelatedConsumeId == input.RelatedConsumeId) | |
| 356 | - .WhereIF(!string.IsNullOrWhiteSpace(input.UsageBatchId), (usage, product) => usage.UsageBatchId == input.UsageBatchId) | |
| 357 | - .WhereIF(input.IsEffective.HasValue, (usage, product) => usage.IsEffective == input.IsEffective.Value) | |
| 358 | - .Select((usage, product) => new LqInventoryUsageListOutput | |
| 337 | + var data = await _db.Queryable<LqInventoryUsageEntity, LqProductEntity>((u, product) => u.ProductId == product.Id) | |
| 338 | + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product) => u.ProductId == input.ProductId) | |
| 339 | + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product) => u.StoreId == input.StoreId) | |
| 340 | + .WhereIF(input.UsageStartTime.HasValue, (u, product) => u.UsageTime >= input.UsageStartTime.Value) | |
| 341 | + .WhereIF(input.UsageEndTime.HasValue, (u, product) => u.UsageTime <= input.UsageEndTime.Value) | |
| 342 | + .WhereIF(!string.IsNullOrWhiteSpace(input.RelatedConsumeId), (u, product) => u.RelatedConsumeId == input.RelatedConsumeId) | |
| 343 | + .WhereIF(!string.IsNullOrWhiteSpace(input.UsageBatchId), (u, product) => u.UsageBatchId == input.UsageBatchId) | |
| 344 | + .WhereIF(input.IsEffective.HasValue, (u, product) => u.IsEffective == input.IsEffective.Value) | |
| 345 | + .Select((u, product) => new LqInventoryUsageListOutput | |
| 359 | 346 | { |
| 360 | - id = usage.Id, | |
| 361 | - productId = usage.ProductId, | |
| 347 | + id = u.Id, | |
| 348 | + productId = u.ProductId, | |
| 362 | 349 | productName = product.ProductName, |
| 363 | 350 | productCategory = product.ProductCategory, |
| 364 | 351 | productPrice = product.Price, |
| 365 | - storeId = usage.StoreId, | |
| 366 | - storeName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(u => u.Id == usage.StoreId).Select(u => u.Dm), | |
| 367 | - usageTime = usage.UsageTime, | |
| 368 | - usageQuantity = usage.UsageQuantity, | |
| 369 | - relatedConsumeId = usage.RelatedConsumeId, | |
| 370 | - usageBatchId = usage.UsageBatchId, | |
| 371 | - createUser = usage.CreateUser, | |
| 352 | + storeId = u.StoreId, | |
| 353 | + storeName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(store => store.Id == u.StoreId).Select(store => store.Dm), | |
| 354 | + usageTime = u.UsageTime, | |
| 355 | + usageQuantity = u.UsageQuantity, | |
| 356 | + relatedConsumeId = u.RelatedConsumeId, | |
| 357 | + usageBatchId = u.UsageBatchId, | |
| 358 | + createUser = u.CreateUser, | |
| 372 | 359 | createUserName = "", |
| 373 | - createTime = usage.CreateTime, | |
| 374 | - updateUser = usage.UpdateUser, | |
| 360 | + createTime = u.CreateTime, | |
| 361 | + updateUser = u.UpdateUser, | |
| 375 | 362 | updateUserName = "", |
| 376 | - updateTime = usage.UpdateTime, | |
| 377 | - isEffective = usage.IsEffective | |
| 363 | + updateTime = u.UpdateTime, | |
| 364 | + isEffective = u.IsEffective | |
| 378 | 365 | }) |
| 379 | 366 | .MergeTable() |
| 380 | 367 | .OrderBy(sidx + " " + input.sort) |
| ... | ... | @@ -459,30 +446,28 @@ namespace NCC.Extend |
| 459 | 446 | } |
| 460 | 447 | |
| 461 | 448 | // 查询该批次的所有使用记录 |
| 462 | - var usageRecords = await _db.Queryable<LqInventoryUsageEntity, LqProductEntity>( | |
| 463 | - (usage, product) => usage.ProductId == product.Id) | |
| 464 | - .LeftJoin<LqMdxxEntity>((usage, product, store) => usage.StoreId == store.Id) | |
| 465 | - .Where((usage, product, store) => usage.UsageBatchId == batchId) | |
| 466 | - .Select((usage, product, store) => new LqInventoryUsageListOutput | |
| 449 | + var usageRecords = await _db.Queryable<LqInventoryUsageEntity, LqProductEntity>((u, product) => u.ProductId == product.Id) | |
| 450 | + .Where((u, product) => u.UsageBatchId == batchId) | |
| 451 | + .Select((u, product) => new LqInventoryUsageListOutput | |
| 467 | 452 | { |
| 468 | - id = usage.Id, | |
| 469 | - productId = usage.ProductId, | |
| 453 | + id = u.Id, | |
| 454 | + productId = u.ProductId, | |
| 470 | 455 | productName = product.ProductName, |
| 471 | 456 | productCategory = product.ProductCategory, |
| 472 | 457 | productPrice = product.Price, |
| 473 | - storeId = usage.StoreId, | |
| 474 | - storeName = store.Dm, | |
| 475 | - usageTime = usage.UsageTime, | |
| 476 | - usageQuantity = usage.UsageQuantity, | |
| 477 | - relatedConsumeId = usage.RelatedConsumeId, | |
| 478 | - usageBatchId = usage.UsageBatchId, | |
| 479 | - createUser = usage.CreateUser, | |
| 458 | + storeId = u.StoreId, | |
| 459 | + storeName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(store => store.Id == u.StoreId).Select(store => store.Dm), | |
| 460 | + usageTime = u.UsageTime, | |
| 461 | + usageQuantity = u.UsageQuantity, | |
| 462 | + relatedConsumeId = u.RelatedConsumeId, | |
| 463 | + usageBatchId = u.UsageBatchId, | |
| 464 | + createUser = u.CreateUser, | |
| 480 | 465 | createUserName = "", |
| 481 | - createTime = usage.CreateTime, | |
| 482 | - updateUser = usage.UpdateUser, | |
| 466 | + createTime = u.CreateTime, | |
| 467 | + updateUser = u.UpdateUser, | |
| 483 | 468 | updateUserName = "", |
| 484 | - updateTime = usage.UpdateTime, | |
| 485 | - isEffective = usage.IsEffective | |
| 469 | + updateTime = u.UpdateTime, | |
| 470 | + isEffective = u.IsEffective | |
| 486 | 471 | }) |
| 487 | 472 | .MergeTable() |
| 488 | 473 | .OrderBy("createTime") |
| ... | ... | @@ -563,23 +548,23 @@ namespace NCC.Extend |
| 563 | 548 | try |
| 564 | 549 | { |
| 565 | 550 | var data = await _db.Queryable<LqInventoryUsageEntity>() |
| 566 | - .LeftJoin<LqProductEntity>((usage, product) => usage.ProductId == product.Id) | |
| 567 | - .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) | |
| 568 | - .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 569 | - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) | |
| 570 | - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId) | |
| 571 | - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product) => product.ProductCategory == input.ProductCategory) | |
| 572 | - .GroupBy((usage, product) => new { usage.ProductId, product.ProductName, product.ProductCategory, product.Price }) | |
| 573 | - .Select((usage, product) => new ProductUsageStatisticsOutput | |
| 551 | + .LeftJoin<LqProductEntity>((u, product) => u.ProductId == product.Id) | |
| 552 | + .Where((u, product) => u.UsageTime >= input.StartTime && u.UsageTime <= input.EndTime) | |
| 553 | + .Where((u, product) => u.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 554 | + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product) => u.ProductId == input.ProductId) | |
| 555 | + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product) => u.StoreId == input.StoreId) | |
| 556 | + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (u, product) => product.ProductCategory == input.ProductCategory) | |
| 557 | + .GroupBy((u, product) => new { u.ProductId, product.ProductName, product.ProductCategory, product.Price }) | |
| 558 | + .Select((u, product) => new ProductUsageStatisticsOutput | |
| 574 | 559 | { |
| 575 | - ProductId = usage.ProductId, | |
| 560 | + ProductId = u.ProductId, | |
| 576 | 561 | ProductName = product.ProductName, |
| 577 | 562 | ProductCategory = product.ProductCategory, |
| 578 | 563 | ProductPrice = product.Price, |
| 579 | - TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity), | |
| 580 | - TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price), | |
| 581 | - UsageCount = SqlFunc.AggregateCount(usage.Id), | |
| 582 | - AverageUsageQuantity = SqlFunc.AggregateAvg(usage.UsageQuantity) | |
| 564 | + TotalUsageQuantity = SqlFunc.AggregateSum(u.UsageQuantity), | |
| 565 | + TotalUsageAmount = SqlFunc.AggregateSum(u.UsageQuantity * product.Price), | |
| 566 | + UsageCount = SqlFunc.AggregateCount(u.Id), | |
| 567 | + AverageUsageQuantity = SqlFunc.AggregateAvg(u.UsageQuantity) | |
| 583 | 568 | }) |
| 584 | 569 | .ToListAsync(); |
| 585 | 570 | |
| ... | ... | @@ -611,22 +596,22 @@ namespace NCC.Extend |
| 611 | 596 | try |
| 612 | 597 | { |
| 613 | 598 | var data = await _db.Queryable<LqInventoryUsageEntity>() |
| 614 | - .LeftJoin<LqProductEntity>((usage, product) => usage.ProductId == product.Id) | |
| 615 | - .LeftJoin<LqMdxxEntity>((usage, product, store) => usage.StoreId == store.Id) | |
| 616 | - .Where((usage, product, store) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) | |
| 617 | - .Where((usage, product, store) => usage.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 618 | - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product, store) => usage.ProductId == input.ProductId) | |
| 619 | - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product, store) => usage.StoreId == input.StoreId) | |
| 620 | - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product, store) => product.ProductCategory == input.ProductCategory) | |
| 621 | - .GroupBy((usage, product, store) => new { usage.StoreId, store.Dm }) | |
| 622 | - .Select((usage, product, store) => new StoreUsageStatisticsOutput | |
| 599 | + .LeftJoin<LqProductEntity>((u, product) => u.ProductId == product.Id) | |
| 600 | + .LeftJoin<LqMdxxEntity>((u, product, store) => u.StoreId == store.Id) | |
| 601 | + .Where((u, product, store) => u.UsageTime >= input.StartTime && u.UsageTime <= input.EndTime) | |
| 602 | + .Where((u, product, store) => u.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 603 | + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product, store) => u.ProductId == input.ProductId) | |
| 604 | + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product, store) => u.StoreId == input.StoreId) | |
| 605 | + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (u, product, store) => product.ProductCategory == input.ProductCategory) | |
| 606 | + .GroupBy((u, product, store) => new { u.StoreId, store.Dm }) | |
| 607 | + .Select((u, product, store) => new StoreUsageStatisticsOutput | |
| 623 | 608 | { |
| 624 | - StoreId = usage.StoreId, | |
| 609 | + StoreId = u.StoreId, | |
| 625 | 610 | StoreName = store.Dm, |
| 626 | - TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity), | |
| 627 | - TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price), | |
| 628 | - UsageCount = SqlFunc.AggregateCount(usage.Id), | |
| 629 | - ProductVarietyCount = SqlFunc.AggregateCount(usage.ProductId) | |
| 611 | + TotalUsageQuantity = SqlFunc.AggregateSum(u.UsageQuantity), | |
| 612 | + TotalUsageAmount = SqlFunc.AggregateSum(u.UsageQuantity * product.Price), | |
| 613 | + UsageCount = SqlFunc.AggregateCount(u.Id), | |
| 614 | + ProductVarietyCount = SqlFunc.AggregateCount(u.ProductId) | |
| 630 | 615 | }) |
| 631 | 616 | .ToListAsync(); |
| 632 | 617 | |
| ... | ... | @@ -659,18 +644,18 @@ namespace NCC.Extend |
| 659 | 644 | { |
| 660 | 645 | // 先获取基础数据 |
| 661 | 646 | var baseData = await _db.Queryable<LqInventoryUsageEntity>() |
| 662 | - .LeftJoin<LqProductEntity>((usage, product) => usage.ProductId == product.Id) | |
| 663 | - .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) | |
| 664 | - .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 665 | - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) | |
| 666 | - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId) | |
| 667 | - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product) => product.ProductCategory == input.ProductCategory) | |
| 668 | - .Select((usage, product) => new | |
| 647 | + .LeftJoin<LqProductEntity>((u, product) => u.ProductId == product.Id) | |
| 648 | + .Where((u, product) => u.UsageTime >= input.StartTime && u.UsageTime <= input.EndTime) | |
| 649 | + .Where((u, product) => u.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 650 | + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product) => u.ProductId == input.ProductId) | |
| 651 | + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product) => u.StoreId == input.StoreId) | |
| 652 | + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (u, product) => product.ProductCategory == input.ProductCategory) | |
| 653 | + .Select((u, product) => new | |
| 669 | 654 | { |
| 670 | - UsageTime = usage.UsageTime, | |
| 671 | - UsageQuantity = usage.UsageQuantity, | |
| 672 | - UsageAmount = usage.UsageQuantity * product.Price, | |
| 673 | - ProductId = usage.ProductId | |
| 655 | + UsageTime = u.UsageTime, | |
| 656 | + UsageQuantity = u.UsageQuantity, | |
| 657 | + UsageAmount = u.UsageQuantity * product.Price, | |
| 658 | + ProductId = u.ProductId | |
| 674 | 659 | }) |
| 675 | 660 | .ToListAsync(); |
| 676 | 661 | |
| ... | ... | @@ -740,22 +725,22 @@ namespace NCC.Extend |
| 740 | 725 | try |
| 741 | 726 | { |
| 742 | 727 | var data = await _db.Queryable<LqInventoryUsageEntity>() |
| 743 | - .LeftJoin<LqProductEntity>((usage, product) => usage.ProductId == product.Id) | |
| 744 | - .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) | |
| 745 | - .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 746 | - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) | |
| 747 | - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId) | |
| 748 | - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product) => product.ProductCategory == input.ProductCategory) | |
| 749 | - .GroupBy((usage, product) => new { usage.ProductId, product.ProductName, product.ProductCategory }) | |
| 750 | - .Select((usage, product) => new ProductUsageRankingOutput | |
| 728 | + .LeftJoin<LqProductEntity>((u, product) => u.ProductId == product.Id) | |
| 729 | + .Where((u, product) => u.UsageTime >= input.StartTime && u.UsageTime <= input.EndTime) | |
| 730 | + .Where((u, product) => u.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 731 | + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (u, product) => u.ProductId == input.ProductId) | |
| 732 | + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (u, product) => u.StoreId == input.StoreId) | |
| 733 | + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (u, product) => product.ProductCategory == input.ProductCategory) | |
| 734 | + .GroupBy((u, product) => new { u.ProductId, product.ProductName, product.ProductCategory }) | |
| 735 | + .Select((u, product) => new ProductUsageRankingOutput | |
| 751 | 736 | { |
| 752 | - ProductId = usage.ProductId, | |
| 737 | + ProductId = u.ProductId, | |
| 753 | 738 | ProductName = product.ProductName, |
| 754 | 739 | ProductCategory = product.ProductCategory, |
| 755 | - TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity), | |
| 756 | - TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price), | |
| 757 | - UsageCount = SqlFunc.AggregateCount(usage.Id), | |
| 758 | - StoreCount = SqlFunc.AggregateCount(usage.StoreId) | |
| 740 | + TotalUsageQuantity = SqlFunc.AggregateSum(u.UsageQuantity), | |
| 741 | + TotalUsageAmount = SqlFunc.AggregateSum(u.UsageQuantity * product.Price), | |
| 742 | + UsageCount = SqlFunc.AggregateCount(u.Id), | |
| 743 | + StoreCount = SqlFunc.AggregateCount(u.StoreId) | |
| 759 | 744 | }) |
| 760 | 745 | .OrderBy("TotalUsageQuantity desc") |
| 761 | 746 | .Take(input.RankingCount) | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
| ... | ... | @@ -2549,7 +2549,8 @@ namespace NCC.Extend.LqKdKdjlb |
| 2549 | 2549 | totalPrice = it.TotalPrice, |
| 2550 | 2550 | actualPrice = it.ActualPrice, |
| 2551 | 2551 | projectNumber = it.ProjectNumber, |
| 2552 | - remark = it.Remark | |
| 2552 | + remark = it.Remark, | |
| 2553 | + itemCategory = it.ItemCategory, | |
| 2553 | 2554 | }) |
| 2554 | 2555 | .ToListAsync(); |
| 2555 | 2556 | |
| ... | ... | @@ -2596,7 +2597,8 @@ namespace NCC.Extend.LqKdKdjlb |
| 2596 | 2597 | totalPrice = x.totalPrice, |
| 2597 | 2598 | actualPrice = x.actualPrice, |
| 2598 | 2599 | projectNumber = x.projectNumber, |
| 2599 | - remark = x.remark | |
| 2600 | + remark = x.remark, | |
| 2601 | + itemCategory = x.itemCategory, | |
| 2600 | 2602 | }).ToList(), |
| 2601 | 2603 | |
| 2602 | 2604 | giftedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "赠送").Select(x => new |
| ... | ... | @@ -2608,7 +2610,8 @@ namespace NCC.Extend.LqKdKdjlb |
| 2608 | 2610 | totalPrice = x.totalPrice, |
| 2609 | 2611 | actualPrice = x.actualPrice, |
| 2610 | 2612 | projectNumber = x.projectNumber, |
| 2611 | - remark = x.remark | |
| 2613 | + remark = x.remark, | |
| 2614 | + itemCategory = x.itemCategory, | |
| 2612 | 2615 | }).ToList(), |
| 2613 | 2616 | |
| 2614 | 2617 | experienceItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "体验").Select(x => new |
| ... | ... | @@ -2620,7 +2623,8 @@ namespace NCC.Extend.LqKdKdjlb |
| 2620 | 2623 | totalPrice = x.totalPrice, |
| 2621 | 2624 | actualPrice = x.actualPrice, |
| 2622 | 2625 | projectNumber = x.projectNumber, |
| 2623 | - remark = x.remark | |
| 2626 | + remark = x.remark, | |
| 2627 | + itemCategory = x.itemCategory, | |
| 2624 | 2628 | }).ToList(), |
| 2625 | 2629 | |
| 2626 | 2630 | // 金额信息 |
| ... | ... | @@ -2670,7 +2674,6 @@ namespace NCC.Extend.LqKdKdjlb |
| 2670 | 2674 | }, |
| 2671 | 2675 | message = "获取开单记录汇总信息成功" |
| 2672 | 2676 | }; |
| 2673 | - | |
| 2674 | 2677 | return result; |
| 2675 | 2678 | } |
| 2676 | 2679 | catch (Exception ex) |
| ... | ... | @@ -3210,8 +3213,10 @@ namespace NCC.Extend.LqKdKdjlb |
| 3210 | 3213 | |
| 3211 | 3214 | -- 消耗相关统计 |
| 3212 | 3215 | COALESCE(consume_stats.ConsumeAmount, 0) as ConsumeAmount, |
| 3213 | - COALESCE(consume_stats.HeadCount, 0) as HeadCount, | |
| 3214 | - COALESCE(consume_stats.PersonCount, 0) as PersonCount, | |
| 3216 | + CAST(COALESCE(headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as HeadCount, | |
| 3217 | + CAST(COALESCE(personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as PersonCount, | |
| 3218 | + CAST(COALESCE(invalid_headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as InvalidHeadCount, | |
| 3219 | + CAST(COALESCE(invalid_personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as InvalidPersonCount, | |
| 3215 | 3220 | CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount |
| 3216 | 3221 | |
| 3217 | 3222 | FROM BASE_USER u |
| ... | ... | @@ -3274,8 +3279,6 @@ namespace NCC.Extend.LqKdKdjlb |
| 3274 | 3279 | SELECT |
| 3275 | 3280 | jksyj.jkszh as EmployeeId, |
| 3276 | 3281 | SUM(jksyj.jksyj) as ConsumeAmount, |
| 3277 | - COUNT(DISTINCT hyhk.hy) as HeadCount, | |
| 3278 | - COUNT(DISTINCT CONCAT(jksyj.jkszh, '_', hyhk.hy, '_', DATE(hyhk.hksj))) as PersonCount, | |
| 3279 | 3282 | CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount |
| 3280 | 3283 | FROM lq_xh_jksyj jksyj |
| 3281 | 3284 | INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id |
| ... | ... | @@ -3287,6 +3290,98 @@ namespace NCC.Extend.LqKdKdjlb |
| 3287 | 3290 | GROUP BY jksyj.jkszh |
| 3288 | 3291 | ) consume_stats ON u.F_Id = consume_stats.EmployeeId |
| 3289 | 3292 | |
| 3293 | + -- 有效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=1) | |
| 3294 | + LEFT JOIN ( | |
| 3295 | + SELECT | |
| 3296 | + F_PersonId as EmployeeId, | |
| 3297 | + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount | |
| 3298 | + FROM ( | |
| 3299 | + SELECT | |
| 3300 | + F_PersonId, | |
| 3301 | + F_WorkMonth, | |
| 3302 | + F_MemberId, | |
| 3303 | + F_Quantity | |
| 3304 | + FROM lq_person_times_record | |
| 3305 | + WHERE F_PersonId IS NOT NULL | |
| 3306 | + AND F_IsEffective = 1 | |
| 3307 | + AND F_PersonType = '健康师' | |
| 3308 | + AND F_HasBilling = 1 | |
| 3309 | + AND F_WorkDate >= DATE(@startTime) | |
| 3310 | + AND F_WorkDate <= DATE(@endTime) | |
| 3311 | + GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity | |
| 3312 | + ) as distinct_headcount | |
| 3313 | + GROUP BY F_PersonId | |
| 3314 | + ) headcount_stats ON u.F_Id = headcount_stats.EmployeeId | |
| 3315 | + | |
| 3316 | + -- 有效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=1) | |
| 3317 | + LEFT JOIN ( | |
| 3318 | + SELECT | |
| 3319 | + F_PersonId as EmployeeId, | |
| 3320 | + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount | |
| 3321 | + FROM ( | |
| 3322 | + SELECT | |
| 3323 | + F_PersonId, | |
| 3324 | + F_WorkDate, | |
| 3325 | + F_MemberId, | |
| 3326 | + F_Quantity | |
| 3327 | + FROM lq_person_times_record | |
| 3328 | + WHERE F_PersonId IS NOT NULL | |
| 3329 | + AND F_IsEffective = 1 | |
| 3330 | + AND F_PersonType = '健康师' | |
| 3331 | + AND F_HasBilling = 1 | |
| 3332 | + AND F_WorkDate >= DATE(@startTime) | |
| 3333 | + AND F_WorkDate <= DATE(@endTime) | |
| 3334 | + GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity | |
| 3335 | + ) as distinct_personcount | |
| 3336 | + GROUP BY F_PersonId | |
| 3337 | + ) personcount_stats ON u.F_Id = personcount_stats.EmployeeId | |
| 3338 | + | |
| 3339 | + -- 无效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=0) | |
| 3340 | + LEFT JOIN ( | |
| 3341 | + SELECT | |
| 3342 | + F_PersonId as EmployeeId, | |
| 3343 | + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount | |
| 3344 | + FROM ( | |
| 3345 | + SELECT | |
| 3346 | + F_PersonId, | |
| 3347 | + F_WorkMonth, | |
| 3348 | + F_MemberId, | |
| 3349 | + F_Quantity | |
| 3350 | + FROM lq_person_times_record | |
| 3351 | + WHERE F_PersonId IS NOT NULL | |
| 3352 | + AND F_IsEffective = 1 | |
| 3353 | + AND F_PersonType = '健康师' | |
| 3354 | + AND F_HasBilling = 0 | |
| 3355 | + AND F_WorkDate >= DATE(@startTime) | |
| 3356 | + AND F_WorkDate <= DATE(@endTime) | |
| 3357 | + GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity | |
| 3358 | + ) as distinct_headcount | |
| 3359 | + GROUP BY F_PersonId | |
| 3360 | + ) invalid_headcount_stats ON u.F_Id = invalid_headcount_stats.EmployeeId | |
| 3361 | + | |
| 3362 | + -- 无效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=0) | |
| 3363 | + LEFT JOIN ( | |
| 3364 | + SELECT | |
| 3365 | + F_PersonId as EmployeeId, | |
| 3366 | + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount | |
| 3367 | + FROM ( | |
| 3368 | + SELECT | |
| 3369 | + F_PersonId, | |
| 3370 | + F_WorkDate, | |
| 3371 | + F_MemberId, | |
| 3372 | + F_Quantity | |
| 3373 | + FROM lq_person_times_record | |
| 3374 | + WHERE F_PersonId IS NOT NULL | |
| 3375 | + AND F_IsEffective = 1 | |
| 3376 | + AND F_PersonType = '健康师' | |
| 3377 | + AND F_HasBilling = 0 | |
| 3378 | + AND F_WorkDate >= DATE(@startTime) | |
| 3379 | + AND F_WorkDate <= DATE(@endTime) | |
| 3380 | + GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity | |
| 3381 | + ) as distinct_personcount | |
| 3382 | + GROUP BY F_PersonId | |
| 3383 | + ) invalid_personcount_stats ON u.F_Id = invalid_personcount_stats.EmployeeId | |
| 3384 | + | |
| 3290 | 3385 | WHERE u.F_GW = '健康师' |
| 3291 | 3386 | "; |
| 3292 | 3387 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
| ... | ... | @@ -1595,7 +1595,7 @@ namespace NCC.Extend.LqKhxx |
| 1595 | 1595 | } |
| 1596 | 1596 | |
| 1597 | 1597 | /// <summary> |
| 1598 | - /// 批量更新所有会员信息(高性能版:使用SQL批量更新) | |
| 1598 | + /// 批量更新所有会员信息(高性能版:使用SQL批量更新)【通过定时任务去执行,每天晚上执行一次】 | |
| 1599 | 1599 | /// </summary> |
| 1600 | 1600 | /// <returns></returns> |
| 1601 | 1601 | [HttpPost("BatchUpdateMemberInfo")] | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs
| ... | ... | @@ -228,7 +228,7 @@ namespace NCC.Extend |
| 228 | 228 | price = x.Price, |
| 229 | 229 | productCategory = x.ProductCategory, |
| 230 | 230 | departmentId = x.DepartmentId, |
| 231 | - departmentName = "", | |
| 231 | + departmentName = SqlFunc.Subqueryable<OrganizeEntity>().Where(y => y.Id == x.DepartmentId).Select(y => y.FullName), | |
| 232 | 232 | standardUnit = x.StandardUnit, |
| 233 | 233 | onShelfStatus = x.OnShelfStatus, |
| 234 | 234 | statisticsCategory = x.StatisticsCategory, | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs
| ... | ... | @@ -28,7 +28,7 @@ namespace NCC.Extend.LqPurchaseRecords |
| 28 | 28 | /// <summary> |
| 29 | 29 | /// 购买记录表服务 |
| 30 | 30 | /// </summary> |
| 31 | - [ApiDescriptionSettings(Tag = "Extend",Name = "LqPurchaseRecords", Order = 200)] | |
| 31 | + [ApiDescriptionSettings(Tag = "Extend", Name = "LqPurchaseRecords", Order = 200)] | |
| 32 | 32 | [Route("api/Extend/[controller]")] |
| 33 | 33 | public class LqPurchaseRecordsService : ILqPurchaseRecordsService, IDynamicApiController, ITransient |
| 34 | 34 | { |
| ... | ... | @@ -43,7 +43,7 @@ namespace NCC.Extend.LqPurchaseRecords |
| 43 | 43 | ISqlSugarRepository<LqPurchaseRecordsEntity> lqPurchaseRecordsRepository, |
| 44 | 44 | IUserManager userManager) |
| 45 | 45 | { |
| 46 | - _lqPurchaseRecordsRepository = lqPurchaseRecordsRepository; | |
| 46 | + _lqPurchaseRecordsRepository = lqPurchaseRecordsRepository; | |
| 47 | 47 | _db = _lqPurchaseRecordsRepository.Context; |
| 48 | 48 | _userManager = userManager; |
| 49 | 49 | } |
| ... | ... | @@ -98,25 +98,25 @@ namespace NCC.Extend.LqPurchaseRecords |
| 98 | 98 | .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0)) |
| 99 | 99 | .WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59)) |
| 100 | 100 | .WhereIF(!string.IsNullOrEmpty(input.applicationId), p => p.ApplicationId.Contains(input.applicationId)) |
| 101 | - .Select(it=> new LqPurchaseRecordsListOutput | |
| 101 | + .Select(it => new LqPurchaseRecordsListOutput | |
| 102 | 102 | { |
| 103 | 103 | id = it.Id, |
| 104 | - reimbursementCategoryId=it.ReimbursementCategoryId, | |
| 105 | - reimbursementCategoryName=it.ReimbursementCategoryName, | |
| 106 | - unitPrice=it.UnitPrice, | |
| 107 | - quantity=it.Quantity, | |
| 108 | - amount=it.Amount, | |
| 109 | - memo=it.Memo, | |
| 110 | - purchaseTime=it.PurchaseTime, | |
| 111 | - createTime=it.CreateTime, | |
| 112 | - createUser=it.CreateUser, | |
| 113 | - createUserStoreId=it.CreateUserStoreId, | |
| 114 | - approveStatus=it.ApproveStatus, | |
| 115 | - approveUser=it.ApproveUser, | |
| 116 | - approveTime=it.ApproveTime, | |
| 117 | - applicationId=it.ApplicationId, | |
| 118 | - }).MergeTable().OrderBy(sidx+" "+input.sort).ToPagedListAsync(input.currentPage, input.pageSize); | |
| 119 | - return PageResult<LqPurchaseRecordsListOutput>.SqlSugarPageResult(data); | |
| 104 | + reimbursementCategoryId = it.ReimbursementCategoryId, | |
| 105 | + reimbursementCategoryName = it.ReimbursementCategoryName, | |
| 106 | + unitPrice = it.UnitPrice, | |
| 107 | + quantity = it.Quantity, | |
| 108 | + amount = it.Amount, | |
| 109 | + memo = it.Memo, | |
| 110 | + purchaseTime = it.PurchaseTime, | |
| 111 | + createTime = it.CreateTime, | |
| 112 | + createUser = it.CreateUser, | |
| 113 | + createUserStoreId = it.CreateUserStoreId, | |
| 114 | + approveStatus = it.ApproveStatus, | |
| 115 | + approveUser = it.ApproveUser, | |
| 116 | + approveTime = it.ApproveTime, | |
| 117 | + applicationId = it.ApplicationId, | |
| 118 | + }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize); | |
| 119 | + return PageResult<LqPurchaseRecordsListOutput>.SqlSugarPageResult(data); | |
| 120 | 120 | } |
| 121 | 121 | |
| 122 | 122 | /// <summary> |
| ... | ... | @@ -171,25 +171,25 @@ namespace NCC.Extend.LqPurchaseRecords |
| 171 | 171 | .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0)) |
| 172 | 172 | .WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59)) |
| 173 | 173 | .WhereIF(!string.IsNullOrEmpty(input.applicationId), p => p.ApplicationId.Contains(input.applicationId)) |
| 174 | - .Select(it=> new LqPurchaseRecordsListOutput | |
| 174 | + .Select(it => new LqPurchaseRecordsListOutput | |
| 175 | 175 | { |
| 176 | 176 | id = it.Id, |
| 177 | - reimbursementCategoryId=it.ReimbursementCategoryId, | |
| 178 | - reimbursementCategoryName=it.ReimbursementCategoryName, | |
| 179 | - unitPrice=it.UnitPrice, | |
| 180 | - quantity=it.Quantity, | |
| 181 | - amount=it.Amount, | |
| 182 | - memo=it.Memo, | |
| 183 | - purchaseTime=it.PurchaseTime, | |
| 184 | - createTime=it.CreateTime, | |
| 185 | - createUser=it.CreateUser, | |
| 186 | - createUserStoreId=it.CreateUserStoreId, | |
| 187 | - approveStatus=it.ApproveStatus, | |
| 188 | - approveUser=it.ApproveUser, | |
| 189 | - approveTime=it.ApproveTime, | |
| 190 | - applicationId=it.ApplicationId, | |
| 191 | - }).MergeTable().OrderBy(sidx+" "+input.sort).ToListAsync(); | |
| 192 | - return data; | |
| 177 | + reimbursementCategoryId = it.ReimbursementCategoryId, | |
| 178 | + reimbursementCategoryName = it.ReimbursementCategoryName, | |
| 179 | + unitPrice = it.UnitPrice, | |
| 180 | + quantity = it.Quantity, | |
| 181 | + amount = it.Amount, | |
| 182 | + memo = it.Memo, | |
| 183 | + purchaseTime = it.PurchaseTime, | |
| 184 | + createTime = it.CreateTime, | |
| 185 | + createUser = it.CreateUser, | |
| 186 | + createUserStoreId = it.CreateUserStoreId, | |
| 187 | + approveStatus = it.ApproveStatus, | |
| 188 | + approveUser = it.ApproveUser, | |
| 189 | + approveTime = it.ApproveTime, | |
| 190 | + applicationId = it.ApplicationId, | |
| 191 | + }).MergeTable().OrderBy(sidx + " " + input.sort).ToListAsync(); | |
| 192 | + return data; | |
| 193 | 193 | } |
| 194 | 194 | |
| 195 | 195 | /// <summary> |
| ... | ... | @@ -211,7 +211,7 @@ namespace NCC.Extend.LqPurchaseRecords |
| 211 | 211 | { |
| 212 | 212 | exportData = await this.GetNoPagingList(input); |
| 213 | 213 | } |
| 214 | - List<ParamsModel> 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<ParamsModel>(); | |
| 214 | + List<ParamsModel> 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<ParamsModel>(); | |
| 215 | 215 | ExcelConfig excelconfig = new ExcelConfig(); |
| 216 | 216 | excelconfig.FileName = "购买记录表.xls"; |
| 217 | 217 | excelconfig.HeadFont = "微软雅黑"; |
| ... | ... | @@ -254,7 +254,7 @@ namespace NCC.Extend.LqPurchaseRecords |
| 254 | 254 | //开启事务 |
| 255 | 255 | _db.BeginTran(); |
| 256 | 256 | //批量删除购买记录表 |
| 257 | - await _db.Deleteable<LqPurchaseRecordsEntity>().In(d => d.Id,ids).ExecuteCommandAsync(); | |
| 257 | + await _db.Deleteable<LqPurchaseRecordsEntity>().In(d => d.Id, ids).ExecuteCommandAsync(); | |
| 258 | 258 | //关闭事务 |
| 259 | 259 | _db.CommitTran(); |
| 260 | 260 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
| ... | ... | @@ -53,6 +53,9 @@ using SqlSugar; |
| 53 | 53 | using Yitter.IdGenerator; |
| 54 | 54 | using NCC.Extend.Entitys.lq_kd_pxmx; |
| 55 | 55 | using NCC.Extend.Entitys.lq_khxx; |
| 56 | +using NCC.Extend.Entitys.lq_tkjlb; | |
| 57 | +using NCC.Extend.Entitys.lq_yaoyjl; | |
| 58 | +using NCC.Extend.Entitys.lq_yyjl; | |
| 56 | 59 | |
| 57 | 60 | namespace NCC.Extend.LqStatistics |
| 58 | 61 | { |
| ... | ... | @@ -3504,6 +3507,7 @@ namespace NCC.Extend.LqStatistics |
| 3504 | 3507 | AND F_PersonType = '健康师' |
| 3505 | 3508 | AND F_WorkMonth = '{month}' |
| 3506 | 3509 | AND F_IsEffective = 1 |
| 3510 | + AND F_HasBilling = 1 | |
| 3507 | 3511 | GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity |
| 3508 | 3512 | ) as distinct_records"; |
| 3509 | 3513 | |
| ... | ... | @@ -3531,7 +3535,8 @@ namespace NCC.Extend.LqStatistics |
| 3531 | 3535 | WHERE F_PersonId = '{userId}' |
| 3532 | 3536 | AND F_PersonType = '健康师' |
| 3533 | 3537 | AND F_WorkMonth = '{month}' |
| 3534 | - AND F_IsEffective = 1 | |
| 3538 | + AND F_IsEffective = 1 | |
| 3539 | + AND F_HasBilling = 1 | |
| 3535 | 3540 | GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity |
| 3536 | 3541 | ) as distinct_records"; |
| 3537 | 3542 | |
| ... | ... | @@ -3947,6 +3952,754 @@ namespace NCC.Extend.LqStatistics |
| 3947 | 3952 | } |
| 3948 | 3953 | #endregion |
| 3949 | 3954 | |
| 3955 | + #region 线索池客户统计报表 | |
| 3956 | + /// <summary> | |
| 3957 | + /// 获取线索池客户统计报表 | |
| 3958 | + /// </summary> | |
| 3959 | + /// <remarks> | |
| 3960 | + /// 根据拓客记录统计线索池客户的邀约、预约、消耗、开单等信息 | |
| 3961 | + /// | |
| 3962 | + /// 业务链路:拓客 -> 邀约 -> 预约 -> 开单/消耗 | |
| 3963 | + /// | |
| 3964 | + /// 示例请求: | |
| 3965 | + /// ```json | |
| 3966 | + /// { | |
| 3967 | + /// "pageIndex": 1, | |
| 3968 | + /// "pageSize": 20, | |
| 3969 | + /// "startTime": "2025-10-01T00:00:00", | |
| 3970 | + /// "endTime": "2025-10-31T23:59:59", | |
| 3971 | + /// "storeIds": ["store1", "store2"], | |
| 3972 | + /// "eventId": "event123" | |
| 3973 | + /// } | |
| 3974 | + /// ``` | |
| 3975 | + /// | |
| 3976 | + /// 参数说明: | |
| 3977 | + /// - pageIndex: 页码,从1开始 | |
| 3978 | + /// - pageSize: 每页数量 | |
| 3979 | + /// - startTime: 拓客时间范围开始时间 | |
| 3980 | + /// - endTime: 拓客时间范围结束时间 | |
| 3981 | + /// - storeIds: 门店ID列表,可传多个 | |
| 3982 | + /// - eventId: 拓客活动ID | |
| 3983 | + /// | |
| 3984 | + /// 返回数据说明: | |
| 3985 | + /// - LeadCustomerId: 线索池客户(拓客编号) | |
| 3986 | + /// - CustomerName: 客户姓名 | |
| 3987 | + /// - ExpansionTime: 拓客时间 | |
| 3988 | + /// - HasInvite: 是否邀约(是/否),通过拓客编号关联邀约表 | |
| 3989 | + /// - HasAppointment: 是否预约(是/否),只统计通过邀约产生的预约(预约表的F_InviteId关联邀约表) | |
| 3990 | + /// - HasConsume: 是否有消耗(是/否),只统计通过预约产生的耗卡(耗卡表的F_AppointmentId关联预约表) | |
| 3991 | + /// - HasBilling: 是否开单(是/否),只统计通过预约产生的开单(开单表的F_AppointmentId关联预约表) | |
| 3992 | + /// - NoBillingReason: 未开单原因,从预约记录的F_NoDealRemark字段获取 | |
| 3993 | + /// - BillingAmount: 开卡金额,汇总通过预约产生的开单记录的整单业绩(zdyj) | |
| 3994 | + /// - BillingItems: 开卡卡项,汇总通过预约产生的开单品项名称,多个用顿号分隔 | |
| 3995 | + /// - ActualAppointmentCount: 实际预约记录数(不管是否通过邀约产生),用于问题分析 | |
| 3996 | + /// - ActualConsumeCount: 实际消耗记录数(不管是否通过预约产生),用于问题分析 | |
| 3997 | + /// - ActualBillingCount: 实际开单记录数(不管是否通过预约产生),用于问题分析 | |
| 3998 | + /// - Analysis: 问题分析说明,自动分析数据异常情况,如:有预约记录但未通过邀约产生、有消耗记录但未通过预约产生等 | |
| 3999 | + /// | |
| 4000 | + /// 返回示例: | |
| 4001 | + /// ```json | |
| 4002 | + /// { | |
| 4003 | + /// "list": [ | |
| 4004 | + /// { | |
| 4005 | + /// "LeadCustomerId": "751248448816153862", | |
| 4006 | + /// "CustomerName": "王女士", | |
| 4007 | + /// "ExpansionTime": "2025-10-24T03:33:10.000Z", | |
| 4008 | + /// "HasInvite": "否", | |
| 4009 | + /// "HasAppointment": "否", | |
| 4010 | + /// "HasConsume": "否", | |
| 4011 | + /// "HasBilling": "否", | |
| 4012 | + /// "NoBillingReason": null, | |
| 4013 | + /// "BillingAmount": 0, | |
| 4014 | + /// "BillingItems": null, | |
| 4015 | + /// "ActualAppointmentCount": 3, | |
| 4016 | + /// "ActualConsumeCount": 4, | |
| 4017 | + /// "ActualBillingCount": 5, | |
| 4018 | + /// "Analysis": "有3条预约记录,但未通过邀约产生(F_InviteId为null);有4条消耗记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生);有5条开单记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)" | |
| 4019 | + /// } | |
| 4020 | + /// ], | |
| 4021 | + /// "pagination": { | |
| 4022 | + /// "pageIndex": 1, | |
| 4023 | + /// "pageSize": 20, | |
| 4024 | + /// "total": 1511 | |
| 4025 | + /// } | |
| 4026 | + /// } | |
| 4027 | + /// ``` | |
| 4028 | + /// </remarks> | |
| 4029 | + /// <param name="input">查询条件</param> | |
| 4030 | + /// <returns>线索池客户统计报表列表,包含统计数据和问题分析</returns> | |
| 4031 | + /// <response code="200">查询成功,返回统计报表列表和分页信息</response> | |
| 4032 | + /// <response code="400">参数错误</response> | |
| 4033 | + /// <response code="500">服务器内部错误</response> | |
| 4034 | + [HttpPost("get-lead-customer-statistics-list")] | |
| 4035 | + public async Task<dynamic> GetLeadCustomerStatisticsList([FromBody] LeadCustomerStatisticsListQueryInput input) | |
| 4036 | + { | |
| 4037 | + try | |
| 4038 | + { | |
| 4039 | + // 构建WHERE条件 | |
| 4040 | + var whereConditions = new List<string>(); | |
| 4041 | + var parameters = new List<SugarParameter>(); | |
| 4042 | + | |
| 4043 | + if (input.StartTime.HasValue) | |
| 4044 | + { | |
| 4045 | + whereConditions.Add("tk.F_ExpansionTime >= @StartTime"); | |
| 4046 | + parameters.Add(new SugarParameter("@StartTime", input.StartTime.Value)); | |
| 4047 | + } | |
| 4048 | + | |
| 4049 | + if (input.EndTime.HasValue) | |
| 4050 | + { | |
| 4051 | + whereConditions.Add("tk.F_ExpansionTime <= @EndTime"); | |
| 4052 | + parameters.Add(new SugarParameter("@EndTime", input.EndTime.Value)); | |
| 4053 | + } | |
| 4054 | + | |
| 4055 | + if (input.StoreIds != null && input.StoreIds.Any()) | |
| 4056 | + { | |
| 4057 | + var storeIdParams = string.Join(",", input.StoreIds.Select((_, i) => $"@StoreId{i}")); | |
| 4058 | + whereConditions.Add($"tk.F_StoreId IN ({storeIdParams})"); | |
| 4059 | + for (int i = 0; i < input.StoreIds.Count; i++) | |
| 4060 | + { | |
| 4061 | + parameters.Add(new SugarParameter($"@StoreId{i}", input.StoreIds[i])); | |
| 4062 | + } | |
| 4063 | + } | |
| 4064 | + | |
| 4065 | + if (!string.IsNullOrEmpty(input.EventId)) | |
| 4066 | + { | |
| 4067 | + whereConditions.Add("tk.F_EventId = @EventId"); | |
| 4068 | + parameters.Add(new SugarParameter("@EventId", input.EventId)); | |
| 4069 | + } | |
| 4070 | + | |
| 4071 | + var whereClause = whereConditions.Any() ? "WHERE " + string.Join(" AND ", whereConditions) : ""; | |
| 4072 | + | |
| 4073 | + // 使用子查询优化性能,避免复杂的JOIN和GROUP BY | |
| 4074 | + var sql = $@" | |
| 4075 | + SELECT | |
| 4076 | + tk.F_Id as LeadCustomerId, | |
| 4077 | + tk.F_CustomerName as CustomerName, | |
| 4078 | + tk.F_ExpansionTime as ExpansionTime, | |
| 4079 | + -- 是否邀约:通过拓客编号关联 | |
| 4080 | + CASE WHEN yaoy_stats.has_invite = 1 THEN '是' ELSE '否' END as HasInvite, | |
| 4081 | + -- 是否预约:通过邀约ID关联(只统计通过邀约产生的预约) | |
| 4082 | + CASE WHEN yy_stats.has_appointment = 1 THEN '是' ELSE '否' END as HasAppointment, | |
| 4083 | + -- 是否有消耗:通过预约ID关联(只统计通过预约产生的耗卡) | |
| 4084 | + CASE WHEN xh_stats.has_consume = 1 THEN '是' ELSE '否' END as HasConsume, | |
| 4085 | + -- 是否开单:通过预约ID关联(只统计通过预约产生的开单) | |
| 4086 | + CASE WHEN kd_stats.has_billing = 1 THEN '是' ELSE '否' END as HasBilling, | |
| 4087 | + -- 未开单原因:从预约记录中获取(只取通过邀约产生的预约) | |
| 4088 | + yy_stats.no_billing_reason as NoBillingReason, | |
| 4089 | + -- 开卡金额:汇总通过预约产生的开单记录 | |
| 4090 | + COALESCE(kd_stats.billing_amount, 0) as BillingAmount, | |
| 4091 | + -- 开卡卡项:汇总通过预约产生的开单品项 | |
| 4092 | + kd_stats.billing_items as BillingItems, | |
| 4093 | + -- 实际预约记录数(不管是否通过邀约产生) | |
| 4094 | + COALESCE(yy_actual.count, 0) as ActualAppointmentCount, | |
| 4095 | + -- 实际消耗记录数(不管是否通过预约产生) | |
| 4096 | + COALESCE(xh_actual.count, 0) as ActualConsumeCount, | |
| 4097 | + -- 实际开单记录数(不管是否通过预约产生) | |
| 4098 | + COALESCE(kd_actual.count, 0) as ActualBillingCount | |
| 4099 | + FROM lq_tkjlb tk | |
| 4100 | + -- 邀约统计子查询 | |
| 4101 | + LEFT JOIN ( | |
| 4102 | + SELECT | |
| 4103 | + yaoy.tkbh as tk_id, | |
| 4104 | + 1 as has_invite | |
| 4105 | + FROM lq_yaoyjl yaoy | |
| 4106 | + GROUP BY yaoy.tkbh | |
| 4107 | + ) yaoy_stats ON yaoy_stats.tk_id = tk.F_Id | |
| 4108 | + -- 预约统计子查询(只统计通过邀约产生的预约) | |
| 4109 | + LEFT JOIN ( | |
| 4110 | + SELECT | |
| 4111 | + tk_inner.F_MemberId as member_id, | |
| 4112 | + 1 as has_appointment, | |
| 4113 | + MAX(yy.F_NoDealRemark) as no_billing_reason | |
| 4114 | + FROM lq_tkjlb tk_inner | |
| 4115 | + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id | |
| 4116 | + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id | |
| 4117 | + GROUP BY tk_inner.F_MemberId | |
| 4118 | + ) yy_stats ON yy_stats.member_id = tk.F_MemberId | |
| 4119 | + -- 消耗统计子查询(只统计通过预约产生的耗卡) | |
| 4120 | + LEFT JOIN ( | |
| 4121 | + SELECT | |
| 4122 | + tk_inner.F_MemberId as member_id, | |
| 4123 | + 1 as has_consume | |
| 4124 | + FROM lq_tkjlb tk_inner | |
| 4125 | + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id | |
| 4126 | + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id | |
| 4127 | + INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1 | |
| 4128 | + GROUP BY tk_inner.F_MemberId | |
| 4129 | + ) xh_stats ON xh_stats.member_id = tk.F_MemberId | |
| 4130 | + -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项) | |
| 4131 | + LEFT JOIN ( | |
| 4132 | + SELECT | |
| 4133 | + tk_inner.F_MemberId as member_id, | |
| 4134 | + 1 as has_billing, | |
| 4135 | + SUM(kd.zdyj) as billing_amount, | |
| 4136 | + GROUP_CONCAT(DISTINCT kdpx.pxmc SEPARATOR '、') as billing_items | |
| 4137 | + FROM lq_tkjlb tk_inner | |
| 4138 | + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id | |
| 4139 | + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id | |
| 4140 | + INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.F_IsEffective = 1 | |
| 4141 | + LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1 | |
| 4142 | + GROUP BY tk_inner.F_MemberId | |
| 4143 | + ) kd_stats ON kd_stats.member_id = tk.F_MemberId | |
| 4144 | + -- 实际预约记录数统计(不管是否通过邀约产生) | |
| 4145 | + LEFT JOIN ( | |
| 4146 | + SELECT | |
| 4147 | + yy.gk as member_id, | |
| 4148 | + COUNT(*) as count | |
| 4149 | + FROM lq_yyjl yy | |
| 4150 | + GROUP BY yy.gk | |
| 4151 | + ) yy_actual ON yy_actual.member_id = tk.F_MemberId | |
| 4152 | + -- 实际消耗记录数统计(不管是否通过预约产生) | |
| 4153 | + LEFT JOIN ( | |
| 4154 | + SELECT | |
| 4155 | + xh.hy as member_id, | |
| 4156 | + COUNT(*) as count | |
| 4157 | + FROM lq_xh_hyhk xh | |
| 4158 | + WHERE xh.F_IsEffective = 1 | |
| 4159 | + GROUP BY xh.hy | |
| 4160 | + ) xh_actual ON xh_actual.member_id = tk.F_MemberId | |
| 4161 | + -- 实际开单记录数统计(不管是否通过预约产生) | |
| 4162 | + LEFT JOIN ( | |
| 4163 | + SELECT | |
| 4164 | + kd.kdhy as member_id, | |
| 4165 | + COUNT(*) as count | |
| 4166 | + FROM lq_kd_kdjlb kd | |
| 4167 | + WHERE kd.F_IsEffective = 1 | |
| 4168 | + GROUP BY kd.kdhy | |
| 4169 | + ) kd_actual ON kd_actual.member_id = tk.F_MemberId | |
| 4170 | + {whereClause} | |
| 4171 | + ORDER BY tk.F_ExpansionTime DESC | |
| 4172 | + LIMIT @PageSize OFFSET @Offset"; | |
| 4173 | + | |
| 4174 | + parameters.Add(new SugarParameter("@PageSize", input.PageSize)); | |
| 4175 | + parameters.Add(new SugarParameter("@Offset", (input.PageIndex - 1) * input.PageSize)); | |
| 4176 | + | |
| 4177 | + // 查询总数 | |
| 4178 | + var countSql = $@" | |
| 4179 | + SELECT COUNT(*) | |
| 4180 | + FROM lq_tkjlb tk | |
| 4181 | + {whereClause}"; | |
| 4182 | + | |
| 4183 | + var countParameters = parameters.Where(p => p.ParameterName != "@PageSize" && p.ParameterName != "@Offset").ToList(); | |
| 4184 | + var totalCount = await _db.Ado.GetIntAsync(countSql, countParameters); | |
| 4185 | + | |
| 4186 | + // 执行查询 | |
| 4187 | + var result = await _db.Ado.SqlQueryAsync<LeadCustomerStatisticsListOutput>(sql, parameters); | |
| 4188 | + | |
| 4189 | + // 生成问题分析说明 | |
| 4190 | + foreach (var item in result) | |
| 4191 | + { | |
| 4192 | + var analysisList = new List<string>(); | |
| 4193 | + | |
| 4194 | + if (item.HasInvite == "否" && item.HasAppointment == "否" && item.ActualAppointmentCount > 0) | |
| 4195 | + { | |
| 4196 | + analysisList.Add($"有{item.ActualAppointmentCount}条预约记录,但未通过邀约产生(F_InviteId为null)"); | |
| 4197 | + } | |
| 4198 | + | |
| 4199 | + if (item.HasAppointment == "否" && item.HasConsume == "否" && item.ActualConsumeCount > 0) | |
| 4200 | + { | |
| 4201 | + analysisList.Add($"有{item.ActualConsumeCount}条消耗记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)"); | |
| 4202 | + } | |
| 4203 | + | |
| 4204 | + if (item.HasAppointment == "否" && item.HasBilling == "否" && item.ActualBillingCount > 0) | |
| 4205 | + { | |
| 4206 | + analysisList.Add($"有{item.ActualBillingCount}条开单记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)"); | |
| 4207 | + } | |
| 4208 | + | |
| 4209 | + if (item.HasInvite == "是" && item.HasAppointment == "否" && item.ActualAppointmentCount > 0) | |
| 4210 | + { | |
| 4211 | + analysisList.Add($"有邀约记录,有{item.ActualAppointmentCount}条预约记录,但预约记录的F_InviteId未关联到邀约记录"); | |
| 4212 | + } | |
| 4213 | + | |
| 4214 | + if (item.HasAppointment == "是" && item.HasConsume == "否" && item.ActualConsumeCount > 0) | |
| 4215 | + { | |
| 4216 | + analysisList.Add($"有预约记录,有{item.ActualConsumeCount}条消耗记录,但消耗记录的F_AppointmentId未关联到预约记录"); | |
| 4217 | + } | |
| 4218 | + | |
| 4219 | + if (item.HasAppointment == "是" && item.HasBilling == "否" && item.ActualBillingCount > 0) | |
| 4220 | + { | |
| 4221 | + analysisList.Add($"有预约记录,有{item.ActualBillingCount}条开单记录,但开单记录的F_AppointmentId未关联到预约记录"); | |
| 4222 | + } | |
| 4223 | + | |
| 4224 | + if (analysisList.Count == 0) | |
| 4225 | + { | |
| 4226 | + item.Analysis = "数据正常,符合业务链路:拓客 -> 邀约 -> 预约 -> 开单/消耗"; | |
| 4227 | + } | |
| 4228 | + else | |
| 4229 | + { | |
| 4230 | + item.Analysis = string.Join(";", analysisList); | |
| 4231 | + } | |
| 4232 | + } | |
| 4233 | + | |
| 4234 | + return new | |
| 4235 | + { | |
| 4236 | + list = result, | |
| 4237 | + pagination = new | |
| 4238 | + { | |
| 4239 | + pageIndex = input.PageIndex, | |
| 4240 | + pageSize = input.PageSize, | |
| 4241 | + total = totalCount | |
| 4242 | + } | |
| 4243 | + }; | |
| 4244 | + } | |
| 4245 | + catch (Exception ex) | |
| 4246 | + { | |
| 4247 | + _logger.LogError(ex, "获取线索池客户统计报表失败"); | |
| 4248 | + throw NCCException.Oh($"获取线索池客户统计报表失败:{ex.Message}"); | |
| 4249 | + } | |
| 4250 | + } | |
| 4251 | + #endregion | |
| 4252 | + | |
| 4253 | + #region 门店统计报表 | |
| 4254 | + /// <summary> | |
| 4255 | + /// 获取门店统计报表 | |
| 4256 | + /// </summary> | |
| 4257 | + /// <remarks> | |
| 4258 | + /// 按门店统计拓客、邀约、预约、消耗、开单等数据 | |
| 4259 | + /// | |
| 4260 | + /// 业务链路:拓客 -> 邀约 -> 预约 -> 开单/消耗 | |
| 4261 | + /// | |
| 4262 | + /// 示例请求: | |
| 4263 | + /// ```json | |
| 4264 | + /// { | |
| 4265 | + /// "startTime": "2025-10-01T00:00:00", | |
| 4266 | + /// "endTime": "2025-10-31T23:59:59", | |
| 4267 | + /// "storeIds": ["store1", "store2"], | |
| 4268 | + /// "eventId": "event123" | |
| 4269 | + /// } | |
| 4270 | + /// ``` | |
| 4271 | + /// | |
| 4272 | + /// 参数说明: | |
| 4273 | + /// - startTime: 拓客时间范围开始时间 | |
| 4274 | + /// - endTime: 拓客时间范围结束时间 | |
| 4275 | + /// - storeIds: 门店ID列表,可传多个 | |
| 4276 | + /// - eventId: 拓客活动ID | |
| 4277 | + /// | |
| 4278 | + /// 返回数据说明: | |
| 4279 | + /// - StoreId: 门店ID | |
| 4280 | + /// - StoreName: 门店名称 | |
| 4281 | + /// - TotalCount: 总人数(从客户信息表按归属门店统计) | |
| 4282 | + /// - TkMemberCount: 拓客人数(拓客记录数,不去重) | |
| 4283 | + /// - InviteCount: 邀约数(通过拓客编号关联的邀约记录数) | |
| 4284 | + /// - AppointmentCount: 预约数(通过邀约ID关联的预约记录数,只统计通过邀约产生的预约) | |
| 4285 | + /// - ConsumeCount: 耗卡数(通过预约ID关联的耗卡记录数,只统计通过预约产生的耗卡) | |
| 4286 | + /// - BillingCount: 开单数(通过预约ID关联的开单记录数,只统计通过预约产生的开单) | |
| 4287 | + /// - BillingAmount: 开单金额(通过预约ID关联的开单记录金额汇总) | |
| 4288 | + /// | |
| 4289 | + /// 返回示例: | |
| 4290 | + /// ```json | |
| 4291 | + /// { | |
| 4292 | + /// "list": [ | |
| 4293 | + /// { | |
| 4294 | + /// "StoreId": "1649328471923847169", | |
| 4295 | + /// "StoreName": "绿纤紫荆店", | |
| 4296 | + /// "TotalCount": 119, | |
| 4297 | + /// "TkMemberCount": 117, | |
| 4298 | + /// "InviteCount": 4, | |
| 4299 | + /// "AppointmentCount": 2, | |
| 4300 | + /// "ConsumeCount": 1, | |
| 4301 | + /// "BillingCount": 1, | |
| 4302 | + /// "BillingAmount": 199.00 | |
| 4303 | + /// } | |
| 4304 | + /// ] | |
| 4305 | + /// } | |
| 4306 | + /// ``` | |
| 4307 | + /// </remarks> | |
| 4308 | + /// <param name="input">查询条件</param> | |
| 4309 | + /// <returns>门店统计报表列表</returns> | |
| 4310 | + /// <response code="200">查询成功,返回门店统计报表列表</response> | |
| 4311 | + /// <response code="400">参数错误</response> | |
| 4312 | + /// <response code="500">服务器内部错误</response> | |
| 4313 | + [HttpPost("get-store-statistics-list")] | |
| 4314 | + public async Task<dynamic> GetStoreStatisticsList([FromBody] StoreStatisticsListQueryInput input) | |
| 4315 | + { | |
| 4316 | + try | |
| 4317 | + { | |
| 4318 | + // 构建WHERE条件(带表别名,用于子查询) | |
| 4319 | + var whereConditions = new List<string>(); | |
| 4320 | + // 构建WHERE条件(不带表别名,用于UNION的SELECT) | |
| 4321 | + var whereConditionsNoAlias = new List<string>(); | |
| 4322 | + var parameters = new List<SugarParameter>(); | |
| 4323 | + | |
| 4324 | + if (input.StartTime.HasValue) | |
| 4325 | + { | |
| 4326 | + whereConditions.Add("tk.F_ExpansionTime >= @StartTime"); | |
| 4327 | + whereConditionsNoAlias.Add("F_ExpansionTime >= @StartTime"); | |
| 4328 | + parameters.Add(new SugarParameter("@StartTime", input.StartTime.Value)); | |
| 4329 | + } | |
| 4330 | + | |
| 4331 | + if (input.EndTime.HasValue) | |
| 4332 | + { | |
| 4333 | + whereConditions.Add("tk.F_ExpansionTime <= @EndTime"); | |
| 4334 | + whereConditionsNoAlias.Add("F_ExpansionTime <= @EndTime"); | |
| 4335 | + parameters.Add(new SugarParameter("@EndTime", input.EndTime.Value)); | |
| 4336 | + } | |
| 4337 | + | |
| 4338 | + if (input.StoreIds != null && input.StoreIds.Any()) | |
| 4339 | + { | |
| 4340 | + var storeIdParams = string.Join(",", input.StoreIds.Select((_, i) => $"@StoreId{i}")); | |
| 4341 | + whereConditions.Add($"tk.F_StoreId IN ({storeIdParams})"); | |
| 4342 | + whereConditionsNoAlias.Add($"F_StoreId IN ({storeIdParams})"); | |
| 4343 | + for (int i = 0; i < input.StoreIds.Count; i++) | |
| 4344 | + { | |
| 4345 | + parameters.Add(new SugarParameter($"@StoreId{i}", input.StoreIds[i])); | |
| 4346 | + } | |
| 4347 | + } | |
| 4348 | + | |
| 4349 | + if (!string.IsNullOrEmpty(input.EventId)) | |
| 4350 | + { | |
| 4351 | + whereConditions.Add("tk.F_EventId = @EventId"); | |
| 4352 | + whereConditionsNoAlias.Add("F_EventId = @EventId"); | |
| 4353 | + parameters.Add(new SugarParameter("@EventId", input.EventId)); | |
| 4354 | + } | |
| 4355 | + | |
| 4356 | + var whereClause = whereConditions.Any() ? "WHERE " + string.Join(" AND ", whereConditions) : ""; | |
| 4357 | + var whereClauseNoAlias = whereConditionsNoAlias.Any() ? "WHERE " + string.Join(" AND ", whereConditionsNoAlias) : ""; | |
| 4358 | + | |
| 4359 | + // 构建门店筛选条件(用于客户信息表查询) | |
| 4360 | + var khWhereConditions = new List<string>(); | |
| 4361 | + var khWhereConditionsNoAlias = new List<string>(); | |
| 4362 | + if (input.StoreIds != null && input.StoreIds.Any()) | |
| 4363 | + { | |
| 4364 | + var storeIdParams = string.Join(",", input.StoreIds.Select((_, i) => $"@StoreId{i}")); | |
| 4365 | + khWhereConditions.Add($"kh.gsmd IN ({storeIdParams})"); | |
| 4366 | + khWhereConditionsNoAlias.Add($"gsmd IN ({storeIdParams})"); | |
| 4367 | + } | |
| 4368 | + | |
| 4369 | + var khWhereClause = khWhereConditions.Any() ? "WHERE " + string.Join(" AND ", khWhereConditions) : "WHERE kh.gsmd IS NOT NULL"; | |
| 4370 | + var khWhereClauseNoAlias = khWhereConditionsNoAlias.Any() ? "WHERE " + string.Join(" AND ", khWhereConditionsNoAlias) : "WHERE gsmd IS NOT NULL"; | |
| 4371 | + | |
| 4372 | + // 使用子查询优化性能,避免复杂的JOIN | |
| 4373 | + var sql = $@" | |
| 4374 | + SELECT | |
| 4375 | + COALESCE(total_stats.StoreId, tk_stats.StoreId, yaoy_stats.StoreId, yy_stats.StoreId, xh_stats.StoreId, kd_stats.StoreId) as StoreId, | |
| 4376 | + COALESCE(md.dm, '') as StoreName, | |
| 4377 | + COALESCE(total_stats.TotalCount, 0) as TotalCount, | |
| 4378 | + COALESCE(tk_stats.TkMemberCount, 0) as TkMemberCount, | |
| 4379 | + COALESCE(yaoy_stats.InviteCount, 0) as InviteCount, | |
| 4380 | + COALESCE(yy_stats.AppointmentCount, 0) as AppointmentCount, | |
| 4381 | + COALESCE(xh_stats.ConsumeCount, 0) as ConsumeCount, | |
| 4382 | + COALESCE(kd_stats.BillingCount, 0) as BillingCount, | |
| 4383 | + COALESCE(kd_stats.BillingAmount, 0) as BillingAmount | |
| 4384 | + FROM ( | |
| 4385 | + SELECT DISTINCT StoreId FROM ( | |
| 4386 | + SELECT gsmd as StoreId FROM lq_khxx {khWhereClauseNoAlias} | |
| 4387 | + UNION | |
| 4388 | + SELECT F_StoreId as StoreId FROM lq_tkjlb {whereClauseNoAlias} | |
| 4389 | + ) as all_stores | |
| 4390 | + ) as stores | |
| 4391 | + LEFT JOIN lq_mdxx md ON md.F_Id = stores.StoreId | |
| 4392 | + -- 总人数统计(从客户信息表按归属门店统计) | |
| 4393 | + LEFT JOIN ( | |
| 4394 | + SELECT | |
| 4395 | + kh.gsmd as StoreId, | |
| 4396 | + COUNT(*) as TotalCount | |
| 4397 | + FROM lq_khxx kh | |
| 4398 | + {khWhereClause} | |
| 4399 | + GROUP BY kh.gsmd | |
| 4400 | + ) total_stats ON total_stats.StoreId = stores.StoreId | |
| 4401 | + -- 拓客人数统计(不用去重) | |
| 4402 | + LEFT JOIN ( | |
| 4403 | + SELECT | |
| 4404 | + tk.F_StoreId as StoreId, | |
| 4405 | + COUNT(tk.F_MemberId) as TkMemberCount | |
| 4406 | + FROM lq_tkjlb tk | |
| 4407 | + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} | |
| 4408 | + GROUP BY tk.F_StoreId | |
| 4409 | + ) tk_stats ON tk_stats.StoreId = stores.StoreId | |
| 4410 | + -- 邀约数统计 | |
| 4411 | + LEFT JOIN ( | |
| 4412 | + SELECT | |
| 4413 | + tk.F_StoreId as StoreId, | |
| 4414 | + COUNT(DISTINCT yaoy.F_Id) as InviteCount | |
| 4415 | + FROM lq_tkjlb tk | |
| 4416 | + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id | |
| 4417 | + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} | |
| 4418 | + GROUP BY tk.F_StoreId | |
| 4419 | + ) yaoy_stats ON yaoy_stats.StoreId = stores.StoreId | |
| 4420 | + -- 预约数统计(通过邀约ID关联) | |
| 4421 | + LEFT JOIN ( | |
| 4422 | + SELECT | |
| 4423 | + tk.F_StoreId as StoreId, | |
| 4424 | + COUNT(DISTINCT yy.F_Id) as AppointmentCount | |
| 4425 | + FROM lq_tkjlb tk | |
| 4426 | + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id | |
| 4427 | + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id | |
| 4428 | + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} | |
| 4429 | + GROUP BY tk.F_StoreId | |
| 4430 | + ) yy_stats ON yy_stats.StoreId = stores.StoreId | |
| 4431 | + -- 耗卡数统计(通过预约ID关联) | |
| 4432 | + LEFT JOIN ( | |
| 4433 | + SELECT | |
| 4434 | + tk.F_StoreId as StoreId, | |
| 4435 | + COUNT(DISTINCT xh.F_Id) as ConsumeCount | |
| 4436 | + FROM lq_tkjlb tk | |
| 4437 | + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id | |
| 4438 | + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id | |
| 4439 | + INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1 | |
| 4440 | + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} | |
| 4441 | + GROUP BY tk.F_StoreId | |
| 4442 | + ) xh_stats ON xh_stats.StoreId = stores.StoreId | |
| 4443 | + -- 开单数和开单金额统计(通过预约ID关联) | |
| 4444 | + LEFT JOIN ( | |
| 4445 | + SELECT | |
| 4446 | + tk.F_StoreId as StoreId, | |
| 4447 | + COUNT(DISTINCT kd.F_Id) as BillingCount, | |
| 4448 | + SUM(kd.zdyj) as BillingAmount | |
| 4449 | + FROM lq_tkjlb tk | |
| 4450 | + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id | |
| 4451 | + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id | |
| 4452 | + INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.F_IsEffective = 1 | |
| 4453 | + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} | |
| 4454 | + GROUP BY tk.F_StoreId | |
| 4455 | + ) kd_stats ON kd_stats.StoreId = stores.StoreId | |
| 4456 | + WHERE stores.StoreId IS NOT NULL | |
| 4457 | + ORDER BY stores.StoreId"; | |
| 4458 | + | |
| 4459 | + // 执行查询 | |
| 4460 | + var result = await _db.Ado.SqlQueryAsync<StoreStatisticsListOutput>(sql, parameters); | |
| 4461 | + | |
| 4462 | + return new | |
| 4463 | + { | |
| 4464 | + list = result | |
| 4465 | + }; | |
| 4466 | + } | |
| 4467 | + catch (Exception ex) | |
| 4468 | + { | |
| 4469 | + _logger.LogError(ex, "获取门店统计报表失败"); | |
| 4470 | + throw NCCException.Oh($"获取门店统计报表失败:{ex.Message}"); | |
| 4471 | + } | |
| 4472 | + } | |
| 4473 | + #endregion | |
| 4474 | + | |
| 4475 | + #region 会员升单统计 | |
| 4476 | + /// <summary> | |
| 4477 | + /// 获取会员升单统计(前4单中是否有升医美、升科美、升生美) | |
| 4478 | + /// </summary> | |
| 4479 | + /// <remarks> | |
| 4480 | + /// 统计每个会员的前4单开单记录中是否有升医美、升科美、升生美 | |
| 4481 | + /// | |
| 4482 | + /// 示例请求: | |
| 4483 | + /// ```json | |
| 4484 | + /// { | |
| 4485 | + /// "pageIndex": 1, | |
| 4486 | + /// "pageSize": 20, | |
| 4487 | + /// "memberIds": ["member1", "member2"], | |
| 4488 | + /// "hasUpgradeMedicalBeauty": true, | |
| 4489 | + /// "hasUpgradeTechBeauty": false, | |
| 4490 | + /// "hasUpgradeLifeBeauty": null | |
| 4491 | + /// } | |
| 4492 | + /// ``` | |
| 4493 | + /// | |
| 4494 | + /// 参数说明: | |
| 4495 | + /// - pageIndex: 页码,从1开始 | |
| 4496 | + /// - pageSize: 每页数量 | |
| 4497 | + /// - memberIds: 会员ID列表(可选,不传则查询所有会员) | |
| 4498 | + /// - hasUpgradeMedicalBeauty: 是否升医美(true-是,false-否,null-不筛选) | |
| 4499 | + /// - hasUpgradeTechBeauty: 是否升科美(true-是,false-否,null-不筛选) | |
| 4500 | + /// - hasUpgradeLifeBeauty: 是否升生美(true-是,false-否,null-不筛选) | |
| 4501 | + /// | |
| 4502 | + /// 返回数据说明: | |
| 4503 | + /// - MemberId: 会员ID | |
| 4504 | + /// - MemberName: 会员姓名 | |
| 4505 | + /// - MemberPhone: 会员手机号 | |
| 4506 | + /// - HasUpgradeMedicalBeauty: 前4单中是否有升医美(是/否) | |
| 4507 | + /// - HasUpgradeTechBeauty: 前4单中是否有升科美(是/否) | |
| 4508 | + /// - HasUpgradeLifeBeauty: 前4单中是否有升生美(是/否) | |
| 4509 | + /// | |
| 4510 | + /// 返回示例: | |
| 4511 | + /// ```json | |
| 4512 | + /// { | |
| 4513 | + /// "list": [ | |
| 4514 | + /// { | |
| 4515 | + /// "MemberId": "744326092097062149", | |
| 4516 | + /// "MemberName": "张女士", | |
| 4517 | + /// "MemberPhone": "13800138000", | |
| 4518 | + /// "HasUpgradeMedicalBeauty": "否", | |
| 4519 | + /// "HasUpgradeTechBeauty": "否", | |
| 4520 | + /// "HasUpgradeLifeBeauty": "否" | |
| 4521 | + /// } | |
| 4522 | + /// ], | |
| 4523 | + /// "pagination": { | |
| 4524 | + /// "pageIndex": 1, | |
| 4525 | + /// "pageSize": 20, | |
| 4526 | + /// "total": 100 | |
| 4527 | + /// } | |
| 4528 | + /// } | |
| 4529 | + /// ``` | |
| 4530 | + /// </remarks> | |
| 4531 | + /// <param name="input">查询条件</param> | |
| 4532 | + /// <returns>会员升单统计列表</returns> | |
| 4533 | + /// <response code="200">查询成功,返回会员升单统计列表</response> | |
| 4534 | + /// <response code="400">参数错误</response> | |
| 4535 | + /// <response code="500">服务器内部错误</response> | |
| 4536 | + [HttpPost("get-member-upgrade-statistics-list")] | |
| 4537 | + public async Task<dynamic> GetMemberUpgradeStatisticsList([FromBody] MemberUpgradeStatisticsListQueryInput input) | |
| 4538 | + { | |
| 4539 | + try | |
| 4540 | + { | |
| 4541 | + // 构建WHERE条件 | |
| 4542 | + var whereConditions = new List<string>(); | |
| 4543 | + var parameters = new List<SugarParameter>(); | |
| 4544 | + | |
| 4545 | + if (input.MemberIds != null && input.MemberIds.Any()) | |
| 4546 | + { | |
| 4547 | + var memberIdParams = string.Join(",", input.MemberIds.Select((_, i) => $"@MemberId{i}")); | |
| 4548 | + whereConditions.Add($"kd.kdhy IN ({memberIdParams})"); | |
| 4549 | + for (int i = 0; i < input.MemberIds.Count; i++) | |
| 4550 | + { | |
| 4551 | + parameters.Add(new SugarParameter($"@MemberId{i}", input.MemberIds[i])); | |
| 4552 | + } | |
| 4553 | + } | |
| 4554 | + | |
| 4555 | + var whereClause = whereConditions.Any() ? "AND " + string.Join(" AND ", whereConditions) : ""; | |
| 4556 | + | |
| 4557 | + // 构建HAVING条件(用于筛选升单条件) | |
| 4558 | + var havingConditions = new List<string>(); | |
| 4559 | + if (input.HasUpgradeMedicalBeauty.HasValue) | |
| 4560 | + { | |
| 4561 | + if (input.HasUpgradeMedicalBeauty.Value) | |
| 4562 | + { | |
| 4563 | + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeMedicalBeauty = '是' THEN 1 ELSE 0 END) = 1"); | |
| 4564 | + } | |
| 4565 | + else | |
| 4566 | + { | |
| 4567 | + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeMedicalBeauty = '是' THEN 1 ELSE 0 END) = 0"); | |
| 4568 | + } | |
| 4569 | + } | |
| 4570 | + if (input.HasUpgradeTechBeauty.HasValue) | |
| 4571 | + { | |
| 4572 | + if (input.HasUpgradeTechBeauty.Value) | |
| 4573 | + { | |
| 4574 | + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeTechBeauty = '是' THEN 1 ELSE 0 END) = 1"); | |
| 4575 | + } | |
| 4576 | + else | |
| 4577 | + { | |
| 4578 | + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeTechBeauty = '是' THEN 1 ELSE 0 END) = 0"); | |
| 4579 | + } | |
| 4580 | + } | |
| 4581 | + if (input.HasUpgradeLifeBeauty.HasValue) | |
| 4582 | + { | |
| 4583 | + if (input.HasUpgradeLifeBeauty.Value) | |
| 4584 | + { | |
| 4585 | + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeLifeBeauty = '是' THEN 1 ELSE 0 END) = 1"); | |
| 4586 | + } | |
| 4587 | + else | |
| 4588 | + { | |
| 4589 | + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeLifeBeauty = '是' THEN 1 ELSE 0 END) = 0"); | |
| 4590 | + } | |
| 4591 | + } | |
| 4592 | + | |
| 4593 | + var havingClause = havingConditions.Any() ? "HAVING " + string.Join(" AND ", havingConditions) : ""; | |
| 4594 | + | |
| 4595 | + // 分页参数 | |
| 4596 | + var offset = (input.PageIndex - 1) * input.PageSize; | |
| 4597 | + parameters.Add(new SugarParameter("@PageSize", input.PageSize)); | |
| 4598 | + parameters.Add(new SugarParameter("@Offset", offset)); | |
| 4599 | + | |
| 4600 | + // 查询每个会员的前4单中是否有升医美、升科美、升生美 | |
| 4601 | + var sql = $@" | |
| 4602 | + SELECT | |
| 4603 | + kd.kdhy as MemberId, | |
| 4604 | + kh.khmc as MemberName, | |
| 4605 | + kh.sjh as MemberPhone, | |
| 4606 | + CASE WHEN MAX(CASE WHEN kd.F_UpgradeMedicalBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeMedicalBeauty, | |
| 4607 | + CASE WHEN MAX(CASE WHEN kd.F_UpgradeTechBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeTechBeauty, | |
| 4608 | + CASE WHEN MAX(CASE WHEN kd.F_UpgradeLifeBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeLifeBeauty | |
| 4609 | + FROM ( | |
| 4610 | + SELECT | |
| 4611 | + kd.kdhy, | |
| 4612 | + kd.F_Id, | |
| 4613 | + kd.kdrq, | |
| 4614 | + kd.F_CreateTime, | |
| 4615 | + kd.F_UpgradeMedicalBeauty, | |
| 4616 | + kd.F_UpgradeTechBeauty, | |
| 4617 | + kd.F_UpgradeLifeBeauty | |
| 4618 | + FROM lq_kd_kdjlb kd | |
| 4619 | + WHERE kd.F_IsEffective = 1 | |
| 4620 | + AND kd.kdhy IS NOT NULL | |
| 4621 | + {whereClause} | |
| 4622 | + AND ( | |
| 4623 | + SELECT COUNT(*) | |
| 4624 | + FROM lq_kd_kdjlb kd2 | |
| 4625 | + WHERE kd2.kdhy = kd.kdhy | |
| 4626 | + AND kd2.F_IsEffective = 1 | |
| 4627 | + AND ( | |
| 4628 | + kd2.kdrq > kd.kdrq | |
| 4629 | + OR (kd2.kdrq = kd.kdrq AND kd2.F_CreateTime > kd.F_CreateTime) | |
| 4630 | + OR (kd2.kdrq = kd.kdrq AND kd2.F_CreateTime = kd.F_CreateTime AND kd2.F_Id > kd.F_Id) | |
| 4631 | + ) | |
| 4632 | + ) < 4 | |
| 4633 | + ) kd | |
| 4634 | + LEFT JOIN lq_khxx kh ON kh.F_Id = kd.kdhy | |
| 4635 | + GROUP BY kd.kdhy, kh.khmc, kh.sjh | |
| 4636 | + {havingClause} | |
| 4637 | + ORDER BY kd.kdhy | |
| 4638 | + LIMIT @PageSize OFFSET @Offset"; | |
| 4639 | + | |
| 4640 | + // 查询总数(需要应用相同的HAVING条件) | |
| 4641 | + var countSql = $@" | |
| 4642 | + SELECT COUNT(*) | |
| 4643 | + FROM ( | |
| 4644 | + SELECT | |
| 4645 | + kd.kdhy, | |
| 4646 | + CASE WHEN MAX(CASE WHEN kd.F_UpgradeMedicalBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeMedicalBeauty, | |
| 4647 | + CASE WHEN MAX(CASE WHEN kd.F_UpgradeTechBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeTechBeauty, | |
| 4648 | + CASE WHEN MAX(CASE WHEN kd.F_UpgradeLifeBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeLifeBeauty | |
| 4649 | + FROM ( | |
| 4650 | + SELECT | |
| 4651 | + kd.kdhy, | |
| 4652 | + kd.F_Id, | |
| 4653 | + kd.kdrq, | |
| 4654 | + kd.F_CreateTime, | |
| 4655 | + kd.F_UpgradeMedicalBeauty, | |
| 4656 | + kd.F_UpgradeTechBeauty, | |
| 4657 | + kd.F_UpgradeLifeBeauty | |
| 4658 | + FROM lq_kd_kdjlb kd | |
| 4659 | + WHERE kd.F_IsEffective = 1 | |
| 4660 | + AND kd.kdhy IS NOT NULL | |
| 4661 | + {whereClause} | |
| 4662 | + AND ( | |
| 4663 | + SELECT COUNT(*) | |
| 4664 | + FROM lq_kd_kdjlb kd2 | |
| 4665 | + WHERE kd2.kdhy = kd.kdhy | |
| 4666 | + AND kd2.F_IsEffective = 1 | |
| 4667 | + AND ( | |
| 4668 | + kd2.kdrq > kd.kdrq | |
| 4669 | + OR (kd2.kdrq = kd.kdrq AND kd2.F_CreateTime > kd.F_CreateTime) | |
| 4670 | + OR (kd2.kdrq = kd.kdrq AND kd2.F_CreateTime = kd.F_CreateTime AND kd2.F_Id > kd.F_Id) | |
| 4671 | + ) | |
| 4672 | + ) < 4 | |
| 4673 | + ) kd | |
| 4674 | + GROUP BY kd.kdhy | |
| 4675 | + {havingClause} | |
| 4676 | + ) as filtered_results"; | |
| 4677 | + | |
| 4678 | + var countParameters = parameters.Where(p => p.ParameterName != "@PageSize" && p.ParameterName != "@Offset").ToList(); | |
| 4679 | + var totalCount = await _db.Ado.GetIntAsync(countSql, countParameters); | |
| 4680 | + | |
| 4681 | + // 执行查询 | |
| 4682 | + var result = await _db.Ado.SqlQueryAsync<MemberUpgradeStatisticsListOutput>(sql, parameters); | |
| 4683 | + | |
| 4684 | + return new | |
| 4685 | + { | |
| 4686 | + list = result, | |
| 4687 | + pagination = new | |
| 4688 | + { | |
| 4689 | + pageIndex = input.PageIndex, | |
| 4690 | + pageSize = input.PageSize, | |
| 4691 | + total = totalCount | |
| 4692 | + } | |
| 4693 | + }; | |
| 4694 | + } | |
| 4695 | + catch (Exception ex) | |
| 4696 | + { | |
| 4697 | + _logger.LogError(ex, "获取会员升单统计失败"); | |
| 4698 | + throw NCCException.Oh($"获取会员升单统计失败:{ex.Message}"); | |
| 4699 | + } | |
| 4700 | + } | |
| 4701 | + #endregion | |
| 4702 | + | |
| 3950 | 4703 | |
| 3951 | 4704 | |
| 3952 | 4705 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqStoreConsumableInventoryService.cs
| 1 | 1 | using System; |
| 2 | +using System.Collections.Generic; | |
| 2 | 3 | using System.Linq; |
| 3 | 4 | using System.Threading.Tasks; |
| 4 | 5 | using Microsoft.AspNetCore.Mvc; |
| 5 | 6 | using Microsoft.Extensions.Logging; |
| 6 | 7 | using NCC.Common.Core.Manager; |
| 7 | 8 | using NCC.Common.Enum; |
| 9 | +using NCC.Common.Extension; | |
| 8 | 10 | using NCC.Common.Filter; |
| 9 | 11 | using NCC.Dependency; |
| 10 | 12 | using NCC.DynamicApiController; |
| 13 | +using NCC.Extend.Entitys.Dto.Common; | |
| 11 | 14 | using NCC.Extend.Entitys.Dto.LqStoreConsumableInventory; |
| 12 | 15 | using NCC.Extend.Entitys.Enum; |
| 13 | 16 | using NCC.Extend.Entitys.lq_mdxx; |
| ... | ... | @@ -367,6 +370,26 @@ namespace NCC.Extend |
| 367 | 370 | } |
| 368 | 371 | } |
| 369 | 372 | #endregion |
| 373 | + | |
| 374 | + | |
| 375 | + #region 获取消耗品产品类型枚举内容 | |
| 376 | + /// <summary> | |
| 377 | + /// 获取消耗品产品类型枚举内容 | |
| 378 | + /// </summary> | |
| 379 | + /// <returns>消耗品产品类型枚举列表</returns> | |
| 380 | + [HttpGet("consumable-product-type")] | |
| 381 | + public List<EnumOutput> GetConsumableProductTypeSelector() | |
| 382 | + { | |
| 383 | + return Enum.GetValues<ConsumableProductTypeEnum>() | |
| 384 | + .Select(e => new EnumOutput | |
| 385 | + { | |
| 386 | + Value = (int)e, | |
| 387 | + Name = e.ToString(), | |
| 388 | + Description = e.GetDescription(), | |
| 389 | + }) | |
| 390 | + .ToList(); | |
| 391 | + } | |
| 392 | + #endregion | |
| 370 | 393 | } |
| 371 | 394 | } |
| 372 | 395 | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
| ... | ... | @@ -1757,11 +1757,20 @@ namespace NCC.Extend.LqXhHyhk |
| 1757 | 1757 | var kjbsyjList = await _db.Queryable<LqXhKjbsyjEntity>().Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync(); |
| 1758 | 1758 | |
| 1759 | 1759 | // 查询已存在的人次记录(按BusinessId去重,避免重复添加) |
| 1760 | - var existingBusinessIds = await _db.Queryable<LqPersonTimesRecordEntity>() | |
| 1761 | - .Where(x => consumeIds.Contains(x.BusinessId) && x.BusinessType == "耗卡" && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1762 | - .Select(x => x.BusinessId) | |
| 1763 | - .Distinct() | |
| 1764 | - .ToListAsync(); | |
| 1760 | + var existingBusinessIds = await _db.Queryable<LqPersonTimesRecordEntity>().Where(x => consumeIds.Contains(x.BusinessId) && x.BusinessType == "耗卡" && x.IsEffective == StatusEnum.有效.GetHashCode()).Select(x => x.BusinessId).Distinct().ToListAsync(); | |
| 1761 | + | |
| 1762 | + // 批量查询所有会员是否有开单记录(优化:避免在循环中执行N次查询) | |
| 1763 | + var memberIds = consumeList.Where(x => !string.IsNullOrEmpty(x.Hy)).Select(x => x.Hy).Distinct().ToList(); | |
| 1764 | + var membersWithBilling = new HashSet<string>(); | |
| 1765 | + if (memberIds.Any()) | |
| 1766 | + { | |
| 1767 | + var billingMemberIds = await _db.Queryable<LqKdKdjlbEntity>() | |
| 1768 | + .Where(x => memberIds.Contains(x.Kdhy) && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Sfyj > 0) | |
| 1769 | + .Select(x => x.Kdhy) | |
| 1770 | + .Distinct() | |
| 1771 | + .ToListAsync(); | |
| 1772 | + membersWithBilling = new HashSet<string>(billingMemberIds); | |
| 1773 | + } | |
| 1765 | 1774 | |
| 1766 | 1775 | // 3. 构建人次记录列表 |
| 1767 | 1776 | var personTimesRecords = new List<LqPersonTimesRecordEntity>(); |
| ... | ... | @@ -1782,19 +1791,14 @@ namespace NCC.Extend.LqXhHyhk |
| 1782 | 1791 | } |
| 1783 | 1792 | var workDate = consume.Hksj.Value.Date; // 工作日期(用于人次统计) |
| 1784 | 1793 | var workMonth = consume.Hksj.Value.ToString("yyyyMM"); // 工作月份(用于人头统计) |
| 1785 | - | |
| 1794 | + //查看该会员是否在开单记录表中存在开单记录(从批量查询结果中查找) | |
| 1795 | + var billingRecord = !string.IsNullOrEmpty(consume.Hy) && membersWithBilling.Contains(consume.Hy); | |
| 1786 | 1796 | // 处理健康师业绩:去重后计算人次数量(剔除T区健康师) |
| 1787 | - var consumeJksyjList = jksyjList.Where(x => x.Glkdbh == consume.Id | |
| 1788 | - && !string.IsNullOrEmpty(x.Jks) | |
| 1789 | - && !string.IsNullOrEmpty(x.Jksxm) | |
| 1790 | - && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList(); | |
| 1797 | + 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(); | |
| 1791 | 1798 | if (consumeJksyjList.Any()) |
| 1792 | 1799 | { |
| 1793 | 1800 | // 按健康师ID去重 |
| 1794 | - var distinctJksyjList = consumeJksyjList | |
| 1795 | - .GroupBy(x => x.Jks) | |
| 1796 | - .Select(g => g.First()) | |
| 1797 | - .ToList(); | |
| 1801 | + var distinctJksyjList = consumeJksyjList.GroupBy(x => x.Jks).Select(g => g.First()).ToList(); | |
| 1798 | 1802 | |
| 1799 | 1803 | // 计算人次数量:1 / 健康师数量 |
| 1800 | 1804 | var jksQuantity = distinctJksyjList.Count > 0 ? 1.0m / distinctJksyjList.Count : 0; |
| ... | ... | @@ -1815,23 +1819,18 @@ namespace NCC.Extend.LqXhHyhk |
| 1815 | 1819 | WorkMonth = workMonth, |
| 1816 | 1820 | Quantity = jksQuantity, |
| 1817 | 1821 | CreateTime = DateTime.Now, |
| 1818 | - IsEffective = StatusEnum.有效.GetHashCode() | |
| 1822 | + IsEffective = StatusEnum.有效.GetHashCode(), | |
| 1823 | + HasBilling = billingRecord ? 1 : 0 | |
| 1819 | 1824 | }); |
| 1820 | 1825 | } |
| 1821 | 1826 | } |
| 1822 | 1827 | |
| 1823 | 1828 | // 处理科技老师业绩:去重后计算人次数量(剔除T区科技老师) |
| 1824 | - var consumeKjbsyjList = kjbsyjList.Where(x => x.Glkdbh == consume.Id | |
| 1825 | - && !string.IsNullOrEmpty(x.Kjbls) | |
| 1826 | - && !string.IsNullOrEmpty(x.Kjblsxm) | |
| 1827 | - && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList(); | |
| 1829 | + 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(); | |
| 1828 | 1830 | if (consumeKjbsyjList.Any()) |
| 1829 | 1831 | { |
| 1830 | 1832 | // 按科技老师ID去重 |
| 1831 | - var distinctKjbsyjList = consumeKjbsyjList | |
| 1832 | - .GroupBy(x => x.Kjbls) | |
| 1833 | - .Select(g => g.First()) | |
| 1834 | - .ToList(); | |
| 1833 | + var distinctKjbsyjList = consumeKjbsyjList.GroupBy(x => x.Kjbls).Select(g => g.First()).ToList(); | |
| 1835 | 1834 | |
| 1836 | 1835 | // 计算人次数量:1 / 科技老师数量 |
| 1837 | 1836 | var kjbsQuantity = distinctKjbsyjList.Count > 0 ? 1.0m / distinctKjbsyjList.Count : 0; |
| ... | ... | @@ -1852,7 +1851,8 @@ namespace NCC.Extend.LqXhHyhk |
| 1852 | 1851 | WorkMonth = workMonth, |
| 1853 | 1852 | Quantity = kjbsQuantity, |
| 1854 | 1853 | CreateTime = DateTime.Now, |
| 1855 | - IsEffective = StatusEnum.有效.GetHashCode() | |
| 1854 | + IsEffective = StatusEnum.有效.GetHashCode(), | |
| 1855 | + HasBilling = billingRecord ? 1 : 0 | |
| 1856 | 1856 | }); |
| 1857 | 1857 | } |
| 1858 | 1858 | } |
| ... | ... | @@ -1865,9 +1865,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1865 | 1865 | // 如果没有指定耗卡ID,不删除任何记录(因为已经在构建记录列表时跳过了已存在的记录) |
| 1866 | 1866 | if (!string.IsNullOrEmpty(consumeId)) |
| 1867 | 1867 | { |
| 1868 | - await _db.Deleteable<LqPersonTimesRecordEntity>() | |
| 1869 | - .Where(x => x.BusinessId == consumeId && x.BusinessType == "耗卡") | |
| 1870 | - .ExecuteCommandAsync(); | |
| 1868 | + await _db.Deleteable<LqPersonTimesRecordEntity>().Where(x => x.BusinessId == consumeId && x.BusinessType == "耗卡").ExecuteCommandAsync(); | |
| 1871 | 1869 | } |
| 1872 | 1870 | |
| 1873 | 1871 | // 批量插入新记录 | ... | ... |
sql/分析健康师消耗项目数差异.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 分析健康师消耗项目数差异 | |
| 3 | +-- 员工ID: 18566028067 (李芳) | |
| 4 | +-- ============================================ | |
| 5 | + | |
| 6 | +-- 1. 接口统计逻辑(使用耗卡时间,时间范围:2025-11-01 到 2025-11-25 11:55:32) | |
| 7 | +SELECT | |
| 8 | + jksyj.jkszh as EmployeeId, | |
| 9 | + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount_接口逻辑 | |
| 10 | +FROM lq_xh_jksyj jksyj | |
| 11 | +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | |
| 12 | +WHERE jksyj.jkszh = '18566028067' | |
| 13 | + AND jksyj.F_IsEffective = 1 | |
| 14 | + AND hyhk.F_IsEffective = 1 | |
| 15 | + AND hyhk.hksj >= '2025-11-01 00:00:00' | |
| 16 | + AND hyhk.hksj <= '2025-11-25 11:55:32' | |
| 17 | +GROUP BY jksyj.jkszh; | |
| 18 | + | |
| 19 | +-- 2. 整个11月的统计(使用业绩时间) | |
| 20 | +SELECT | |
| 21 | + jksyj.jkszh as EmployeeId, | |
| 22 | + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount_整个11月 | |
| 23 | +FROM lq_xh_jksyj jksyj | |
| 24 | +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') | |
| 25 | + AND jksyj.F_IsEffective = 1 | |
| 26 | + AND DATE_FORMAT(jksyj.yjsj, '%Y%m') = '202511' | |
| 27 | +GROUP BY jksyj.jkszh; | |
| 28 | + | |
| 29 | +-- 3. 查看11月25日之后的记录(可能导致差异的原因) | |
| 30 | +SELECT | |
| 31 | + jksyj.jkszh as EmployeeId, | |
| 32 | + jksyj.F_kdpxNumber as 项目数, | |
| 33 | + jksyj.yjsj as 业绩时间, | |
| 34 | + hyhk.hksj as 耗卡时间, | |
| 35 | + hyhk.F_IsEffective as 耗卡记录是否有效, | |
| 36 | + jksyj.F_IsEffective as 业绩记录是否有效 | |
| 37 | +FROM lq_xh_jksyj jksyj | |
| 38 | +LEFT JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | |
| 39 | +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') | |
| 40 | + AND jksyj.F_IsEffective = 1 | |
| 41 | + AND ( | |
| 42 | + (jksyj.yjsj >= '2025-11-25 11:55:32' AND jksyj.yjsj < '2025-12-01') | |
| 43 | + OR (hyhk.hksj >= '2025-11-25 11:55:32' AND hyhk.hksj < '2025-12-01') | |
| 44 | + ) | |
| 45 | +ORDER BY jksyj.yjsj; | |
| 46 | + | |
| 47 | +-- 4. 查看耗卡记录无效但业绩记录有效的记录(可能导致差异的原因) | |
| 48 | +SELECT | |
| 49 | + jksyj.jkszh as EmployeeId, | |
| 50 | + jksyj.F_kdpxNumber as 项目数, | |
| 51 | + jksyj.yjsj as 业绩时间, | |
| 52 | + hyhk.hksj as 耗卡时间, | |
| 53 | + hyhk.F_IsEffective as 耗卡记录是否有效, | |
| 54 | + jksyj.F_IsEffective as 业绩记录是否有效 | |
| 55 | +FROM lq_xh_jksyj jksyj | |
| 56 | +LEFT JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | |
| 57 | +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') | |
| 58 | + AND jksyj.F_IsEffective = 1 | |
| 59 | + AND (hyhk.F_IsEffective != 1 OR hyhk.F_IsEffective IS NULL) | |
| 60 | + AND jksyj.yjsj >= '2025-11-01' | |
| 61 | + AND jksyj.yjsj < '2025-12-01' | |
| 62 | +ORDER BY jksyj.yjsj; | |
| 63 | + | |
| 64 | +-- 5. 查看耗卡时间和业绩时间不一致的记录(可能导致差异的原因) | |
| 65 | +SELECT | |
| 66 | + jksyj.jkszh as EmployeeId, | |
| 67 | + jksyj.F_kdpxNumber as 项目数, | |
| 68 | + jksyj.yjsj as 业绩时间, | |
| 69 | + hyhk.hksj as 耗卡时间, | |
| 70 | + DATEDIFF(jksyj.yjsj, hyhk.hksj) as 时间差_天, | |
| 71 | + CASE | |
| 72 | + WHEN hyhk.hksj >= '2025-11-01 00:00:00' AND hyhk.hksj <= '2025-11-25 11:55:32' THEN '在接口时间范围内' | |
| 73 | + ELSE '不在接口时间范围内' | |
| 74 | + END as 是否在接口时间范围 | |
| 75 | +FROM lq_xh_jksyj jksyj | |
| 76 | +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | |
| 77 | +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') | |
| 78 | + AND jksyj.F_IsEffective = 1 | |
| 79 | + AND hyhk.F_IsEffective = 1 | |
| 80 | + AND ( | |
| 81 | + (jksyj.yjsj >= '2025-11-01' AND jksyj.yjsj < '2025-12-01') | |
| 82 | + OR (hyhk.hksj >= '2025-11-01' AND hyhk.hksj < '2025-12-01') | |
| 83 | + ) | |
| 84 | + AND ( | |
| 85 | + jksyj.yjsj < '2025-11-01' | |
| 86 | + OR jksyj.yjsj >= '2025-12-01' | |
| 87 | + OR hyhk.hksj < '2025-11-01' | |
| 88 | + OR hyhk.hksj >= '2025-12-01' | |
| 89 | + OR DATEDIFF(jksyj.yjsj, hyhk.hksj) != 0 | |
| 90 | + ) | |
| 91 | +ORDER BY jksyj.yjsj; | |
| 92 | + | |
| 93 | + | ... | ... |
sql/同步健康师业绩表品项分类和品项ID.sql
0 → 100644
| 1 | +-- 同步健康师业绩表和科技老师业绩表中的品项分类和品项ID字段 | |
| 2 | +-- 数据来源:通过关联的品项明细表获取 | |
| 3 | + | |
| 4 | +-- ============================================ | |
| 5 | +-- 健康师业绩表同步 | |
| 6 | +-- ============================================ | |
| 7 | + | |
| 8 | +-- 1. 开单健康师业绩表:从开单品项明细表(lq_kd_pxmx)同步 | |
| 9 | +UPDATE lq_kd_jksyj kd | |
| 10 | +INNER JOIN lq_kd_pxmx px ON px.F_Id = kd.F_kdpxid | |
| 11 | +SET | |
| 12 | + kd.F_ItemCategory = px.F_ItemCategory, | |
| 13 | + kd.F_ItemId = px.px | |
| 14 | +WHERE kd.F_kdpxid IS NOT NULL; | |
| 15 | + | |
| 16 | +-- 2. 耗卡健康师业绩表:从耗卡品项明细表(lq_xh_pxmx)同步 | |
| 17 | +UPDATE lq_xh_jksyj xh | |
| 18 | +INNER JOIN lq_xh_pxmx px ON px.F_Id = xh.F_kdpxid | |
| 19 | +SET | |
| 20 | + xh.F_ItemCategory = px.F_ItemCategory, | |
| 21 | + xh.F_ItemId = px.px | |
| 22 | +WHERE xh.F_kdpxid IS NOT NULL; | |
| 23 | + | |
| 24 | +-- 3. 退卡健康师业绩表:从退卡品项明细表(lq_hytk_mx)同步 | |
| 25 | +-- 注意:F_CardReturn 关联到 lq_hytk_mx.F_Id,F_tkpxid 是项目资料表ID(品项ID) | |
| 26 | +UPDATE lq_hytk_jksyj tk | |
| 27 | +INNER JOIN lq_hytk_mx mx ON mx.F_Id = tk.F_CardReturn | |
| 28 | +SET | |
| 29 | + tk.F_ItemCategory = mx.F_ItemCategory, | |
| 30 | + tk.F_ItemId = mx.px | |
| 31 | +WHERE tk.F_CardReturn IS NOT NULL; | |
| 32 | + | |
| 33 | +-- ============================================ | |
| 34 | +-- 科技老师业绩表同步 | |
| 35 | +-- ============================================ | |
| 36 | + | |
| 37 | +-- 4. 开单科技老师业绩表:从开单品项明细表(lq_kd_pxmx)同步 | |
| 38 | +UPDATE lq_kd_kjbsyj kd | |
| 39 | +INNER JOIN lq_kd_pxmx px ON px.F_Id = kd.F_kdpxid | |
| 40 | +SET | |
| 41 | + kd.F_ItemCategory = px.F_ItemCategory, | |
| 42 | + kd.F_ItemId = px.px | |
| 43 | +WHERE kd.F_kdpxid IS NOT NULL; | |
| 44 | + | |
| 45 | +-- 5. 耗卡科技老师业绩表:从耗卡品项明细表(lq_xh_pxmx)同步 | |
| 46 | +UPDATE lq_xh_kjbsyj xh | |
| 47 | +INNER JOIN lq_xh_pxmx px ON px.F_Id = xh.F_hkpxid | |
| 48 | +SET | |
| 49 | + xh.F_ItemCategory = px.F_ItemCategory, | |
| 50 | + xh.F_ItemId = px.px | |
| 51 | +WHERE xh.F_hkpxid IS NOT NULL; | |
| 52 | + | |
| 53 | +-- 6. 退卡科技老师业绩表:从退卡品项明细表(lq_hytk_mx)同步 | |
| 54 | +-- 注意:F_CardReturn 关联到 lq_hytk_mx.F_Id,F_tkpxid 是项目资料表ID(品项ID) | |
| 55 | +UPDATE lq_hytk_kjbsyj tk | |
| 56 | +INNER JOIN lq_hytk_mx mx ON mx.F_Id = tk.F_CardReturn | |
| 57 | +SET | |
| 58 | + tk.F_ItemCategory = mx.F_ItemCategory, | |
| 59 | + tk.F_ItemId = mx.px | |
| 60 | +WHERE tk.F_CardReturn IS NOT NULL; | |
| 61 | + | ... | ... |
sql/查询员工11月25日之后的记录.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 查询员工在2025-11-25 11:55:32之后的记录 | |
| 3 | +-- 员工ID: 18566028067 (李芳) | |
| 4 | +-- ============================================ | |
| 5 | + | |
| 6 | +-- 1. 查询11月25日11:55:32之后的业绩记录(使用业绩时间) | |
| 7 | +SELECT | |
| 8 | + jksyj.jkszh as 健康师账号, | |
| 9 | + jksyj.jksxm as 健康师姓名, | |
| 10 | + jksyj.F_kdpxNumber as 项目数, | |
| 11 | + jksyj.yjsj as 业绩时间, | |
| 12 | + hyhk.hksj as 耗卡时间, | |
| 13 | + hyhk.F_IsEffective as 耗卡记录是否有效, | |
| 14 | + jksyj.F_IsEffective as 业绩记录是否有效, | |
| 15 | + hyhk.F_Id as 耗卡记录ID | |
| 16 | +FROM lq_xh_jksyj jksyj | |
| 17 | +LEFT JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | |
| 18 | +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') | |
| 19 | + AND jksyj.F_IsEffective = 1 | |
| 20 | + AND jksyj.yjsj > '2025-11-25 11:55:32' | |
| 21 | + AND jksyj.yjsj < '2025-12-01' | |
| 22 | +ORDER BY jksyj.yjsj; | |
| 23 | + | |
| 24 | +-- 2. 查询11月25日11:55:32之后的耗卡记录(使用耗卡时间,这是接口统计使用的条件) | |
| 25 | +SELECT | |
| 26 | + jksyj.jkszh as 健康师账号, | |
| 27 | + jksyj.jksxm as 健康师姓名, | |
| 28 | + jksyj.F_kdpxNumber as 项目数, | |
| 29 | + jksyj.yjsj as 业绩时间, | |
| 30 | + hyhk.hksj as 耗卡时间, | |
| 31 | + hyhk.F_IsEffective as 耗卡记录是否有效, | |
| 32 | + jksyj.F_IsEffective as 业绩记录是否有效, | |
| 33 | + hyhk.F_Id as 耗卡记录ID | |
| 34 | +FROM lq_xh_jksyj jksyj | |
| 35 | +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | |
| 36 | +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') | |
| 37 | + AND jksyj.F_IsEffective = 1 | |
| 38 | + AND hyhk.F_IsEffective = 1 | |
| 39 | + AND hyhk.hksj > '2025-11-25 11:55:32' | |
| 40 | + AND hyhk.hksj < '2025-12-01' | |
| 41 | +ORDER BY hyhk.hksj; | |
| 42 | + | |
| 43 | +-- 3. 统计11月25日11:55:32之后的项目数总和(使用耗卡时间,接口统计逻辑) | |
| 44 | +SELECT | |
| 45 | + jksyj.jkszh as 健康师账号, | |
| 46 | + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as 项目数总和 | |
| 47 | +FROM lq_xh_jksyj jksyj | |
| 48 | +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | |
| 49 | +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') | |
| 50 | + AND jksyj.F_IsEffective = 1 | |
| 51 | + AND hyhk.F_IsEffective = 1 | |
| 52 | + AND hyhk.hksj > '2025-11-25 11:55:32' | |
| 53 | + AND hyhk.hksj < '2025-12-01' | |
| 54 | +GROUP BY jksyj.jkszh; | |
| 55 | + | |
| 56 | + | ... | ... |
sql/查询员工消耗项目数.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 查询员工在指定月份的消耗项目数 | |
| 3 | +-- ============================================ | |
| 4 | +-- 员工ID: 18566028067 | |
| 5 | +-- 查询月份: 2025年11月 (202511) | |
| 6 | +-- ============================================ | |
| 7 | + | |
| 8 | +-- 方式1:从健康师业绩表统计(推荐,使用F_kdpxNumber字段,包含原始+加班+陪同项目数) | |
| 9 | +SELECT | |
| 10 | + jksyj.jks as 健康师ID, | |
| 11 | + jksyj.jksxm as 健康师姓名, | |
| 12 | + jksyj.jkszh as 健康师账号, | |
| 13 | + COALESCE(SUM(jksyj.F_kdpxNumber), 0) as 消耗项目总数, | |
| 14 | + COALESCE(SUM(COALESCE(jksyj.F_OriginalKdpxNumber, jksyj.F_kdpxNumber)), 0) as 原始项目数, | |
| 15 | + COALESCE(SUM(COALESCE(jksyj.F_OvertimeKdpxNumber, 0)), 0) as 加班项目数, | |
| 16 | + COALESCE(SUM(COALESCE(jksyj.F_AccompaniedProjectNumber, 0)), 0) as 陪同项目数 | |
| 17 | +FROM lq_xh_jksyj jksyj | |
| 18 | +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') | |
| 19 | + AND jksyj.F_IsEffective = 1 | |
| 20 | + AND DATE_FORMAT(jksyj.yjsj, '%Y%m') = '202511' | |
| 21 | +GROUP BY jksyj.jks, jksyj.jksxm, jksyj.jkszh; | |
| 22 | + | |
| 23 | +-- 方式2:从品项明细表统计(备用方式,统计F_ProjectNumber字段) | |
| 24 | +SELECT | |
| 25 | + jksyj.jks as 健康师ID, | |
| 26 | + jksyj.jksxm as 健康师姓名, | |
| 27 | + jksyj.jkszh as 健康师账号, | |
| 28 | + COALESCE(SUM(pxmx.F_ProjectNumber), 0) as 消耗项目数 | |
| 29 | +FROM lq_xh_jksyj jksyj | |
| 30 | +INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id | |
| 31 | +INNER JOIN lq_xh_pxmx pxmx ON pxmx.F_ConsumeInfoId = hyhk.F_Id | |
| 32 | +WHERE (jksyj.jks = '18566028067' OR jksyj.jkszh = '18566028067') | |
| 33 | + AND jksyj.F_IsEffective = 1 | |
| 34 | + AND hyhk.F_IsEffective = 1 | |
| 35 | + AND pxmx.F_IsEffective = 1 | |
| 36 | + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '202511' | |
| 37 | +GROUP BY jksyj.jks, jksyj.jksxm, jksyj.jkszh; | |
| 38 | + | |
| 39 | + | ... | ... |
sql/添加业绩表品项分类字段.sql
0 → 100644
| 1 | +-- 为6个业绩表添加品项分类字段和品项ID字段 | |
| 2 | + | |
| 3 | +-- 1. 开单健康师业绩表 | |
| 4 | +ALTER TABLE `lq_kd_jksyj` | |
| 5 | +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_ActivityId`, | |
| 6 | +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; | |
| 7 | + | |
| 8 | +-- 2. 开单科技老师业绩表 | |
| 9 | +ALTER TABLE `lq_kd_kjbsyj` | |
| 10 | +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_ActivityId`, | |
| 11 | +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; | |
| 12 | + | |
| 13 | +-- 3. 耗卡健康师业绩表 | |
| 14 | +ALTER TABLE `lq_xh_jksyj` | |
| 15 | +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_AccompaniedProjectNumber`, | |
| 16 | +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; | |
| 17 | + | |
| 18 | +-- 4. 耗卡科技老师业绩表 | |
| 19 | +ALTER TABLE `lq_xh_kjbsyj` | |
| 20 | +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_IsEffective`, | |
| 21 | +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; | |
| 22 | + | |
| 23 | +-- 5. 退卡健康师业绩表 | |
| 24 | +ALTER TABLE `lq_hytk_jksyj` | |
| 25 | +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_IsEffective`, | |
| 26 | +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; | |
| 27 | + | |
| 28 | +-- 6. 退卡科技老师业绩表 | |
| 29 | +ALTER TABLE `lq_hytk_kjbsyj` | |
| 30 | +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_IsEffective`, | |
| 31 | +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; | |
| 32 | + | ... | ... |