Commit 273e501a67e35ce6595cb3443ae60f8b374df2e6

Authored by “wangming”
1 parent 404cbc89

修复产品上下架功能;修复门店每日统计中消耗业绩和项目数统计逻辑,避免笛卡尔积问题

Showing 23 changed files with 2568 additions and 371 deletions
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryCrInput.cs
@@ -9,50 +9,45 @@ namespace NCC.Extend.Entitys.Dto.LqInventory @@ -9,50 +9,45 @@ namespace NCC.Extend.Entitys.Dto.LqInventory
9 public class LqInventoryCrInput 9 public class LqInventoryCrInput
10 { 10 {
11 /// <summary> 11 /// <summary>
12 - /// 产品名称 12 + /// 产品ID
13 /// </summary> 13 /// </summary>
14 - [Required(ErrorMessage = "产品名称不能为空")]  
15 - [StringLength(100, ErrorMessage = "产品名称长度不能超过100个字符")]  
16 - [Display(Name = "产品名称")]  
17 - public string ProductName { get; set; } 14 + [Required(ErrorMessage = "产品ID不能为空")]
  15 + [StringLength(50, ErrorMessage = "产品ID长度不能超过50个字符")]
  16 + [Display(Name = "产品ID")]
  17 + public string ProductId { get; set; }
18 18
19 /// <summary> 19 /// <summary>
20 - /// 价格 20 + /// 库存数量
21 /// </summary> 21 /// </summary>
22 - [Required(ErrorMessage = "价格不能为空")]  
23 - [Range(0, double.MaxValue, ErrorMessage = "价格不能小于0")]  
24 - [Display(Name = "价格")]  
25 - public decimal Price { get; set; } 22 + [Required(ErrorMessage = "库存数量不能为空")]
  23 + [Range(1, int.MaxValue, ErrorMessage = "库存数量必须大于0")]
  24 + [Display(Name = "库存数量")]
  25 + public int Quantity { get; set; }
26 26
27 /// <summary> 27 /// <summary>
28 - /// 数量 28 + /// 入库时间
29 /// </summary> 29 /// </summary>
30 - [Range(0, int.MaxValue, ErrorMessage = "数量不能小于0")]  
31 - [Display(Name = "数量")]  
32 - public int Quantity { get; set; } = 0; 30 + [Display(Name = "入库时间")]
  31 + public DateTime? StockInTime { get; set; }
33 32
34 /// <summary> 33 /// <summary>
35 - /// 产品类别 34 + /// 生产日期
36 /// </summary> 35 /// </summary>
37 - [Required(ErrorMessage = "产品类别不能为空")]  
38 - [StringLength(50, ErrorMessage = "产品类别长度不能超过50个字符")]  
39 - [Display(Name = "产品类别")]  
40 - public string ProductCategory { get; set; } 36 + [Display(Name = "生产日期")]
  37 + public DateTime? ProductionDate { get; set; }
41 38
42 /// <summary> 39 /// <summary>
43 - /// 归属部门ID 40 + /// 保质期(天数)
44 /// </summary> 41 /// </summary>
45 - [Required(ErrorMessage = "归属部门ID不能为空")]  
46 - [StringLength(50, ErrorMessage = "归属部门ID长度不能超过50个字符")]  
47 - [Display(Name = "归属部门ID")]  
48 - public string DepartmentId { get; set; } 42 + [Range(0, int.MaxValue, ErrorMessage = "保质期不能小于0")]
  43 + [Display(Name = "保质期")]
  44 + public int? ShelfLife { get; set; }
49 45
50 /// <summary> 46 /// <summary>
51 - /// 标准单位 47 + /// 批次号
52 /// </summary> 48 /// </summary>
53 - [Required(ErrorMessage = "标准单位不能为空")]  
54 - [StringLength(20, ErrorMessage = "标准单位长度不能超过20个字符")]  
55 - [Display(Name = "标准单位")]  
56 - public string StandardUnit { get; set; } 49 + [StringLength(100, ErrorMessage = "批次号长度不能超过100个字符")]
  50 + [Display(Name = "批次号")]
  51 + public string BatchNumber { get; set; }
57 } 52 }
58 } 53 }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListOutput.cs
@@ -15,52 +15,64 @@ namespace NCC.Extend.Entitys.Dto.LqInventory @@ -15,52 +15,64 @@ namespace NCC.Extend.Entitys.Dto.LqInventory
15 public string id { get; set; } 15 public string id { get; set; }
16 16
17 /// <summary> 17 /// <summary>
  18 + /// 产品ID
  19 + /// </summary>
  20 + [Display(Name = "产品ID")]
  21 + public string productId { get; set; }
  22 +
  23 + /// <summary>
18 /// 产品名称 24 /// 产品名称
19 /// </summary> 25 /// </summary>
20 [Display(Name = "产品名称")] 26 [Display(Name = "产品名称")]
21 public string productName { get; set; } 27 public string productName { get; set; }
22 28
23 /// <summary> 29 /// <summary>
24 - /// 价格 30 + /// 产品价格
25 /// </summary> 31 /// </summary>
26 - [Display(Name = "价格")]  
27 - public decimal price { get; set; } 32 + [Display(Name = "产品价格")]
  33 + public decimal productPrice { get; set; }
28 34
29 /// <summary> 35 /// <summary>
30 - /// 数量 36 + /// 库存数量
31 /// </summary> 37 /// </summary>
32 - [Display(Name = "数量")] 38 + [Display(Name = "库存数量")]
33 public int quantity { get; set; } 39 public int quantity { get; set; }
34 40
35 /// <summary> 41 /// <summary>
36 - /// 产品类别 42 + /// 已使用数量
  43 + /// </summary>
  44 + [Display(Name = "已使用数量")]
  45 + public int usedQuantity { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 可用数量(库存数量 - 已使用数量)
37 /// </summary> 49 /// </summary>
38 - [Display(Name = "产品类别")]  
39 - public string productCategory { get; set; } 50 + [Display(Name = "可用数量")]
  51 + public int availableQuantity { get; set; }
40 52
41 /// <summary> 53 /// <summary>
42 - /// 归属部门ID 54 + /// 入库时间
43 /// </summary> 55 /// </summary>
44 - [Display(Name = "归属部门ID")]  
45 - public string departmentId { get; set; } 56 + [Display(Name = "入库时间")]
  57 + public DateTime? stockInTime { get; set; }
46 58
47 /// <summary> 59 /// <summary>
48 - /// 部门名称 60 + /// 生产日期
49 /// </summary> 61 /// </summary>
50 - [Display(Name = "部门名称")]  
51 - public string departmentName { get; set; } 62 + [Display(Name = "生产日期")]
  63 + public DateTime? productionDate { get; set; }
52 64
53 /// <summary> 65 /// <summary>
54 - /// 标准单位 66 + /// 保质期(天数)
55 /// </summary> 67 /// </summary>
56 - [Display(Name = "标准单位")]  
57 - public string standardUnit { get; set; } 68 + [Display(Name = "保质期")]
  69 + public int? shelfLife { get; set; }
58 70
59 /// <summary> 71 /// <summary>
60 - /// 总价值 72 + /// 批次号
61 /// </summary> 73 /// </summary>
62 - [Display(Name = "总价值")]  
63 - public decimal totalValue { get; set; } 74 + [Display(Name = "批次号")]
  75 + public string batchNumber { get; set; }
64 76
65 /// <summary> 77 /// <summary>
66 /// 创建人ID 78 /// 创建人ID
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListQueryInput.cs
@@ -10,34 +10,22 @@ namespace NCC.Extend.Entitys.Dto.LqInventory @@ -10,34 +10,22 @@ namespace NCC.Extend.Entitys.Dto.LqInventory
10 public class LqInventoryListQueryInput : PageInputBase 10 public class LqInventoryListQueryInput : PageInputBase
11 { 11 {
12 /// <summary> 12 /// <summary>
13 - /// 产品名称 13 + /// 产品ID
14 /// </summary> 14 /// </summary>
15 - [Display(Name = "产品名称", Description = "根据产品名称筛选")]  
16 - public string ProductName { get; set; }  
17 -  
18 - /// <summary>  
19 - /// 产品类别  
20 - /// </summary>  
21 - [Display(Name = "产品类别", Description = "根据产品类别筛选")]  
22 - public string ProductCategory { get; set; } 15 + [Display(Name = "产品ID", Description = "根据产品ID筛选")]
  16 + public string ProductId { get; set; }
23 17
24 /// <summary> 18 /// <summary>
25 - /// 归属部门ID 19 + /// 产品名称(模糊查询)
26 /// </summary> 20 /// </summary>
27 - [Display(Name = "归属部门ID", Description = "根据归属部门ID筛选")]  
28 - public string DepartmentId { get; set; }  
29 -  
30 - /// <summary>  
31 - /// 价格最小值  
32 - /// </summary>  
33 - [Display(Name = "价格最小值", Description = "价格范围的最小值")]  
34 - public decimal? PriceMin { get; set; } 21 + [Display(Name = "产品名称", Description = "根据产品名称筛选")]
  22 + public string ProductName { get; set; }
35 23
36 /// <summary> 24 /// <summary>
37 - /// 价格最大值 25 + /// 批次号
38 /// </summary> 26 /// </summary>
39 - [Display(Name = "价格最大值", Description = "价格范围的最大值")]  
40 - public decimal? PriceMax { get; set; } 27 + [Display(Name = "批次号", Description = "根据批次号筛选")]
  28 + public string BatchNumber { get; set; }
41 29
42 /// <summary> 30 /// <summary>
43 /// 数量最小值 31 /// 数量最小值
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductCrInput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqProduct
  5 +{
  6 + /// <summary>
  7 + /// 产品创建输入
  8 + /// </summary>
  9 + public class LqProductCrInput
  10 + {
  11 + /// <summary>
  12 + /// 产品名称
  13 + /// </summary>
  14 + [Required(ErrorMessage = "产品名称不能为空")]
  15 + [StringLength(200, ErrorMessage = "产品名称长度不能超过200个字符")]
  16 + [Display(Name = "产品名称")]
  17 + public string ProductName { get; set; }
  18 +
  19 + /// <summary>
  20 + /// 价格
  21 + /// </summary>
  22 + [Required(ErrorMessage = "价格不能为空")]
  23 + [Range(0, double.MaxValue, ErrorMessage = "价格不能小于0")]
  24 + [Display(Name = "价格")]
  25 + public decimal Price { get; set; }
  26 +
  27 + /// <summary>
  28 + /// 产品类别
  29 + /// </summary>
  30 + [StringLength(50, ErrorMessage = "产品类别长度不能超过50个字符")]
  31 + [Display(Name = "产品类别")]
  32 + public string ProductCategory { get; set; }
  33 +
  34 + /// <summary>
  35 + /// 归属部门ID
  36 + /// </summary>
  37 + [StringLength(50, ErrorMessage = "归属部门ID长度不能超过50个字符")]
  38 + [Display(Name = "归属部门ID")]
  39 + public string DepartmentId { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 标准单位
  43 + /// </summary>
  44 + [StringLength(20, ErrorMessage = "标准单位长度不能超过20个字符")]
  45 + [Display(Name = "标准单位")]
  46 + public string StandardUnit { get; set; }
  47 +
  48 + /// <summary>
  49 + /// 上架状态(1:上架 0:下架)
  50 + /// </summary>
  51 + [Display(Name = "上架状态")]
  52 + public int OnShelfStatus { get; set; } = 1;
  53 +
  54 + /// <summary>
  55 + /// 统计分类
  56 + /// </summary>
  57 + [StringLength(50, ErrorMessage = "统计分类长度不能超过50个字符")]
  58 + [Display(Name = "统计分类")]
  59 + public string StatisticsCategory { get; set; }
  60 +
  61 + /// <summary>
  62 + /// 归属仓库
  63 + /// </summary>
  64 + [StringLength(100, ErrorMessage = "归属仓库长度不能超过100个字符")]
  65 + [Display(Name = "归属仓库")]
  66 + public string Warehouse { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 单位换算
  70 + /// </summary>
  71 + [StringLength(200, ErrorMessage = "单位换算长度不能超过200个字符")]
  72 + [Display(Name = "单位换算")]
  73 + public string UnitConversion { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 供应商名称
  77 + /// </summary>
  78 + [StringLength(200, ErrorMessage = "供应商名称长度不能超过200个字符")]
  79 + [Display(Name = "供应商名称")]
  80 + public string SupplierName { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 合同签订日期
  84 + /// </summary>
  85 + [Display(Name = "合同签订日期")]
  86 + public DateTime? ContractSignDate { get; set; }
  87 +
  88 + /// <summary>
  89 + /// 合同结束日期
  90 + /// </summary>
  91 + [Display(Name = "合同结束日期")]
  92 + public DateTime? ContractEndDate { get; set; }
  93 +
  94 + /// <summary>
  95 + /// 备注
  96 + /// </summary>
  97 + [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
  98 + [Display(Name = "备注")]
  99 + public string Remark { get; set; }
  100 + }
  101 +}
  102 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInfoOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqProduct
  4 +{
  5 + /// <summary>
  6 + /// 产品详情输出
  7 + /// </summary>
  8 + public class LqProductInfoOutput
  9 + {
  10 + /// <summary>
  11 + /// 产品ID
  12 + /// </summary>
  13 + public string id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 产品名称
  17 + /// </summary>
  18 + public string productName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 价格
  22 + /// </summary>
  23 + public decimal price { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 产品类别
  27 + /// </summary>
  28 + public string productCategory { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 归属部门ID
  32 + /// </summary>
  33 + public string departmentId { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 部门名称
  37 + /// </summary>
  38 + public string departmentName { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 标准单位
  42 + /// </summary>
  43 + public string standardUnit { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 上架状态(1:上架 0:下架)
  47 + /// </summary>
  48 + public int onShelfStatus { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 统计分类
  52 + /// </summary>
  53 + public string statisticsCategory { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 归属仓库
  57 + /// </summary>
  58 + public string warehouse { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 单位换算
  62 + /// </summary>
  63 + public string unitConversion { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 供应商名称
  67 + /// </summary>
  68 + public string supplierName { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 合同签订日期
  72 + /// </summary>
  73 + public DateTime? contractSignDate { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 合同结束日期
  77 + /// </summary>
  78 + public DateTime? contractEndDate { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 备注
  82 + /// </summary>
  83 + public string remark { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 创建人ID
  87 + /// </summary>
  88 + public string createUser { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 创建人姓名
  92 + /// </summary>
  93 + public string createUserName { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 创建时间
  97 + /// </summary>
  98 + public DateTime createTime { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 更新人ID
  102 + /// </summary>
  103 + public string updateUser { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 更新人姓名
  107 + /// </summary>
  108 + public string updateUserName { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 更新时间
  112 + /// </summary>
  113 + public DateTime? updateTime { get; set; }
  114 + }
  115 +}
  116 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInventoryDetailOutput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqProduct
  5 +{
  6 + /// <summary>
  7 + /// 产品库存详情输出
  8 + /// </summary>
  9 + public class LqProductInventoryDetailOutput
  10 + {
  11 + /// <summary>
  12 + /// 产品信息
  13 + /// </summary>
  14 + public LqProductInfoOutput productInfo { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 库存列表
  18 + /// </summary>
  19 + public List<ProductInventoryItem> inventoryList { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 使用记录列表
  23 + /// </summary>
  24 + public List<ProductUsageItem> usageList { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 总库存数量
  28 + /// </summary>
  29 + public int totalInventory { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 已使用数量
  33 + /// </summary>
  34 + public int totalUsage { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 可用库存
  38 + /// </summary>
  39 + public int availableInventory { get; set; }
  40 + }
  41 +
  42 + /// <summary>
  43 + /// 产品库存项
  44 + /// </summary>
  45 + public class ProductInventoryItem
  46 + {
  47 + /// <summary>
  48 + /// 库存ID
  49 + /// </summary>
  50 + public string id { get; set; }
  51 +
  52 + /// <summary>
  53 + /// 库存数量
  54 + /// </summary>
  55 + public int quantity { get; set; }
  56 +
  57 + /// <summary>
  58 + /// 入库时间
  59 + /// </summary>
  60 + public DateTime? stockInTime { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 生产日期
  64 + /// </summary>
  65 + public DateTime? productionDate { get; set; }
  66 +
  67 + /// <summary>
  68 + /// 保质期(天数)
  69 + /// </summary>
  70 + public int? shelfLife { get; set; }
  71 +
  72 + /// <summary>
  73 + /// 批次号
  74 + /// </summary>
  75 + public string batchNumber { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 是否有效
  79 + /// </summary>
  80 + public int isEffective { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 创建时间
  84 + /// </summary>
  85 + public DateTime createTime { get; set; }
  86 + }
  87 +
  88 + /// <summary>
  89 + /// 产品使用记录项
  90 + /// </summary>
  91 + public class ProductUsageItem
  92 + {
  93 + /// <summary>
  94 + /// 使用记录ID
  95 + /// </summary>
  96 + public string id { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 门店ID
  100 + /// </summary>
  101 + public string storeId { get; set; }
  102 +
  103 + /// <summary>
  104 + /// 门店名称
  105 + /// </summary>
  106 + public string storeName { get; set; }
  107 +
  108 + /// <summary>
  109 + /// 使用时间
  110 + /// </summary>
  111 + public DateTime usageTime { get; set; }
  112 +
  113 + /// <summary>
  114 + /// 使用数量
  115 + /// </summary>
  116 + public int usageQuantity { get; set; }
  117 +
  118 + /// <summary>
  119 + /// 关联消耗ID
  120 + /// </summary>
  121 + public string relatedConsumeId { get; set; }
  122 +
  123 + /// <summary>
  124 + /// 创建时间
  125 + /// </summary>
  126 + public DateTime createTime { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 是否有效
  130 + /// </summary>
  131 + public int isEffective { get; set; }
  132 + }
  133 +}
  134 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductListOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqProduct
  4 +{
  5 + /// <summary>
  6 + /// 产品列表输出
  7 + /// </summary>
  8 + public class LqProductListOutput
  9 + {
  10 + /// <summary>
  11 + /// 产品ID
  12 + /// </summary>
  13 + public string id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 产品名称
  17 + /// </summary>
  18 + public string productName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 价格
  22 + /// </summary>
  23 + public decimal price { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 产品类别
  27 + /// </summary>
  28 + public string productCategory { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 归属部门ID
  32 + /// </summary>
  33 + public string departmentId { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 部门名称
  37 + /// </summary>
  38 + public string departmentName { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 标准单位
  42 + /// </summary>
  43 + public string standardUnit { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 上架状态(1:上架 0:下架)
  47 + /// </summary>
  48 + public int onShelfStatus { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 统计分类
  52 + /// </summary>
  53 + public string statisticsCategory { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 归属仓库
  57 + /// </summary>
  58 + public string warehouse { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 单位换算
  62 + /// </summary>
  63 + public string unitConversion { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 供应商名称
  67 + /// </summary>
  68 + public string supplierName { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 合同签订日期
  72 + /// </summary>
  73 + public DateTime? contractSignDate { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 合同结束日期
  77 + /// </summary>
  78 + public DateTime? contractEndDate { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 备注
  82 + /// </summary>
  83 + public string remark { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 现有库存数量(所有有效库存的总和)
  87 + /// </summary>
  88 + public int currentInventory { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 创建人ID
  92 + /// </summary>
  93 + public string createUser { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 创建人姓名
  97 + /// </summary>
  98 + public string createUserName { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 创建时间
  102 + /// </summary>
  103 + public DateTime createTime { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 更新人ID
  107 + /// </summary>
  108 + public string updateUser { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 更新人姓名
  112 + /// </summary>
  113 + public string updateUserName { get; set; }
  114 +
  115 + /// <summary>
  116 + /// 更新时间
  117 + /// </summary>
  118 + public DateTime? updateTime { get; set; }
  119 + }
  120 +}
  121 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqProduct
  4 +{
  5 + /// <summary>
  6 + /// 产品列表查询输入
  7 + /// </summary>
  8 + public class LqProductListQueryInput : PageInputBase
  9 + {
  10 + /// <summary>
  11 + /// 产品名称(模糊查询)
  12 + /// </summary>
  13 + public string ProductName { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 产品类别
  17 + /// </summary>
  18 + public string ProductCategory { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 归属部门ID
  22 + /// </summary>
  23 + public string DepartmentId { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 上架状态(1:上架 0:下架)
  27 + /// </summary>
  28 + public int? OnShelfStatus { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 统计分类
  32 + /// </summary>
  33 + public string StatisticsCategory { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 归属仓库
  37 + /// </summary>
  38 + public string Warehouse { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 供应商名称(模糊查询)
  42 + /// </summary>
  43 + public string SupplierName { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 价格最小值
  47 + /// </summary>
  48 + public decimal? PriceMin { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 价格最大值
  52 + /// </summary>
  53 + public decimal? PriceMax { get; set; }
  54 + }
  55 +}
  56 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductSelectOutput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqProduct
  2 +{
  3 + /// <summary>
  4 + /// 产品下拉选择输出
  5 + /// </summary>
  6 + public class LqProductSelectOutput
  7 + {
  8 + /// <summary>
  9 + /// 产品ID
  10 + /// </summary>
  11 + public string id { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 产品名称
  15 + /// </summary>
  16 + public string productName { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 价格
  20 + /// </summary>
  21 + public decimal price { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 标准单位
  25 + /// </summary>
  26 + public string standardUnit { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 现有库存数量
  30 + /// </summary>
  31 + public int currentInventory { get; set; }
  32 + }
  33 +}
  34 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductToggleShelfInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqProduct
  4 +{
  5 + /// <summary>
  6 + /// 产品上下架输入
  7 + /// </summary>
  8 + public class LqProductToggleShelfInput
  9 + {
  10 + /// <summary>
  11 + /// 产品ID
  12 + /// </summary>
  13 + [Required(ErrorMessage = "产品ID不能为空")]
  14 + [Display(Name = "产品ID")]
  15 + public string ProductId { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 上架状态(1:上架 0:下架)
  19 + /// </summary>
  20 + [Required(ErrorMessage = "上架状态不能为空")]
  21 + [Range(0, 1, ErrorMessage = "上架状态只能是0(下架)或1(上架)")]
  22 + [Display(Name = "上架状态")]
  23 + public int OnShelfStatus { get; set; }
  24 + }
  25 +}
  26 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductUpInput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqProduct
  5 +{
  6 + /// <summary>
  7 + /// 产品更新输入
  8 + /// </summary>
  9 + public class LqProductUpInput
  10 + {
  11 + /// <summary>
  12 + /// 产品ID
  13 + /// </summary>
  14 + [Required(ErrorMessage = "产品ID不能为空")]
  15 + [Display(Name = "产品ID")]
  16 + public string Id { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 产品名称
  20 + /// </summary>
  21 + [Required(ErrorMessage = "产品名称不能为空")]
  22 + [StringLength(200, ErrorMessage = "产品名称长度不能超过200个字符")]
  23 + [Display(Name = "产品名称")]
  24 + public string ProductName { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 价格
  28 + /// </summary>
  29 + [Required(ErrorMessage = "价格不能为空")]
  30 + [Range(0, double.MaxValue, ErrorMessage = "价格不能小于0")]
  31 + [Display(Name = "价格")]
  32 + public decimal Price { get; set; }
  33 +
  34 + /// <summary>
  35 + /// 产品类别
  36 + /// </summary>
  37 + [StringLength(50, ErrorMessage = "产品类别长度不能超过50个字符")]
  38 + [Display(Name = "产品类别")]
  39 + public string ProductCategory { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 归属部门ID
  43 + /// </summary>
  44 + [StringLength(50, ErrorMessage = "归属部门ID长度不能超过50个字符")]
  45 + [Display(Name = "归属部门ID")]
  46 + public string DepartmentId { get; set; }
  47 +
  48 + /// <summary>
  49 + /// 标准单位
  50 + /// </summary>
  51 + [StringLength(20, ErrorMessage = "标准单位长度不能超过20个字符")]
  52 + [Display(Name = "标准单位")]
  53 + public string StandardUnit { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 上架状态(1:上架 0:下架)
  57 + /// </summary>
  58 + [Display(Name = "上架状态")]
  59 + public int OnShelfStatus { get; set; } = 1;
  60 +
  61 + /// <summary>
  62 + /// 统计分类
  63 + /// </summary>
  64 + [StringLength(50, ErrorMessage = "统计分类长度不能超过50个字符")]
  65 + [Display(Name = "统计分类")]
  66 + public string StatisticsCategory { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 归属仓库
  70 + /// </summary>
  71 + [StringLength(100, ErrorMessage = "归属仓库长度不能超过100个字符")]
  72 + [Display(Name = "归属仓库")]
  73 + public string Warehouse { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 单位换算
  77 + /// </summary>
  78 + [StringLength(200, ErrorMessage = "单位换算长度不能超过200个字符")]
  79 + [Display(Name = "单位换算")]
  80 + public string UnitConversion { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 供应商名称
  84 + /// </summary>
  85 + [StringLength(200, ErrorMessage = "供应商名称长度不能超过200个字符")]
  86 + [Display(Name = "供应商名称")]
  87 + public string SupplierName { get; set; }
  88 +
  89 + /// <summary>
  90 + /// 合同签订日期
  91 + /// </summary>
  92 + [Display(Name = "合同签订日期")]
  93 + public DateTime? ContractSignDate { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 合同结束日期
  97 + /// </summary>
  98 + [Display(Name = "合同结束日期")]
  99 + public DateTime? ContractEndDate { get; set; }
  100 +
  101 + /// <summary>
  102 + /// 备注
  103 + /// </summary>
  104 + [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
  105 + [Display(Name = "备注")]
  106 + public string Remark { get; set; }
  107 + }
  108 +}
  109 +
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory/LqInventoryEntity.cs
@@ -18,40 +18,46 @@ namespace NCC.Extend.Entitys.lq_inventory @@ -18,40 +18,46 @@ namespace NCC.Extend.Entitys.lq_inventory
18 public string Id { get; set; } 18 public string Id { get; set; }
19 19
20 /// <summary> 20 /// <summary>
21 - /// 产品名称 21 + /// 产品ID(关联产品表)
22 /// </summary> 22 /// </summary>
23 - [SugarColumn(ColumnName = "F_ProductName")]  
24 - public string ProductName { get; set; } 23 + [SugarColumn(ColumnName = "F_ProductId")]
  24 + public string ProductId { get; set; }
25 25
26 /// <summary> 26 /// <summary>
27 - /// 价格 27 + /// 库存数量
28 /// </summary> 28 /// </summary>
29 - [SugarColumn(ColumnName = "F_Price")]  
30 - public decimal Price { get; set; } 29 + [SugarColumn(ColumnName = "F_Quantity")]
  30 + public int Quantity { get; set; } = 0;
31 31
32 /// <summary> 32 /// <summary>
33 - /// 数量 33 + /// 入库时间
34 /// </summary> 34 /// </summary>
35 - [SugarColumn(ColumnName = "F_Quantity")]  
36 - public int Quantity { get; set; } = 0; 35 + [SugarColumn(ColumnName = "F_StockInTime")]
  36 + public DateTime? StockInTime { get; set; }
37 37
38 /// <summary> 38 /// <summary>
39 - /// 产品类别 39 + /// 生产日期
40 /// </summary> 40 /// </summary>
41 - [SugarColumn(ColumnName = "F_ProductCategory")]  
42 - public string ProductCategory { get; set; } 41 + [SugarColumn(ColumnName = "F_ProductionDate")]
  42 + public DateTime? ProductionDate { get; set; }
43 43
44 /// <summary> 44 /// <summary>
45 - /// 归属部门ID 45 + /// 保质期(天数)
46 /// </summary> 46 /// </summary>
47 - [SugarColumn(ColumnName = "F_DepartmentId")]  
48 - public string DepartmentId { get; set; } 47 + [SugarColumn(ColumnName = "F_ShelfLife")]
  48 + public int? ShelfLife { get; set; }
49 49
50 /// <summary> 50 /// <summary>
51 - /// 标准单位 51 + /// 批次号
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_BatchNumber")]
  54 + public string BatchNumber { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 是否有效(1:有效 0:无效)
52 /// </summary> 58 /// </summary>
53 - [SugarColumn(ColumnName = "F_StandardUnit")]  
54 - public string StandardUnit { get; set; } 59 + [SugarColumn(ColumnName = "F_IsEffective")]
  60 + public int IsEffective { get; set; } = 1;
55 61
56 /// <summary> 62 /// <summary>
57 /// 创建人ID 63 /// 创建人ID
@@ -76,11 +82,5 @@ namespace NCC.Extend.Entitys.lq_inventory @@ -76,11 +82,5 @@ namespace NCC.Extend.Entitys.lq_inventory
76 /// </summary> 82 /// </summary>
77 [SugarColumn(ColumnName = "F_UpdateTime")] 83 [SugarColumn(ColumnName = "F_UpdateTime")]
78 public DateTime? UpdateTime { get; set; } 84 public DateTime? UpdateTime { get; set; }
79 -  
80 - /// <summary>  
81 - /// 是否有效(1:有效 0:无效)  
82 - /// </summary>  
83 - [SugarColumn(ColumnName = "F_IsEffective")]  
84 - public int IsEffective { get; set; } = 1;  
85 } 85 }
86 } 86 }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_product/LqProductEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_product
  6 +{
  7 + /// <summary>
  8 + /// 产品表
  9 + /// </summary>
  10 + [SugarTable("lq_product")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqProductEntity
  13 + {
  14 + /// <summary>
  15 + /// 产品ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 产品名称
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_ProductName")]
  24 + public string ProductName { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 价格
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_Price")]
  30 + public decimal Price { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 产品类别
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_ProductCategory")]
  36 + public string ProductCategory { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 归属部门ID
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_DepartmentId")]
  42 + public string DepartmentId { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 标准单位
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_StandardUnit")]
  48 + public string StandardUnit { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 上架状态(1:上架 0:下架)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_OnShelfStatus")]
  54 + public int OnShelfStatus { get; set; } = 1;
  55 +
  56 + /// <summary>
  57 + /// 统计分类
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_StatisticsCategory")]
  60 + public string StatisticsCategory { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 归属仓库
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_Warehouse")]
  66 + public string Warehouse { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 单位换算
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_UnitConversion")]
  72 + public string UnitConversion { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 供应商名称
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_SupplierName")]
  78 + public string SupplierName { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 合同签订日期
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_ContractSignDate")]
  84 + public DateTime? ContractSignDate { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 合同结束日期
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_ContractEndDate")]
  90 + public DateTime? ContractEndDate { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 备注
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_Remark")]
  96 + public string Remark { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 创建人ID
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_CreateUser")]
  102 + public string CreateUser { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 创建时间
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_CreateTime")]
  108 + public DateTime CreateTime { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 更新人ID
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_UpdateUser")]
  114 + public string UpdateUser { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 更新时间
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_UpdateTime")]
  120 + public DateTime? UpdateTime { get; set; }
  121 + }
  122 +}
  123 +
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqInventoryUsage/ILqInventoryUsageService.cs
@@ -9,54 +9,6 @@ namespace NCC.Extend.Interfaces.LqInventoryUsage @@ -9,54 +9,6 @@ namespace NCC.Extend.Interfaces.LqInventoryUsage
9 /// </summary> 9 /// </summary>
10 public interface ILqInventoryUsageService 10 public interface ILqInventoryUsageService
11 { 11 {
12 - /// <summary>  
13 - /// 添加库存使用记录  
14 - /// </summary>  
15 - /// <param name="input">创建输入</param>  
16 - /// <returns></returns>  
17 - Task CreateAsync(LqInventoryUsageCrInput input);  
18 12
19 - /// <summary>  
20 - /// 作废库存使用记录  
21 - /// </summary>  
22 - /// <param name="id">使用记录ID</param>  
23 - /// <param name="remarks">作废备注</param>  
24 - /// <returns></returns>  
25 - Task CancelAsync(string id, string remarks = null);  
26 -  
27 - /// <summary>  
28 - /// 获取使用记录列表  
29 - /// </summary>  
30 - /// <param name="input">查询输入</param>  
31 - /// <returns></returns>  
32 - Task<dynamic> GetListAsync(LqInventoryUsageListQueryInput input);  
33 -  
34 - /// <summary>  
35 - /// 统计时间周期内每个产品的使用数量  
36 - /// </summary>  
37 - /// <param name="input">统计查询输入</param>  
38 - /// <returns></returns>  
39 - Task<dynamic> GetProductUsageStatisticsAsync(LqInventoryUsageStatisticsInput input);  
40 -  
41 - /// <summary>  
42 - /// 统计时间周期内每个门店的使用情况  
43 - /// </summary>  
44 - /// <param name="input">统计查询输入</param>  
45 - /// <returns></returns>  
46 - Task<dynamic> GetStoreUsageStatisticsAsync(LqInventoryUsageStatisticsInput input);  
47 -  
48 - /// <summary>  
49 - /// 统计时间周期内使用趋势  
50 - /// </summary>  
51 - /// <param name="input">统计查询输入</param>  
52 - /// <returns></returns>  
53 - Task<dynamic> GetUsageTrendStatisticsAsync(LqInventoryUsageStatisticsInput input);  
54 -  
55 - /// <summary>  
56 - /// 统计产品使用排行榜  
57 - /// </summary>  
58 - /// <param name="input">统计查询输入</param>  
59 - /// <returns></returns>  
60 - Task<dynamic> GetProductUsageRankingAsync(LqInventoryUsageStatisticsInput input);  
61 } 13 }
62 } 14 }
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqProduct/ILqProductService.cs 0 → 100644
  1 +using System.Threading.Tasks;
  2 +using NCC.Extend.Entitys.Dto.LqProduct;
  3 +using NCC.Common.Filter;
  4 +
  5 +namespace NCC.Extend.Interfaces.LqProduct
  6 +{
  7 + /// <summary>
  8 + /// 产品服务接口
  9 + /// </summary>
  10 + public interface ILqProductService
  11 + {
  12 + /// <summary>
  13 + /// 添加产品信息
  14 + /// </summary>
  15 + /// <param name="input">创建输入</param>
  16 + /// <returns></returns>
  17 + Task CreateAsync(LqProductCrInput input);
  18 +
  19 + /// <summary>
  20 + /// 更新产品信息
  21 + /// </summary>
  22 + /// <param name="input">更新输入</param>
  23 + /// <returns></returns>
  24 + Task UpdateAsync(LqProductUpInput input);
  25 +
  26 + /// <summary>
  27 + /// 获取产品列表(包含现有库存)
  28 + /// </summary>
  29 + /// <param name="input">查询输入</param>
  30 + /// <returns></returns>
  31 + Task<dynamic> GetListAsync(LqProductListQueryInput input);
  32 +
  33 + /// <summary>
  34 + /// 获取产品详情
  35 + /// </summary>
  36 + /// <param name="id">产品ID</param>
  37 + /// <returns></returns>
  38 + Task<dynamic> GetInfoAsync(string id);
  39 +
  40 + /// <summary>
  41 + /// 获取产品下拉列表
  42 + /// </summary>
  43 + /// <param name="productName">产品名称(可选,模糊查询)</param>
  44 + /// <param name="onShelfStatus">上架状态(可选,1:上架 0:下架)</param>
  45 + /// <returns></returns>
  46 + Task<dynamic> GetSelectListAsync(string productName = null, int? onShelfStatus = 1);
  47 +
  48 + /// <summary>
  49 + /// 作废产品
  50 + /// </summary>
  51 + /// <param name="id">产品ID</param>
  52 + /// <param name="remarks">作废备注</param>
  53 + /// <returns></returns>
  54 + Task CancelAsync(string id, string remarks = null);
  55 +
  56 + /// <summary>
  57 + /// 产品上下架
  58 + /// </summary>
  59 + /// <param name="input">上下架输入</param>
  60 + /// <returns></returns>
  61 + Task ToggleShelfAsync(LqProductToggleShelfInput input);
  62 + }
  63 +}
  64 +
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
@@ -129,7 +129,7 @@ namespace NCC.Extend @@ -129,7 +129,7 @@ namespace NCC.Extend
129 } 129 }
130 130
131 // SQL查询:获取门店每日运营数据(只统计zxzt为"开店"的门店) 131 // SQL查询:获取门店每日运营数据(只统计zxzt为"开店"的门店)
132 - // 优化:使用 LEFT JOIN 一次性统计所有项目数,避免多个子查询 132 + // 注意:避免笛卡尔积问题,消耗业绩和项目数分别统计,避免重复计算
133 var sql = $@" 133 var sql = $@"
134 SELECT 134 SELECT
135 consume.Md as StoreId, 135 consume.Md as StoreId,
@@ -138,23 +138,40 @@ namespace NCC.Extend @@ -138,23 +138,40 @@ namespace NCC.Extend
138 COALESCE(COUNT(DISTINCT consume.Hy), 0) as HeadCount, 138 COALESCE(COUNT(DISTINCT consume.Hy), 0) as HeadCount,
139 -- 人次(日度去重客户数) 139 -- 人次(日度去重客户数)
140 COALESCE(COUNT(DISTINCT CONCAT(consume.Hy, '-', DATE_FORMAT(consume.Hksj, '%Y-%m-%d'))), 0) as PersonCount, 140 COALESCE(COUNT(DISTINCT CONCAT(consume.Hy, '-', DATE_FORMAT(consume.Hksj, '%Y-%m-%d'))), 0) as PersonCount,
141 - -- 项目数(消耗的项目总数,从品项明细表统计)  
142 - COALESCE(SUM(project.F_ProjectNumber), 0) as ProjectCount,  
143 - -- 消耗总项目数(健康师业绩表统计,包含原始+加班+陪同)  
144 - COALESCE(SUM(COALESCE(jksyj.F_kdpxNumber, 0)), 0) as TotalProjectCount,  
145 - -- 消耗原始项目数(健康师业绩表统计)  
146 - COALESCE(SUM(COALESCE(jksyj.F_OriginalKdpxNumber, jksyj.F_kdpxNumber, 0)), 0) as OriginalProjectCount,  
147 - -- 消耗加班项目数(健康师业绩表统计)  
148 - COALESCE(SUM(COALESCE(jksyj.F_OvertimeKdpxNumber, 0)), 0) as OvertimeProjectCount,  
149 - -- 消耗陪同项目数(健康师业绩表统计)  
150 - COALESCE(SUM(COALESCE(jksyj.F_AccompaniedProjectNumber, 0)), 0) as AccompaniedProjectCount,  
151 - -- 消耗业绩(总金额)  
152 - COALESCE(SUM(project.F_TotalPrice), 0) as ConsumePerformance 141 + -- 项目数(消耗的项目总数,从品项明细表统计原始项目数,与健康师业绩表的原始项目数保持一致)
  142 + COALESCE(SUM((
  143 + SELECT COALESCE(SUM(COALESCE(px.F_OriginalProjectNumber, px.F_ProjectNumber, 0)), 0)
  144 + FROM lq_xh_pxmx px
  145 + WHERE px.F_ConsumeInfoId = consume.F_Id AND px.F_IsEffective = 1
  146 + )), 0) as ProjectCount,
  147 + -- 消耗总项目数(健康师业绩表统计,包含原始+加班+陪同,使用SUM聚合子查询结果)
  148 + COALESCE(SUM((
  149 + SELECT COALESCE(SUM(COALESCE(j.F_kdpxNumber, 0)), 0)
  150 + FROM lq_xh_jksyj j
  151 + WHERE j.glkdbh = consume.F_Id AND j.F_IsEffective = 1
  152 + )), 0) as TotalProjectCount,
  153 + -- 消耗原始项目数(健康师业绩表统计,使用SUM聚合子查询结果)
  154 + COALESCE(SUM((
  155 + SELECT COALESCE(SUM(COALESCE(j.F_OriginalKdpxNumber, j.F_kdpxNumber, 0)), 0)
  156 + FROM lq_xh_jksyj j
  157 + WHERE j.glkdbh = consume.F_Id AND j.F_IsEffective = 1
  158 + )), 0) as OriginalProjectCount,
  159 + -- 消耗加班项目数(健康师业绩表统计,使用SUM聚合子查询结果)
  160 + COALESCE(SUM((
  161 + SELECT COALESCE(SUM(COALESCE(j.F_OvertimeKdpxNumber, 0)), 0)
  162 + FROM lq_xh_jksyj j
  163 + WHERE j.glkdbh = consume.F_Id AND j.F_IsEffective = 1
  164 + )), 0) as OvertimeProjectCount,
  165 + -- 消耗陪同项目数(健康师业绩表统计,使用SUM聚合子查询结果)
  166 + COALESCE(SUM((
  167 + SELECT COALESCE(SUM(COALESCE(j.F_AccompaniedProjectNumber, 0)), 0)
  168 + FROM lq_xh_jksyj j
  169 + WHERE j.glkdbh = consume.F_Id AND j.F_IsEffective = 1
  170 + )), 0) as AccompaniedProjectCount,
  171 + -- 消耗业绩(总金额,直接从主表xfje字段统计,避免子查询和JOIN导致的性能问题)
  172 + COALESCE(SUM(consume.xfje), 0) as ConsumePerformance
153 FROM lq_xh_hyhk consume 173 FROM lq_xh_hyhk consume
154 INNER JOIN lq_mdxx store ON consume.Md = store.F_Id AND store.zxzt = '开店' 174 INNER JOIN lq_mdxx store ON consume.Md = store.F_Id AND store.zxzt = '开店'
155 - LEFT JOIN lq_xh_pxmx project ON consume.F_Id = project.F_ConsumeInfoId AND project.F_IsEffective = 1  
156 - LEFT JOIN lq_xh_jksyj jksyj ON jksyj.glkdbh = consume.F_Id  
157 - AND jksyj.F_IsEffective = 1  
158 WHERE consume.F_IsEffective = 1 175 WHERE consume.F_IsEffective = 1
159 AND consume.Hksj >= '{startDate:yyyy-MM-dd} 00:00:00' 176 AND consume.Hksj >= '{startDate:yyyy-MM-dd} 00:00:00'
160 AND consume.Hksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' 177 AND consume.Hksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
1 using System; 1 using System;
  2 +using System.Linq;
2 using System.Threading.Tasks; 3 using System.Threading.Tasks;
3 using Microsoft.AspNetCore.Mvc; 4 using Microsoft.AspNetCore.Mvc;
4 using Microsoft.Extensions.Logging; 5 using Microsoft.Extensions.Logging;
@@ -10,6 +11,8 @@ using NCC.DynamicApiController; @@ -10,6 +11,8 @@ using NCC.DynamicApiController;
10 using NCC.Extend.Entitys.Dto.LqInventory; 11 using NCC.Extend.Entitys.Dto.LqInventory;
11 using NCC.Extend.Entitys.Enum; 12 using NCC.Extend.Entitys.Enum;
12 using NCC.Extend.Entitys.lq_inventory; 13 using NCC.Extend.Entitys.lq_inventory;
  14 +using NCC.Extend.Entitys.lq_inventory_usage;
  15 +using NCC.Extend.Entitys.lq_product;
13 using NCC.Extend.Interfaces.LqInventory; 16 using NCC.Extend.Interfaces.LqInventory;
14 using NCC.FriendlyException; 17 using NCC.FriendlyException;
15 using NCC.System.Entitys.Permission; 18 using NCC.System.Entitys.Permission;
@@ -42,39 +45,58 @@ namespace NCC.Extend @@ -42,39 +45,58 @@ namespace NCC.Extend
42 _db = db; 45 _db = db;
43 } 46 }
44 47
45 - #region 创建库存 48 + #region 添加库存信息
46 /// <summary> 49 /// <summary>
47 - /// 创建库存 50 + /// 添加库存信息
48 /// </summary> 51 /// </summary>
  52 + /// <remarks>
  53 + /// 针对某个产品添加库存记录
  54 + ///
  55 + /// 示例请求:
  56 + /// ```json
  57 + /// {
  58 + /// "productId": "产品ID",
  59 + /// "quantity": 100,
  60 + /// "stockInTime": "2024-01-01",
  61 + /// "productionDate": "2023-12-01",
  62 + /// "shelfLife": 365,
  63 + /// "batchNumber": "批次号001"
  64 + /// }
  65 + /// ```
  66 + /// </remarks>
49 /// <param name="input">创建输入</param> 67 /// <param name="input">创建输入</param>
50 /// <returns>创建结果</returns> 68 /// <returns>创建结果</returns>
  69 + /// <response code="200">创建成功</response>
  70 + /// <response code="400">产品不存在或参数错误</response>
  71 + /// <response code="500">服务器错误</response>
51 [HttpPost("Create")] 72 [HttpPost("Create")]
52 public async Task CreateAsync([FromBody] LqInventoryCrInput input) 73 public async Task CreateAsync([FromBody] LqInventoryCrInput input)
53 { 74 {
54 try 75 try
55 { 76 {
56 - // 检查产品名称是否已存在  
57 - var existingProduct = await _db.Queryable<LqInventoryEntity>()  
58 - .Where(x => x.ProductName == input.ProductName && x.IsEffective == StatusEnum.有效.GetHashCode()) 77 + // 验证产品是否存在
  78 + var product = await _db.Queryable<LqProductEntity>()
  79 + .Where(x => x.Id == input.ProductId)
59 .FirstAsync(); 80 .FirstAsync();
60 - if (existingProduct != null) 81 +
  82 + if (product == null)
61 { 83 {
62 - throw NCCException.Oh($"产品名称'{input.ProductName}'已存在"); 84 + throw NCCException.Oh("产品不存在");
63 } 85 }
64 86
65 // 创建库存记录 87 // 创建库存记录
66 var inventoryEntity = new LqInventoryEntity 88 var inventoryEntity = new LqInventoryEntity
67 { 89 {
68 Id = YitIdHelper.NextId().ToString(), 90 Id = YitIdHelper.NextId().ToString(),
69 - ProductName = input.ProductName,  
70 - Price = input.Price, 91 + ProductId = input.ProductId,
71 Quantity = input.Quantity, 92 Quantity = input.Quantity,
72 - ProductCategory = input.ProductCategory,  
73 - DepartmentId = input.DepartmentId,  
74 - StandardUnit = input.StandardUnit, 93 + StockInTime = input.StockInTime ?? DateTime.Now,
  94 + ProductionDate = input.ProductionDate,
  95 + ShelfLife = input.ShelfLife,
  96 + BatchNumber = input.BatchNumber,
  97 + IsEffective = StatusEnum.有效.GetHashCode(),
75 CreateUser = _userManager.UserId, 98 CreateUser = _userManager.UserId,
76 - CreateTime = DateTime.Now,  
77 - IsEffective = StatusEnum.有效.GetHashCode() 99 + CreateTime = DateTime.Now
78 }; 100 };
79 101
80 var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync(); 102 var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync();
@@ -88,9 +110,9 @@ namespace NCC.Extend @@ -88,9 +110,9 @@ namespace NCC.Extend
88 } 110 }
89 #endregion 111 #endregion
90 112
91 - #region 更新库存 113 + #region 更新库存信息
92 /// <summary> 114 /// <summary>
93 - /// 更新库存 115 + /// 更新库存信息
94 /// </summary> 116 /// </summary>
95 /// <param name="input">更新输入</param> 117 /// <param name="input">更新输入</param>
96 /// <returns>更新结果</returns> 118 /// <returns>更新结果</returns>
@@ -99,7 +121,6 @@ namespace NCC.Extend @@ -99,7 +121,6 @@ namespace NCC.Extend
99 { 121 {
100 try 122 try
101 { 123 {
102 - // 检查库存记录是否存在  
103 var existingInventory = await _db.Queryable<LqInventoryEntity>() 124 var existingInventory = await _db.Queryable<LqInventoryEntity>()
104 .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) 125 .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
105 .FirstAsync(); 126 .FirstAsync();
@@ -109,23 +130,23 @@ namespace NCC.Extend @@ -109,23 +130,23 @@ namespace NCC.Extend
109 throw NCCException.Oh("库存记录不存在或已失效"); 130 throw NCCException.Oh("库存记录不存在或已失效");
110 } 131 }
111 132
112 - // 检查产品名称是否与其他记录重复  
113 - var duplicateProduct = await _db.Queryable<LqInventoryEntity>()  
114 - .Where(x => x.ProductName == input.ProductName && x.Id != input.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) 133 + // 验证产品是否存在
  134 + var product = await _db.Queryable<LqProductEntity>()
  135 + .Where(x => x.Id == input.ProductId)
115 .FirstAsync(); 136 .FirstAsync();
116 137
117 - if (duplicateProduct != null) 138 + if (product == null)
118 { 139 {
119 - throw NCCException.Oh($"产品名称'{input.ProductName}'已存在"); 140 + throw NCCException.Oh("产品不存在");
120 } 141 }
121 142
122 // 更新库存记录 143 // 更新库存记录
123 - existingInventory.ProductName = input.ProductName;  
124 - existingInventory.Price = input.Price; 144 + existingInventory.ProductId = input.ProductId;
125 existingInventory.Quantity = input.Quantity; 145 existingInventory.Quantity = input.Quantity;
126 - existingInventory.ProductCategory = input.ProductCategory;  
127 - existingInventory.DepartmentId = input.DepartmentId;  
128 - existingInventory.StandardUnit = input.StandardUnit; 146 + existingInventory.StockInTime = input.StockInTime;
  147 + existingInventory.ProductionDate = input.ProductionDate;
  148 + existingInventory.ShelfLife = input.ShelfLife;
  149 + existingInventory.BatchNumber = input.BatchNumber;
129 existingInventory.UpdateUser = _userManager.UserId; 150 existingInventory.UpdateUser = _userManager.UserId;
130 existingInventory.UpdateTime = DateTime.Now; 151 existingInventory.UpdateTime = DateTime.Now;
131 152
@@ -144,6 +165,9 @@ namespace NCC.Extend @@ -144,6 +165,9 @@ namespace NCC.Extend
144 /// <summary> 165 /// <summary>
145 /// 获取库存列表 166 /// 获取库存列表
146 /// </summary> 167 /// </summary>
  168 + /// <remarks>
  169 + /// 返回库存列表,包含产品信息、已使用数量、可用数量等
  170 + /// </remarks>
147 /// <param name="input">查询输入</param> 171 /// <param name="input">查询输入</param>
148 /// <returns>库存列表</returns> 172 /// <returns>库存列表</returns>
149 [HttpGet("GetList")] 173 [HttpGet("GetList")]
@@ -151,60 +175,98 @@ namespace NCC.Extend @@ -151,60 +175,98 @@ namespace NCC.Extend
151 { 175 {
152 try 176 try
153 { 177 {
154 - var sidx = input.sidx == null ? "id" : input.sidx;  
155 -  
156 - // 查询库存信息  
157 - var data = await _db.Queryable<LqInventoryEntity>()  
158 - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductName), x => x.ProductName.Contains(input.ProductName))  
159 - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), x => x.ProductCategory.Contains(input.ProductCategory))  
160 - .WhereIF(!string.IsNullOrWhiteSpace(input.DepartmentId), x => x.DepartmentId == input.DepartmentId)  
161 - .WhereIF(input.PriceMin.HasValue, x => x.Price >= input.PriceMin.Value)  
162 - .WhereIF(input.PriceMax.HasValue, x => x.Price <= input.PriceMax.Value)  
163 - .WhereIF(input.QuantityMin.HasValue, x => x.Quantity >= input.QuantityMin.Value)  
164 - .WhereIF(input.QuantityMax.HasValue, x => x.Quantity <= input.QuantityMax.Value)  
165 - .WhereIF(input.IsEffective.HasValue, x => x.IsEffective == input.IsEffective.Value)  
166 - .Select(x => new LqInventoryListOutput 178 + var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx;
  179 + var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
  180 +
  181 + // 查询库存信息,关联产品表
  182 + var data = await _db.Queryable<LqInventoryEntity, LqProductEntity>(
  183 + (inv, prod) => inv.ProductId == prod.Id)
  184 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (inv, prod) => inv.ProductId == input.ProductId)
  185 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductName), (inv, prod) => prod.ProductName.Contains(input.ProductName))
  186 + .WhereIF(!string.IsNullOrWhiteSpace(input.BatchNumber), (inv, prod) => inv.BatchNumber != null && inv.BatchNumber.Contains(input.BatchNumber))
  187 + .WhereIF(input.QuantityMin.HasValue, (inv, prod) => inv.Quantity >= input.QuantityMin.Value)
  188 + .WhereIF(input.QuantityMax.HasValue, (inv, prod) => inv.Quantity <= input.QuantityMax.Value)
  189 + .WhereIF(input.IsEffective.HasValue, (inv, prod) => inv.IsEffective == input.IsEffective.Value)
  190 + .Select((inv, prod) => new LqInventoryListOutput
167 { 191 {
168 - id = x.Id,  
169 - productName = x.ProductName,  
170 - price = x.Price,  
171 - quantity = x.Quantity,  
172 - productCategory = x.ProductCategory,  
173 - departmentId = x.DepartmentId,  
174 - departmentName = "",  
175 - standardUnit = x.StandardUnit,  
176 - totalValue = x.Price * x.Quantity,  
177 - createUser = x.CreateUser, 192 + id = inv.Id,
  193 + productId = inv.ProductId,
  194 + productName = prod.ProductName,
  195 + productPrice = prod.Price,
  196 + quantity = inv.Quantity,
  197 + usedQuantity = 0, // 稍后计算
  198 + availableQuantity = inv.Quantity, // 稍后计算
  199 + stockInTime = inv.StockInTime,
  200 + productionDate = inv.ProductionDate,
  201 + shelfLife = inv.ShelfLife,
  202 + batchNumber = inv.BatchNumber,
  203 + createUser = inv.CreateUser,
178 createUserName = "", 204 createUserName = "",
179 - createTime = x.CreateTime,  
180 - updateUser = x.UpdateUser, 205 + createTime = inv.CreateTime,
  206 + updateUser = inv.UpdateUser,
181 updateUserName = "", 207 updateUserName = "",
182 - updateTime = x.UpdateTime,  
183 - isEffective = x.IsEffective 208 + updateTime = inv.UpdateTime,
  209 + isEffective = inv.IsEffective
184 }) 210 })
185 .MergeTable() 211 .MergeTable()
186 - .OrderBy(sidx + " " + input.sort) 212 + .OrderBy(sidx + " " + sort)
187 .ToPagedListAsync(input.currentPage, input.pageSize); 213 .ToPagedListAsync(input.currentPage, input.pageSize);
188 214
189 - // 补充用户名称信息  
190 - foreach (var item in data.list) 215 + // 批量查询每个库存的已使用数量
  216 + var inventoryIds = data.list.Select(x => x.id).ToList();
  217 + if (inventoryIds.Any())
191 { 218 {
192 - if (!string.IsNullOrEmpty(item.departmentId))  
193 - {  
194 - var deptUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.departmentId).FirstAsync();  
195 - item.departmentName = deptUser?.RealName ?? "";  
196 - }  
197 - if (!string.IsNullOrEmpty(item.createUser)) 219 + // 注意:库存使用记录表中存储的是产品ID,不是库存ID
  220 + // 这里需要根据业务逻辑调整,如果使用记录需要关联到具体库存批次,需要修改使用记录表结构
  221 + // 暂时按产品ID统计已使用数量
  222 + var productIds = data.list.Select(x => x.productId).Distinct().ToList();
  223 + var usageDict = await _db.Queryable<LqInventoryUsageEntity>()
  224 + .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  225 + .GroupBy(x => x.ProductId)
  226 + .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
  227 + .ToListAsync();
  228 +
  229 + var usageDictMap = usageDict.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
  230 +
  231 + // 计算每个库存的已使用数量和可用数量
  232 + // 注意:这里假设同一产品的所有库存共享使用记录,如果需要按批次区分,需要修改使用记录表
  233 + foreach (var item in data.list)
198 { 234 {
199 - var createUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.createUser).FirstAsync();  
200 - item.createUserName = createUser?.RealName ?? ""; 235 + var totalUsage = usageDictMap.ContainsKey(item.productId) ? usageDictMap[item.productId] : 0;
  236 + // 按比例分配已使用数量(简化处理,实际可能需要更复杂的逻辑)
  237 + var productInventoryList = data.list.Where(x => x.productId == item.productId).ToList();
  238 + var totalProductQuantity = productInventoryList.Sum(x => x.quantity);
  239 + if (totalProductQuantity > 0)
  240 + {
  241 + item.usedQuantity = (int)(item.quantity * 1.0m / totalProductQuantity * totalUsage);
  242 + }
  243 + item.availableQuantity = item.quantity - item.usedQuantity;
201 } 244 }
202 - if (!string.IsNullOrEmpty(item.updateUser)) 245 + }
  246 +
  247 + // 补充用户名称信息
  248 + var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser })
  249 + .Where(x => !string.IsNullOrEmpty(x))
  250 + .Distinct()
  251 + .ToList();
  252 +
  253 + if (userIds.Any())
  254 + {
  255 + var userList = await _db.Queryable<UserEntity>()
  256 + .Where(x => userIds.Contains(x.Id))
  257 + .Select(x => new { x.Id, x.RealName })
  258 + .ToListAsync();
  259 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  260 +
  261 + foreach (var item in data.list)
203 { 262 {
204 - var updateUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.updateUser).FirstAsync();  
205 - item.updateUserName = updateUser?.RealName ?? ""; 263 + if (!string.IsNullOrEmpty(item.createUser) && userDict.ContainsKey(item.createUser))
  264 + item.createUserName = userDict[item.createUser];
  265 + if (!string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser))
  266 + item.updateUserName = userDict[item.updateUser];
206 } 267 }
207 } 268 }
  269 +
208 return PageResult<LqInventoryListOutput>.SqlSugarPageResult(data); 270 return PageResult<LqInventoryListOutput>.SqlSugarPageResult(data);
209 } 271 }
210 catch (Exception ex) 272 catch (Exception ex)
@@ -230,52 +292,75 @@ namespace NCC.Extend @@ -230,52 +292,75 @@ namespace NCC.Extend
230 { 292 {
231 throw NCCException.Oh("库存ID不能为空"); 293 throw NCCException.Oh("库存ID不能为空");
232 } 294 }
233 - // 查询库存信息  
234 - var inventory = await _db.Queryable<LqInventoryEntity>().Where(x => x.Id == id).FirstAsync(); 295 +
  296 + var inventory = await _db.Queryable<LqInventoryEntity, LqProductEntity>(
  297 + (inv, prod) => inv.ProductId == prod.Id)
  298 + .Where((inv, prod) => inv.Id == id)
  299 + .Select((inv, prod) => new LqInventoryInfoOutput
  300 + {
  301 + id = inv.Id,
  302 + productId = inv.ProductId,
  303 + productName = prod.ProductName,
  304 + productPrice = prod.Price,
  305 + quantity = inv.Quantity,
  306 + usedQuantity = 0, // 稍后计算
  307 + availableQuantity = inv.Quantity, // 稍后计算
  308 + stockInTime = inv.StockInTime,
  309 + productionDate = inv.ProductionDate,
  310 + shelfLife = inv.ShelfLife,
  311 + batchNumber = inv.BatchNumber,
  312 + createUser = inv.CreateUser,
  313 + createUserName = "",
  314 + createTime = inv.CreateTime,
  315 + updateUser = inv.UpdateUser,
  316 + updateUserName = "",
  317 + updateTime = inv.UpdateTime,
  318 + isEffective = inv.IsEffective
  319 + })
  320 + .FirstAsync();
235 321
236 if (inventory == null) 322 if (inventory == null)
237 { 323 {
238 throw NCCException.Oh("库存记录不存在"); 324 throw NCCException.Oh("库存记录不存在");
239 } 325 }
240 326
241 - var result = new LqInventoryInfoOutput  
242 - {  
243 - id = inventory.Id,  
244 - productName = inventory.ProductName,  
245 - price = inventory.Price,  
246 - quantity = inventory.Quantity,  
247 - productCategory = inventory.ProductCategory,  
248 - departmentId = inventory.DepartmentId,  
249 - departmentName = "",  
250 - standardUnit = inventory.StandardUnit,  
251 - totalValue = inventory.Price * inventory.Quantity,  
252 - createUser = inventory.CreateUser,  
253 - createUserName = "",  
254 - createTime = inventory.CreateTime,  
255 - updateUser = inventory.UpdateUser,  
256 - updateUserName = "",  
257 - updateTime = inventory.UpdateTime,  
258 - isEffective = inventory.IsEffective  
259 - }; 327 + // 计算已使用数量
  328 + var totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
  329 + .Where(x => x.ProductId == inventory.productId && x.IsEffective == StatusEnum.有效.GetHashCode())
  330 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
260 331
261 - // 补充用户名称信息  
262 - if (!string.IsNullOrEmpty(result.departmentId))  
263 - {  
264 - var deptUser = await _db.Queryable<UserEntity>().Where(u => u.Id == result.departmentId).FirstAsync();  
265 - result.departmentName = deptUser?.RealName ?? "";  
266 - }  
267 - if (!string.IsNullOrEmpty(result.createUser)) 332 + // 计算该产品的总库存
  333 + var totalInventory = await _db.Queryable<LqInventoryEntity>()
  334 + .Where(x => x.ProductId == inventory.productId && x.IsEffective == StatusEnum.有效.GetHashCode())
  335 + .SumAsync(x => (int?)x.Quantity) ?? 0;
  336 +
  337 + if (totalInventory > 0)
268 { 338 {
269 - var createUser = await _db.Queryable<UserEntity>().Where(u => u.Id == result.createUser).FirstAsync();  
270 - result.createUserName = createUser?.RealName ?? ""; 339 + inventory.usedQuantity = (int)(inventory.quantity * 1.0m / totalInventory * totalUsage);
271 } 340 }
272 - if (!string.IsNullOrEmpty(result.updateUser)) 341 + inventory.availableQuantity = inventory.quantity - inventory.usedQuantity;
  342 +
  343 + // 补充用户名称信息
  344 + var userIds = new[] { inventory.createUser, inventory.updateUser }
  345 + .Where(x => !string.IsNullOrEmpty(x))
  346 + .Distinct()
  347 + .ToList();
  348 +
  349 + if (userIds.Any())
273 { 350 {
274 - var updateUser = await _db.Queryable<UserEntity>().Where(u => u.Id == result.updateUser).FirstAsync();  
275 - result.updateUserName = updateUser?.RealName ?? ""; 351 + var userList = await _db.Queryable<UserEntity>()
  352 + .Where(x => userIds.Contains(x.Id))
  353 + .Select(x => new { x.Id, x.RealName })
  354 + .ToListAsync();
  355 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  356 +
  357 + if (!string.IsNullOrEmpty(inventory.createUser) && userDict.ContainsKey(inventory.createUser))
  358 + inventory.createUserName = userDict[inventory.createUser];
  359 + if (!string.IsNullOrEmpty(inventory.updateUser) && userDict.ContainsKey(inventory.updateUser))
  360 + inventory.updateUserName = userDict[inventory.updateUser];
276 } 361 }
277 362
278 - return result; 363 + return inventory;
279 } 364 }
280 catch (Exception ex) 365 catch (Exception ex)
281 { 366 {
@@ -285,6 +370,77 @@ namespace NCC.Extend @@ -285,6 +370,77 @@ namespace NCC.Extend
285 } 370 }
286 #endregion 371 #endregion
287 372
  373 + #region 作废库存
  374 + /// <summary>
  375 + /// 作废库存
  376 + /// </summary>
  377 + /// <remarks>
  378 + /// 作废库存记录,需要判断已使用数量是否超过作废后的总数量
  379 + /// 如果已使用数量 > 作废后的总数量,则不允许作废
  380 + ///
  381 + /// 示例请求:
  382 + /// ```
  383 + /// PUT /api/Extend/LqInventory/Cancel/{id}?remarks=作废备注
  384 + /// ```
  385 + /// </remarks>
  386 + /// <param name="id">库存ID</param>
  387 + /// <param name="remarks">作废备注</param>
  388 + /// <returns>作废结果</returns>
  389 + /// <response code="200">作废成功</response>
  390 + /// <response code="400">库存不存在或已使用数量超过作废后的总数量</response>
  391 + /// <response code="500">服务器错误</response>
  392 + [HttpPut("Cancel/{id}")]
  393 + public async Task CancelAsync([FromRoute] string id, [FromQuery] string remarks = null)
  394 + {
  395 + try
  396 + {
  397 + if (string.IsNullOrWhiteSpace(id))
  398 + {
  399 + throw NCCException.Oh("库存ID不能为空");
  400 + }
  401 +
  402 + var inventory = await _db.Queryable<LqInventoryEntity>()
  403 + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  404 + .FirstAsync();
  405 +
  406 + if (inventory == null)
  407 + {
  408 + throw NCCException.Oh("库存记录不存在或已失效");
  409 + }
  410 +
  411 + // 计算该产品的总库存(包括当前库存)
  412 + var totalInventory = await _db.Queryable<LqInventoryEntity>()
  413 + .Where(x => x.ProductId == inventory.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
  414 + .SumAsync(x => (int?)x.Quantity) ?? 0;
  415 +
  416 + // 计算该产品的已使用数量
  417 + var totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
  418 + .Where(x => x.ProductId == inventory.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
  419 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  420 +
  421 + // 作废后的总数量 = 当前总库存 - 当前库存数量
  422 + var inventoryAfterCancel = totalInventory - inventory.Quantity;
  423 +
  424 + // 判断:如果已使用数量 > 作废后的总数量,则不允许作废
  425 + if (totalUsage > inventoryAfterCancel)
  426 + {
  427 + throw NCCException.Oh($"不允许作废:已使用数量({totalUsage})超过作废后的总数量({inventoryAfterCancel})");
  428 + }
  429 +
  430 + // 作废库存
  431 + inventory.IsEffective = StatusEnum.无效.GetHashCode();
  432 + inventory.UpdateUser = _userManager.UserId;
  433 + inventory.UpdateTime = DateTime.Now;
288 434
  435 + var isOk = await _db.Updateable(inventory).ExecuteCommandAsync();
  436 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  437 + }
  438 + catch (Exception ex)
  439 + {
  440 + _logger.LogError(ex, "作废库存失败");
  441 + throw NCCException.Oh($"作废失败:{ex.Message}");
  442 + }
  443 + }
  444 + #endregion
289 } 445 }
290 } 446 }
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
@@ -11,6 +11,7 @@ using NCC.DynamicApiController; @@ -11,6 +11,7 @@ using NCC.DynamicApiController;
11 using NCC.Extend.Entitys.Dto.LqInventoryUsage; 11 using NCC.Extend.Entitys.Dto.LqInventoryUsage;
12 using NCC.Extend.Entitys.Enum; 12 using NCC.Extend.Entitys.Enum;
13 using NCC.Extend.Entitys.lq_inventory; 13 using NCC.Extend.Entitys.lq_inventory;
  14 +using NCC.Extend.Entitys.lq_product;
14 using NCC.Extend.Entitys.lq_inventory_usage; 15 using NCC.Extend.Entitys.lq_inventory_usage;
15 using NCC.Extend.Entitys.lq_mdxx; 16 using NCC.Extend.Entitys.lq_mdxx;
16 using NCC.Extend.Interfaces.LqInventoryUsage; 17 using NCC.Extend.Interfaces.LqInventoryUsage;
@@ -57,17 +58,32 @@ namespace NCC.Extend @@ -57,17 +58,32 @@ namespace NCC.Extend
57 try 58 try
58 { 59 {
59 // 验证产品是否存在 60 // 验证产品是否存在
60 - var product = await _db.Queryable<LqInventoryEntity>().Where(x => x.Id == input.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync(); 61 + var product = await _db.Queryable<LqProductEntity>()
  62 + .Where(x => x.Id == input.ProductId)
  63 + .FirstAsync();
61 64
62 if (product == null) 65 if (product == null)
63 { 66 {
64 - throw NCCException.Oh("产品不存在或已失效"); 67 + throw NCCException.Oh("产品不存在");
65 } 68 }
66 69
  70 + // 计算该产品的总库存数量(所有有效库存的总和)
  71 + var totalInventory = await _db.Queryable<LqInventoryEntity>()
  72 + .Where(x => x.ProductId == input.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
  73 + .SumAsync(x => (int?)x.Quantity) ?? 0;
  74 +
  75 + // 计算该产品的已使用数量
  76 + var totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
  77 + .Where(x => x.ProductId == input.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
  78 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  79 +
  80 + // 计算可用库存
  81 + var availableInventory = totalInventory - totalUsage;
  82 +
67 // 检查库存数量是否足够 83 // 检查库存数量是否足够
68 - if (product.Quantity < input.UsageQuantity) 84 + if (availableInventory < input.UsageQuantity)
69 { 85 {
70 - throw NCCException.Oh($"库存不足,当前库存:{product.Quantity},需要数量:{input.UsageQuantity}"); 86 + throw NCCException.Oh($"库存不足,当前可用库存:{availableInventory},需要数量:{input.UsageQuantity}");
71 } 87 }
72 88
73 _db.Ado.BeginTran(); 89 _db.Ado.BeginTran();
@@ -89,12 +105,7 @@ namespace NCC.Extend @@ -89,12 +105,7 @@ namespace NCC.Extend
89 var isOk = await _db.Insertable(usageEntity).ExecuteCommandAsync(); 105 var isOk = await _db.Insertable(usageEntity).ExecuteCommandAsync();
90 if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); 106 if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
91 107
92 - // 更新库存数量  
93 - product.Quantity -= input.UsageQuantity;  
94 - product.UpdateUser = _userManager.UserId;  
95 - product.UpdateTime = DateTime.Now;  
96 - var updateOk = await _db.Updateable(product).ExecuteCommandAsync();  
97 - if (!(updateOk > 0)) throw NCCException.Oh(ErrorCode.COM1001); 108 + // 注意:新结构下不需要更新库存表的数量,因为库存数量是固定的,使用记录只是记录使用情况
98 _db.Ado.CommitTran(); 109 _db.Ado.CommitTran();
99 } 110 }
100 catch (Exception ex) 111 catch (Exception ex)
@@ -140,17 +151,8 @@ namespace NCC.Extend @@ -140,17 +151,8 @@ namespace NCC.Extend
140 151
141 var isOk = await _db.Updateable(usageRecord).ExecuteCommandAsync(); 152 var isOk = await _db.Updateable(usageRecord).ExecuteCommandAsync();
142 if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1003); 153 if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1003);
143 - // 恢复库存数量  
144 - var product = await _db.Queryable<LqInventoryEntity>().Where(x => x.Id == usageRecord.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync();  
145 - if (product != null)  
146 - {  
147 - product.Quantity += usageRecord.UsageQuantity;  
148 - product.UpdateUser = _userManager.UserId;  
149 - product.UpdateTime = DateTime.Now;  
150 - var updateOk = await _db.Updateable(product).ExecuteCommandAsync();  
151 - if (!(updateOk > 0)) throw NCCException.Oh(ErrorCode.COM1001);  
152 - }  
153 154
  155 + // 注意:新结构下不需要恢复库存表的数量,因为库存数量是固定的,使用记录只是记录使用情况
154 _db.Ado.CommitTran(); 156 _db.Ado.CommitTran();
155 } 157 }
156 catch (Exception ex) 158 catch (Exception ex)
@@ -175,82 +177,77 @@ namespace NCC.Extend @@ -175,82 +177,77 @@ namespace NCC.Extend
175 { 177 {
176 var sidx = input.sidx == null ? "id" : input.sidx; 178 var sidx = input.sidx == null ? "id" : input.sidx;
177 179
178 - // 查询使用记录信息  
179 - var data = await _db.Queryable<LqInventoryUsageEntity>()  
180 - .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), x => x.ProductId == input.ProductId)  
181 - .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), x => x.StoreId == input.StoreId)  
182 - .WhereIF(input.UsageStartTime.HasValue, x => x.UsageTime >= input.UsageStartTime.Value)  
183 - .WhereIF(input.UsageEndTime.HasValue, x => x.UsageTime <= input.UsageEndTime.Value)  
184 - .WhereIF(!string.IsNullOrWhiteSpace(input.RelatedConsumeId), x => x.RelatedConsumeId == input.RelatedConsumeId)  
185 - .WhereIF(input.IsEffective.HasValue, x => x.IsEffective == input.IsEffective.Value)  
186 - .Select(x => new LqInventoryUsageListOutput 180 + // 查询使用记录信息,关联产品表
  181 + var data = await _db.Queryable<LqInventoryUsageEntity, LqProductEntity>(
  182 + (usage, product) => usage.ProductId == product.Id)
  183 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId)
  184 + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId)
  185 + .WhereIF(input.UsageStartTime.HasValue, (usage, product) => usage.UsageTime >= input.UsageStartTime.Value)
  186 + .WhereIF(input.UsageEndTime.HasValue, (usage, product) => usage.UsageTime <= input.UsageEndTime.Value)
  187 + .WhereIF(!string.IsNullOrWhiteSpace(input.RelatedConsumeId), (usage, product) => usage.RelatedConsumeId == input.RelatedConsumeId)
  188 + .WhereIF(input.IsEffective.HasValue, (usage, product) => usage.IsEffective == input.IsEffective.Value)
  189 + .Select((usage, product) => new LqInventoryUsageListOutput
187 { 190 {
188 - id = x.Id,  
189 - productId = x.ProductId,  
190 - productName = "",  
191 - productCategory = "",  
192 - productPrice = 0,  
193 - storeId = x.StoreId,  
194 - storeName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(u => u.Id == x.StoreId).Select(u => u.Dm),  
195 - usageTime = x.UsageTime,  
196 - usageQuantity = x.UsageQuantity,  
197 - relatedConsumeId = x.RelatedConsumeId,  
198 - createUser = x.CreateUser, 191 + id = usage.Id,
  192 + productId = usage.ProductId,
  193 + productName = product.ProductName,
  194 + productCategory = product.ProductCategory,
  195 + productPrice = product.Price,
  196 + storeId = usage.StoreId,
  197 + storeName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(u => u.Id == usage.StoreId).Select(u => u.Dm),
  198 + usageTime = usage.UsageTime,
  199 + usageQuantity = usage.UsageQuantity,
  200 + relatedConsumeId = usage.RelatedConsumeId,
  201 + createUser = usage.CreateUser,
199 createUserName = "", 202 createUserName = "",
200 - createTime = x.CreateTime,  
201 - updateUser = x.UpdateUser, 203 + createTime = usage.CreateTime,
  204 + updateUser = usage.UpdateUser,
202 updateUserName = "", 205 updateUserName = "",
203 - updateTime = x.UpdateTime,  
204 - isEffective = x.IsEffective 206 + updateTime = usage.UpdateTime,
  207 + isEffective = usage.IsEffective
205 }) 208 })
206 .MergeTable() 209 .MergeTable()
207 .OrderBy(sidx + " " + input.sort) 210 .OrderBy(sidx + " " + input.sort)
208 .ToPagedListAsync(input.currentPage, input.pageSize); 211 .ToPagedListAsync(input.currentPage, input.pageSize);
209 212
210 - // 补充产品信息和用户信息  
211 - foreach (var item in data.list) 213 + // 补充用户信息
  214 + var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser })
  215 + .Where(x => !string.IsNullOrEmpty(x))
  216 + .Distinct()
  217 + .ToList();
  218 +
  219 + if (userIds.Any())
212 { 220 {
213 - if (!string.IsNullOrEmpty(item.productId))  
214 - {  
215 - var product = await _db.Queryable<LqInventoryEntity>().Where(p => p.Id == item.productId).FirstAsync();  
216 - if (product != null)  
217 - {  
218 - item.productName = product.ProductName;  
219 - item.productCategory = product.ProductCategory;  
220 - item.productPrice = product.Price;  
221 - }  
222 - }  
223 - if (!string.IsNullOrEmpty(item.storeId))  
224 - {  
225 - var store = await _db.Queryable<UserEntity>().Where(u => u.Id == item.storeId).FirstAsync();  
226 - item.storeName = store?.RealName ?? "";  
227 - }  
228 - if (!string.IsNullOrEmpty(item.createUser))  
229 - {  
230 - var createUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.createUser).FirstAsync();  
231 - item.createUserName = createUser?.RealName ?? "";  
232 - }  
233 - if (!string.IsNullOrEmpty(item.updateUser)) 221 + var userList = await _db.Queryable<UserEntity>()
  222 + .Where(x => userIds.Contains(x.Id))
  223 + .Select(x => new { x.Id, x.RealName })
  224 + .ToListAsync();
  225 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  226 +
  227 + foreach (var item in data.list)
234 { 228 {
235 - var updateUser = await _db.Queryable<UserEntity>().Where(u => u.Id == item.updateUser).FirstAsync();  
236 - item.updateUserName = updateUser?.RealName ?? ""; 229 + if (!string.IsNullOrEmpty(item.createUser) && userDict.ContainsKey(item.createUser))
  230 + item.createUserName = userDict[item.createUser];
  231 + if (!string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser))
  232 + item.updateUserName = userDict[item.updateUser];
237 } 233 }
238 } 234 }
239 235
240 - // 应用产品名称和分类的过滤条件 236 + // 应用产品名称和分类的过滤条件(在内存中过滤,因为已经在Select中获取了)
241 if (!string.IsNullOrWhiteSpace(input.ProductName) || !string.IsNullOrWhiteSpace(input.ProductCategory)) 237 if (!string.IsNullOrWhiteSpace(input.ProductName) || !string.IsNullOrWhiteSpace(input.ProductCategory))
242 { 238 {
243 data.list = data.list.Where(x => 239 data.list = data.list.Where(x =>
244 - (string.IsNullOrWhiteSpace(input.ProductName) || x.productName.Contains(input.ProductName)) &&  
245 - (string.IsNullOrWhiteSpace(input.ProductCategory) || x.productCategory.Contains(input.ProductCategory)) 240 + (string.IsNullOrWhiteSpace(input.ProductName) || (x.productName != null && x.productName.Contains(input.ProductName))) &&
  241 + (string.IsNullOrWhiteSpace(input.ProductCategory) || (x.productCategory != null && x.productCategory.Contains(input.ProductCategory)))
246 ).ToList(); 242 ).ToList();
247 } 243 }
248 244
249 - // 应用门店名称的过滤条件 245 + // 应用门店名称的过滤条件(在内存中过滤,因为已经在Select中获取了)
250 if (!string.IsNullOrWhiteSpace(input.StoreName)) 246 if (!string.IsNullOrWhiteSpace(input.StoreName))
251 { 247 {
252 - data.list = data.list.Where(x => x.storeName.Contains(input.StoreName)).ToList(); 248 + data.list = data.list.Where(x => x.storeName != null && x.storeName.Contains(input.StoreName)).ToList();
253 } 249 }
  250 +
254 return PageResult<LqInventoryUsageListOutput>.SqlSugarPageResult(data); 251 return PageResult<LqInventoryUsageListOutput>.SqlSugarPageResult(data);
255 } 252 }
256 catch (Exception ex) 253 catch (Exception ex)
@@ -273,7 +270,7 @@ namespace NCC.Extend @@ -273,7 +270,7 @@ namespace NCC.Extend
273 try 270 try
274 { 271 {
275 var data = await _db.Queryable<LqInventoryUsageEntity>() 272 var data = await _db.Queryable<LqInventoryUsageEntity>()
276 - .LeftJoin<LqInventoryEntity>((usage, product) => usage.ProductId == product.Id) 273 + .LeftJoin<LqProductEntity>((usage, product) => usage.ProductId == product.Id)
277 .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) 274 .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime)
278 .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) 275 .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode())
279 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) 276 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId)
@@ -321,18 +318,18 @@ namespace NCC.Extend @@ -321,18 +318,18 @@ namespace NCC.Extend
321 try 318 try
322 { 319 {
323 var data = await _db.Queryable<LqInventoryUsageEntity>() 320 var data = await _db.Queryable<LqInventoryUsageEntity>()
324 - .LeftJoin<LqInventoryEntity>((usage, product) => usage.ProductId == product.Id)  
325 - .LeftJoin<UserEntity>((usage, product, store) => usage.StoreId == store.Id) 321 + .LeftJoin<LqProductEntity>((usage, product) => usage.ProductId == product.Id)
  322 + .LeftJoin<LqMdxxEntity>((usage, product, store) => usage.StoreId == store.Id)
326 .Where((usage, product, store) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) 323 .Where((usage, product, store) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime)
327 .Where((usage, product, store) => usage.IsEffective == StatusEnum.有效.GetHashCode()) 324 .Where((usage, product, store) => usage.IsEffective == StatusEnum.有效.GetHashCode())
328 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product, store) => usage.ProductId == input.ProductId) 325 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product, store) => usage.ProductId == input.ProductId)
329 .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product, store) => usage.StoreId == input.StoreId) 326 .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product, store) => usage.StoreId == input.StoreId)
330 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product, store) => product.ProductCategory == input.ProductCategory) 327 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product, store) => product.ProductCategory == input.ProductCategory)
331 - .GroupBy((usage, product, store) => new { usage.StoreId, store.RealName }) 328 + .GroupBy((usage, product, store) => new { usage.StoreId, store.Dm })
332 .Select((usage, product, store) => new StoreUsageStatisticsOutput 329 .Select((usage, product, store) => new StoreUsageStatisticsOutput
333 { 330 {
334 StoreId = usage.StoreId, 331 StoreId = usage.StoreId,
335 - StoreName = store.RealName, 332 + StoreName = store.Dm,
336 TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity), 333 TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity),
337 TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price), 334 TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price),
338 UsageCount = SqlFunc.AggregateCount(usage.Id), 335 UsageCount = SqlFunc.AggregateCount(usage.Id),
@@ -369,7 +366,7 @@ namespace NCC.Extend @@ -369,7 +366,7 @@ namespace NCC.Extend
369 { 366 {
370 // 先获取基础数据 367 // 先获取基础数据
371 var baseData = await _db.Queryable<LqInventoryUsageEntity>() 368 var baseData = await _db.Queryable<LqInventoryUsageEntity>()
372 - .LeftJoin<LqInventoryEntity>((usage, product) => usage.ProductId == product.Id) 369 + .LeftJoin<LqProductEntity>((usage, product) => usage.ProductId == product.Id)
373 .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) 370 .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime)
374 .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) 371 .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode())
375 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) 372 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId)
@@ -450,7 +447,7 @@ namespace NCC.Extend @@ -450,7 +447,7 @@ namespace NCC.Extend
450 try 447 try
451 { 448 {
452 var data = await _db.Queryable<LqInventoryUsageEntity>() 449 var data = await _db.Queryable<LqInventoryUsageEntity>()
453 - .LeftJoin<LqInventoryEntity>((usage, product) => usage.ProductId == product.Id) 450 + .LeftJoin<LqProductEntity>((usage, product) => usage.ProductId == product.Id)
454 .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) 451 .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime)
455 .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) 452 .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode())
456 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) 453 .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId)
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -3678,5 +3678,188 @@ namespace NCC.Extend.LqKdKdjlb @@ -3678,5 +3678,188 @@ namespace NCC.Extend.LqKdKdjlb
3678 } 3678 }
3679 #endregion 3679 #endregion
3680 3680
  3681 + #region 批量处理历史开单数据的升单类型
  3682 + /// <summary>
  3683 + /// 批量处理历史开单数据的升单类型
  3684 + /// </summary>
  3685 + /// <remarks>
  3686 + /// 批量更新历史开单记录的升单类型字段(升生美、升科美、升医美)
  3687 + ///
  3688 + /// 处理逻辑:
  3689 + /// 1. 查询所有有效的开单记录
  3690 + /// 2. 对于每条开单记录,应用升单判断逻辑:
  3691 + /// - 判断当前开单是否包含医美/科美/生美品项
  3692 + /// - 判断该会员在当前开单日期之前是否有对应类型的开单记录(重要:只看该开单之前的记录)
  3693 + /// - 更新升单类型字段
  3694 + /// 3. 批量更新开单记录
  3695 + ///
  3696 + /// 升单判断规则:
  3697 + /// - 升医美:当前开单包含医美品项 + 该会员在当前开单日期之前有医美类型的开单记录 + 当前开单医美品项金额>=1000
  3698 + /// - 升科美:当前开单包含科美品项 + 该会员在当前开单日期之前有科美类型的开单记录
  3699 + /// - 升生美:当前开单包含生美品项 + 该会员在当前开单日期之前有生美类型的开单记录
  3700 + /// </remarks>
  3701 + /// <returns>处理结果,包含处理的记录数和成功数</returns>
  3702 + /// <response code="200">处理成功</response>
  3703 + /// <response code="500">服务器错误</response>
  3704 + [HttpPost("batch-update-upgrade-type")]
  3705 + public async Task<dynamic> BatchUpdateUpgradeType()
  3706 + {
  3707 + try
  3708 + {
  3709 + // 1. 查询所有有效的开单记录
  3710 + var billingList = await _db.Queryable<LqKdKdjlbEntity>().Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
  3711 + if (!billingList.Any())
  3712 + {
  3713 + return new { success = true, message = "没有需要处理的开单记录", totalCount = 0, successCount = 0 };
  3714 + }
  3715 + _logger.LogInformation($"找到 {billingList.Count} 条开单记录需要处理");
  3716 + // 2. 批量查询关联数据
  3717 + var billingIds = billingList.Select(x => x.Id).ToList();
  3718 + var memberIds = billingList.Select(x => x.Kdhy).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
  3719 + // 查询所有品项明细
  3720 + var pxmxList = await _db.Queryable<LqKdPxmxEntity>().Where(x => billingIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
  3721 + // 查询所有项目资料(用于获取品项分类)
  3722 + var itemIds = pxmxList.Where(x => !string.IsNullOrEmpty(x.Px)).Select(x => x.Px).Distinct().ToList();
  3723 + var itemCategoryDict = new Dictionary<string, string>();
  3724 + if (itemIds.Any())
  3725 + {
  3726 + var itemCategories = await _db.Queryable<LqXmzlEntity>().Where(x => itemIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode()).Select(x => new { x.Id, x.Qt2 }).ToListAsync();
  3727 + itemCategoryDict = itemCategories.ToDictionary(k => k.Id, v => v.Qt2 ?? "");
  3728 + }
  3729 +
  3730 + // 按开单ID分组品项明细
  3731 + var pxmxGrouped = pxmxList.GroupBy(x => x.Glkdbh).ToDictionary(g => g.Key, g => g.ToList());
  3732 +
  3733 + // 3. 批量处理,每批500条
  3734 + const int batchSize = 500;
  3735 + var totalBatches = (int)Math.Ceiling((double)billingList.Count / batchSize);
  3736 + var successCount = 0;
  3737 + var updateList = new List<LqKdKdjlbEntity>();
  3738 +
  3739 + for (int batchIndex = 0; batchIndex < totalBatches; batchIndex++)
  3740 + {
  3741 + var batchBillingList = billingList.Skip(batchIndex * batchSize).Take(batchSize).ToList();
  3742 +
  3743 + foreach (var billing in batchBillingList)
  3744 + {
  3745 + try
  3746 + {
  3747 + // 获取该开单的品项明细
  3748 + var currentPxmxList = pxmxGrouped.ContainsKey(billing.Id) ? pxmxGrouped[billing.Id] : new List<LqKdPxmxEntity>();
  3749 +
  3750 + if (!currentPxmxList.Any())
  3751 + {
  3752 + // 如果没有品项明细,设置为"否"
  3753 + billing.UpgradeMedicalBeauty = "否";
  3754 + billing.UpgradeTechBeauty = "否";
  3755 + billing.UpgradeLifeBeauty = "否";
  3756 + updateList.Add(billing);
  3757 + continue;
  3758 + }
  3759 +
  3760 + // 判断当前开单是否包含医美/科美/生美品项
  3761 + var hasMedicalItem = currentPxmxList.Any(x =>
  3762 + !string.IsNullOrEmpty(x.Px) && itemCategoryDict.ContainsKey(x.Px) && itemCategoryDict[x.Px] == "医美");
  3763 + var medicalItemAmount = currentPxmxList
  3764 + .Where(x => !string.IsNullOrEmpty(x.Px) && itemCategoryDict.ContainsKey(x.Px) && itemCategoryDict[x.Px] == "医美")
  3765 + .Sum(x => x.ActualPrice);
  3766 +
  3767 + var hasTechItem = currentPxmxList.Any(x =>
  3768 + !string.IsNullOrEmpty(x.Px) && itemCategoryDict.ContainsKey(x.Px) && itemCategoryDict[x.Px] == "科美");
  3769 +
  3770 + var hasLifeItem = currentPxmxList.Any(x =>
  3771 + !string.IsNullOrEmpty(x.Px) && itemCategoryDict.ContainsKey(x.Px) && itemCategoryDict[x.Px] == "生美");
  3772 +
  3773 + // 判断该会员在当前开单日期之前是否有对应类型的开单记录
  3774 + if (!string.IsNullOrEmpty(billing.Kdhy) && billing.Kdrq != null)
  3775 + {
  3776 + var currentBillingDate = billing.Kdrq.Value;
  3777 +
  3778 + // 查询该会员在当前开单日期之前的开单记录
  3779 + var previousBillingIds = await _db.Queryable<LqKdKdjlbEntity>()
  3780 + .Where(x => x.Kdhy == billing.Kdhy
  3781 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  3782 + && x.Kdrq < currentBillingDate)
  3783 + .Select(x => x.Id)
  3784 + .ToListAsync();
  3785 +
  3786 + if (previousBillingIds.Any())
  3787 + {
  3788 + // 查询这些开单的品项明细
  3789 + var previousPxmxList = await _db.Queryable<LqKdPxmxEntity>()
  3790 + .Where(x => previousBillingIds.Contains(x.Glkdbh)
  3791 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  3792 + && x.ActualPrice > 0
  3793 + && x.Px != "61")
  3794 + .Select(x => x.ItemCategory)
  3795 + .Distinct()
  3796 + .ToListAsync();
  3797 +
  3798 + var hasPreviousMedical = previousPxmxList.Contains("医美");
  3799 + var hasPreviousTech = previousPxmxList.Contains("科美");
  3800 + var hasPreviousLife = previousPxmxList.Contains("生美");
  3801 +
  3802 + // 判断升医美
  3803 + billing.UpgradeMedicalBeauty = (hasMedicalItem && hasPreviousMedical && medicalItemAmount >= 1000) ? "是" : "否";
  3804 +
  3805 + // 判断升科美
  3806 + billing.UpgradeTechBeauty = (hasTechItem && hasPreviousTech) ? "是" : "否";
  3807 +
  3808 + // 判断升生美
  3809 + billing.UpgradeLifeBeauty = (hasLifeItem && hasPreviousLife) ? "是" : "否";
  3810 + }
  3811 + else
  3812 + {
  3813 + billing.UpgradeMedicalBeauty = "否";
  3814 + billing.UpgradeTechBeauty = "否";
  3815 + billing.UpgradeLifeBeauty = "否";
  3816 + }
  3817 + }
  3818 + else
  3819 + {
  3820 + billing.UpgradeMedicalBeauty = "否";
  3821 + billing.UpgradeTechBeauty = "否";
  3822 + billing.UpgradeLifeBeauty = "否";
  3823 + }
  3824 +
  3825 + updateList.Add(billing);
  3826 + }
  3827 + catch (Exception ex)
  3828 + {
  3829 + _logger.LogError(ex, $"处理开单记录 {billing.Id} 失败: {ex.Message}");
  3830 + }
  3831 + }
  3832 +
  3833 + // 批量更新
  3834 + if (updateList.Any())
  3835 + {
  3836 + await _db.Updateable(updateList)
  3837 + .UpdateColumns(it => new { it.UpgradeMedicalBeauty, it.UpgradeTechBeauty, it.UpgradeLifeBeauty })
  3838 + .ExecuteCommandAsync();
  3839 + successCount += updateList.Count;
  3840 + updateList.Clear();
  3841 + }
  3842 +
  3843 + _logger.LogInformation($"已处理 {Math.Min((batchIndex + 1) * batchSize, billingList.Count)} / {billingList.Count} 条记录");
  3844 + }
  3845 +
  3846 + _logger.LogInformation($"批量处理完成,共处理 {billingList.Count} 条记录,成功 {successCount} 条");
  3847 +
  3848 + return new
  3849 + {
  3850 + success = true,
  3851 + message = "批量处理完成",
  3852 + totalCount = billingList.Count,
  3853 + successCount = successCount
  3854 + };
  3855 + }
  3856 + catch (Exception ex)
  3857 + {
  3858 + _logger.LogError(ex, $"批量处理历史开单数据的升单类型失败: {ex.ToString()}");
  3859 + throw NCCException.Oh(ErrorCode.COM1005, $"批量处理历史开单数据的升单类型失败: {ex.Message}");
  3860 + }
  3861 + }
  3862 + #endregion
  3863 +
3681 } 3864 }
3682 } 3865 }
netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs 0 → 100644
  1 +using System;
  2 +using System.Linq;
  3 +using System.Threading.Tasks;
  4 +using Microsoft.AspNetCore.Mvc;
  5 +using Microsoft.Extensions.Logging;
  6 +using NCC.Common.Core.Manager;
  7 +using NCC.Common.Enum;
  8 +using NCC.Common.Filter;
  9 +using NCC.Dependency;
  10 +using NCC.DynamicApiController;
  11 +using NCC.Extend.Entitys.Dto.LqProduct;
  12 +using NCC.Extend.Entitys.Enum;
  13 +using NCC.Extend.Entitys.lq_inventory;
  14 +using NCC.Extend.Entitys.lq_inventory_usage;
  15 +using NCC.Extend.Entitys.lq_mdxx;
  16 +using NCC.Extend.Entitys.lq_product;
  17 +using NCC.Extend.Interfaces.LqProduct;
  18 +using NCC.FriendlyException;
  19 +using NCC.System.Entitys.Permission;
  20 +using SqlSugar;
  21 +using Yitter.IdGenerator;
  22 +
  23 +namespace NCC.Extend
  24 +{
  25 + /// <summary>
  26 + /// 产品服务
  27 + /// </summary>
  28 + [ApiDescriptionSettings(Tag = "绿纤产品管理", Name = "LqProduct", Order = 200)]
  29 + [Route("api/Extend/LqProduct")]
  30 + public class LqProductService : IDynamicApiController, ITransient, ILqProductService
  31 + {
  32 + private readonly IUserManager _userManager;
  33 + private readonly ILogger<LqProductService> _logger;
  34 + private readonly ISqlSugarClient _db;
  35 +
  36 + /// <summary>
  37 + /// 构造函数
  38 + /// </summary>
  39 + /// <param name="userManager">用户管理器</param>
  40 + /// <param name="logger">日志记录器</param>
  41 + /// <param name="db">数据库客户端</param>
  42 + public LqProductService(IUserManager userManager, ILogger<LqProductService> logger, ISqlSugarClient db)
  43 + {
  44 + _userManager = userManager;
  45 + _logger = logger;
  46 + _db = db;
  47 + }
  48 +
  49 + #region 添加产品信息
  50 + /// <summary>
  51 + /// 添加产品信息
  52 + /// </summary>
  53 + /// <remarks>
  54 + /// 创建新产品,包含产品的基本信息、价格、类别等
  55 + ///
  56 + /// 示例请求:
  57 + /// ```json
  58 + /// {
  59 + /// "productName": "产品名称",
  60 + /// "price": 100.00,
  61 + /// "productCategory": "产品类别",
  62 + /// "departmentId": "部门ID",
  63 + /// "standardUnit": "标准单位",
  64 + /// "onShelfStatus": 1,
  65 + /// "statisticsCategory": "统计分类",
  66 + /// "warehouse": "归属仓库",
  67 + /// "supplierName": "供应商名称"
  68 + /// }
  69 + /// ```
  70 + /// </remarks>
  71 + /// <param name="input">创建输入</param>
  72 + /// <returns>创建结果</returns>
  73 + /// <response code="200">创建成功</response>
  74 + /// <response code="400">请求参数错误</response>
  75 + /// <response code="500">服务器错误</response>
  76 + [HttpPost("Create")]
  77 + public async Task CreateAsync([FromBody] LqProductCrInput input)
  78 + {
  79 + try
  80 + {
  81 + // 检查产品名称是否已存在
  82 + var existingProduct = await _db.Queryable<LqProductEntity>()
  83 + .Where(x => x.ProductName == input.ProductName)
  84 + .FirstAsync();
  85 +
  86 + if (existingProduct != null)
  87 + {
  88 + throw NCCException.Oh($"产品名称'{input.ProductName}'已存在");
  89 + }
  90 +
  91 + // 创建产品
  92 + var productEntity = new LqProductEntity
  93 + {
  94 + Id = YitIdHelper.NextId().ToString(),
  95 + ProductName = input.ProductName,
  96 + Price = input.Price,
  97 + ProductCategory = input.ProductCategory,
  98 + DepartmentId = input.DepartmentId,
  99 + StandardUnit = input.StandardUnit,
  100 + OnShelfStatus = input.OnShelfStatus,
  101 + StatisticsCategory = input.StatisticsCategory,
  102 + Warehouse = input.Warehouse,
  103 + UnitConversion = input.UnitConversion,
  104 + SupplierName = input.SupplierName,
  105 + ContractSignDate = input.ContractSignDate,
  106 + ContractEndDate = input.ContractEndDate,
  107 + Remark = input.Remark,
  108 + CreateUser = _userManager.UserId,
  109 + CreateTime = DateTime.Now
  110 + };
  111 +
  112 + var isOk = await _db.Insertable(productEntity).ExecuteCommandAsync();
  113 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  114 + }
  115 + catch (Exception ex)
  116 + {
  117 + _logger.LogError(ex, "创建产品失败");
  118 + throw NCCException.Oh($"创建失败:{ex.Message}");
  119 + }
  120 + }
  121 + #endregion
  122 +
  123 + #region 更新产品信息
  124 + /// <summary>
  125 + /// 更新产品信息
  126 + /// </summary>
  127 + /// <param name="input">更新输入</param>
  128 + /// <returns>更新结果</returns>
  129 + [HttpPut("Update")]
  130 + public async Task UpdateAsync([FromBody] LqProductUpInput input)
  131 + {
  132 + try
  133 + {
  134 + var existingProduct = await _db.Queryable<LqProductEntity>()
  135 + .Where(x => x.Id == input.Id)
  136 + .FirstAsync();
  137 +
  138 + if (existingProduct == null)
  139 + {
  140 + throw NCCException.Oh("产品不存在");
  141 + }
  142 +
  143 + // 检查产品名称是否与其他产品重复
  144 + var duplicateProduct = await _db.Queryable<LqProductEntity>()
  145 + .Where(x => x.ProductName == input.ProductName && x.Id != input.Id)
  146 + .FirstAsync();
  147 +
  148 + if (duplicateProduct != null)
  149 + {
  150 + throw NCCException.Oh($"产品名称'{input.ProductName}'已存在");
  151 + }
  152 +
  153 + // 更新产品信息
  154 + existingProduct.ProductName = input.ProductName;
  155 + existingProduct.Price = input.Price;
  156 + existingProduct.ProductCategory = input.ProductCategory;
  157 + existingProduct.DepartmentId = input.DepartmentId;
  158 + existingProduct.StandardUnit = input.StandardUnit;
  159 + existingProduct.OnShelfStatus = input.OnShelfStatus;
  160 + existingProduct.StatisticsCategory = input.StatisticsCategory;
  161 + existingProduct.Warehouse = input.Warehouse;
  162 + existingProduct.UnitConversion = input.UnitConversion;
  163 + existingProduct.SupplierName = input.SupplierName;
  164 + existingProduct.ContractSignDate = input.ContractSignDate;
  165 + existingProduct.ContractEndDate = input.ContractEndDate;
  166 + existingProduct.Remark = input.Remark;
  167 + existingProduct.UpdateUser = _userManager.UserId;
  168 + existingProduct.UpdateTime = DateTime.Now;
  169 +
  170 + var isOk = await _db.Updateable(existingProduct).ExecuteCommandAsync();
  171 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  172 + }
  173 + catch (Exception ex)
  174 + {
  175 + _logger.LogError(ex, "更新产品失败");
  176 + throw NCCException.Oh($"更新失败:{ex.Message}");
  177 + }
  178 + }
  179 + #endregion
  180 +
  181 + #region 获取产品列表(包含现有库存)
  182 + /// <summary>
  183 + /// 获取产品列表(包含现有库存)
  184 + /// </summary>
  185 + /// <remarks>
  186 + /// 返回产品列表,每个产品包含当前库存数量(所有有效库存的总和)
  187 + ///
  188 + /// 示例请求:
  189 + /// ```json
  190 + /// {
  191 + /// "currentPage": 1,
  192 + /// "pageSize": 10,
  193 + /// "sidx": "createTime",
  194 + /// "sort": "desc",
  195 + /// "productName": "产品名称",
  196 + /// "productCategory": "产品类别",
  197 + /// "onShelfStatus": 1
  198 + /// }
  199 + /// ```
  200 + /// </remarks>
  201 + /// <param name="input">查询输入</param>
  202 + /// <returns>产品列表(包含现有库存)</returns>
  203 + /// <response code="200">查询成功</response>
  204 + /// <response code="500">服务器错误</response>
  205 + [HttpGet("GetList")]
  206 + public async Task<dynamic> GetListAsync([FromQuery] LqProductListQueryInput input)
  207 + {
  208 + try
  209 + {
  210 + var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx;
  211 + var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
  212 +
  213 + // 查询产品信息
  214 + var data = await _db.Queryable<LqProductEntity>()
  215 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductName), x => x.ProductName.Contains(input.ProductName))
  216 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), x => x.ProductCategory == input.ProductCategory)
  217 + .WhereIF(!string.IsNullOrWhiteSpace(input.DepartmentId), x => x.DepartmentId == input.DepartmentId)
  218 + .WhereIF(input.OnShelfStatus.HasValue, x => x.OnShelfStatus == input.OnShelfStatus.Value)
  219 + .WhereIF(!string.IsNullOrWhiteSpace(input.StatisticsCategory), x => x.StatisticsCategory == input.StatisticsCategory)
  220 + .WhereIF(!string.IsNullOrWhiteSpace(input.Warehouse), x => x.Warehouse == input.Warehouse)
  221 + .WhereIF(!string.IsNullOrWhiteSpace(input.SupplierName), x => x.SupplierName != null && x.SupplierName.Contains(input.SupplierName))
  222 + .WhereIF(input.PriceMin.HasValue, x => x.Price >= input.PriceMin.Value)
  223 + .WhereIF(input.PriceMax.HasValue, x => x.Price <= input.PriceMax.Value)
  224 + .Select(x => new LqProductListOutput
  225 + {
  226 + id = x.Id,
  227 + productName = x.ProductName,
  228 + price = x.Price,
  229 + productCategory = x.ProductCategory,
  230 + departmentId = x.DepartmentId,
  231 + departmentName = "",
  232 + standardUnit = x.StandardUnit,
  233 + onShelfStatus = x.OnShelfStatus,
  234 + statisticsCategory = x.StatisticsCategory,
  235 + warehouse = x.Warehouse,
  236 + unitConversion = x.UnitConversion,
  237 + supplierName = x.SupplierName,
  238 + contractSignDate = x.ContractSignDate,
  239 + contractEndDate = x.ContractEndDate,
  240 + remark = x.Remark,
  241 + currentInventory = 0, // 稍后计算
  242 + createUser = x.CreateUser,
  243 + createUserName = "",
  244 + createTime = x.CreateTime,
  245 + updateUser = x.UpdateUser,
  246 + updateUserName = "",
  247 + updateTime = x.UpdateTime
  248 + })
  249 + .MergeTable()
  250 + .OrderBy(sidx + " " + sort)
  251 + .ToPagedListAsync(input.currentPage, input.pageSize);
  252 +
  253 + // 批量查询每个产品的库存数量
  254 + var productIds = data.list.Select(x => x.id).ToList();
  255 + if (productIds.Any())
  256 + {
  257 + var inventoryDict = await _db.Queryable<LqInventoryEntity>()
  258 + .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  259 + .GroupBy(x => x.ProductId)
  260 + .Select(x => new { ProductId = x.ProductId, TotalQuantity = SqlFunc.AggregateSum((decimal?)x.Quantity) })
  261 + .ToListAsync();
  262 +
  263 + var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0);
  264 +
  265 + // 填充库存数量
  266 + foreach (var item in data.list)
  267 + {
  268 + item.currentInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0;
  269 + }
  270 + }
  271 +
  272 + // 补充用户名称信息
  273 + var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser, x.departmentId })
  274 + .Where(x => !string.IsNullOrEmpty(x))
  275 + .Distinct()
  276 + .ToList();
  277 +
  278 + if (userIds.Any())
  279 + {
  280 + var userList = await _db.Queryable<UserEntity>()
  281 + .Where(x => userIds.Contains(x.Id))
  282 + .Select(x => new { x.Id, x.RealName })
  283 + .ToListAsync();
  284 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  285 +
  286 + foreach (var item in data.list)
  287 + {
  288 + if (!string.IsNullOrEmpty(item.createUser) && userDict.ContainsKey(item.createUser))
  289 + item.createUserName = userDict[item.createUser];
  290 + if (!string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser))
  291 + item.updateUserName = userDict[item.updateUser];
  292 + if (!string.IsNullOrEmpty(item.departmentId) && userDict.ContainsKey(item.departmentId))
  293 + item.departmentName = userDict[item.departmentId];
  294 + }
  295 + }
  296 +
  297 + return PageResult<LqProductListOutput>.SqlSugarPageResult(data);
  298 + }
  299 + catch (Exception ex)
  300 + {
  301 + _logger.LogError(ex, "获取产品列表失败");
  302 + throw NCCException.Oh($"获取产品列表失败:{ex.Message}");
  303 + }
  304 + }
  305 + #endregion
  306 +
  307 + #region 获取产品详情
  308 + /// <summary>
  309 + /// 获取产品详情
  310 + /// </summary>
  311 + /// <param name="id">产品ID</param>
  312 + /// <returns>产品详情</returns>
  313 + [HttpGet("GetInfo")]
  314 + public async Task<dynamic> GetInfoAsync([FromQuery] string id)
  315 + {
  316 + try
  317 + {
  318 + if (string.IsNullOrWhiteSpace(id))
  319 + {
  320 + throw NCCException.Oh("产品ID不能为空");
  321 + }
  322 +
  323 + var product = await _db.Queryable<LqProductEntity>()
  324 + .Where(x => x.Id == id)
  325 + .FirstAsync();
  326 +
  327 + if (product == null)
  328 + {
  329 + throw NCCException.Oh("产品不存在");
  330 + }
  331 +
  332 + var result = new LqProductInfoOutput
  333 + {
  334 + id = product.Id,
  335 + productName = product.ProductName,
  336 + price = product.Price,
  337 + productCategory = product.ProductCategory,
  338 + departmentId = product.DepartmentId,
  339 + departmentName = "",
  340 + standardUnit = product.StandardUnit,
  341 + onShelfStatus = product.OnShelfStatus,
  342 + statisticsCategory = product.StatisticsCategory,
  343 + warehouse = product.Warehouse,
  344 + unitConversion = product.UnitConversion,
  345 + supplierName = product.SupplierName,
  346 + contractSignDate = product.ContractSignDate,
  347 + contractEndDate = product.ContractEndDate,
  348 + remark = product.Remark,
  349 + createUser = product.CreateUser,
  350 + createUserName = "",
  351 + createTime = product.CreateTime,
  352 + updateUser = product.UpdateUser,
  353 + updateUserName = "",
  354 + updateTime = product.UpdateTime
  355 + };
  356 +
  357 + // 补充用户名称信息
  358 + var userIds = new[] { result.createUser, result.updateUser, result.departmentId }
  359 + .Where(x => !string.IsNullOrEmpty(x))
  360 + .Distinct()
  361 + .ToList();
  362 +
  363 + if (userIds.Any())
  364 + {
  365 + var userList = await _db.Queryable<UserEntity>()
  366 + .Where(x => userIds.Contains(x.Id))
  367 + .Select(x => new { x.Id, x.RealName })
  368 + .ToListAsync();
  369 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  370 +
  371 + if (!string.IsNullOrEmpty(result.createUser) && userDict.ContainsKey(result.createUser))
  372 + result.createUserName = userDict[result.createUser];
  373 + if (!string.IsNullOrEmpty(result.updateUser) && userDict.ContainsKey(result.updateUser))
  374 + result.updateUserName = userDict[result.updateUser];
  375 + if (!string.IsNullOrEmpty(result.departmentId) && userDict.ContainsKey(result.departmentId))
  376 + result.departmentName = userDict[result.departmentId];
  377 + }
  378 +
  379 + return result;
  380 + }
  381 + catch (Exception ex)
  382 + {
  383 + _logger.LogError(ex, "获取产品详情失败");
  384 + throw NCCException.Oh($"获取产品详情失败:{ex.Message}");
  385 + }
  386 + }
  387 + #endregion
  388 +
  389 + #region 获取产品下拉列表
  390 + /// <summary>
  391 + /// 获取产品下拉列表
  392 + /// </summary>
  393 + /// <remarks>
  394 + /// 用于下拉选择的产品列表,只返回上架的产品
  395 + ///
  396 + /// 示例请求:
  397 + /// ```
  398 + /// GET /api/Extend/LqProduct/GetSelectList?productName=产品
  399 + /// ```
  400 + /// </remarks>
  401 + /// <param name="productName">产品名称(可选,模糊查询)</param>
  402 + /// <param name="onShelfStatus">上架状态(可选,1:上架 0:下架,默认1)</param>
  403 + /// <returns>产品下拉列表</returns>
  404 + /// <response code="200">查询成功</response>
  405 + /// <response code="500">服务器错误</response>
  406 + [HttpGet("GetSelectList")]
  407 + public async Task<dynamic> GetSelectListAsync([FromQuery] string productName = null, [FromQuery] int? onShelfStatus = 1)
  408 + {
  409 + try
  410 + {
  411 + var query = _db.Queryable<LqProductEntity>()
  412 + .WhereIF(!string.IsNullOrWhiteSpace(productName), x => x.ProductName.Contains(productName))
  413 + .WhereIF(onShelfStatus.HasValue, x => x.OnShelfStatus == onShelfStatus.Value);
  414 +
  415 + var products = await query
  416 + .Select(x => new LqProductSelectOutput
  417 + {
  418 + id = x.Id,
  419 + productName = x.ProductName,
  420 + price = x.Price,
  421 + standardUnit = x.StandardUnit,
  422 + currentInventory = 0 // 稍后计算
  423 + })
  424 + .ToListAsync();
  425 +
  426 + // 批量查询每个产品的库存数量
  427 + var productIds = products.Select(x => x.id).ToList();
  428 + if (productIds.Any())
  429 + {
  430 + var inventoryDict = await _db.Queryable<LqInventoryEntity>()
  431 + .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  432 + .GroupBy(x => x.ProductId)
  433 + .Select(x => new { ProductId = x.ProductId, TotalQuantity = SqlFunc.AggregateSum((decimal?)x.Quantity) })
  434 + .ToListAsync();
  435 +
  436 + var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0);
  437 +
  438 + // 填充库存数量
  439 + foreach (var item in products)
  440 + {
  441 + item.currentInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0;
  442 + }
  443 + }
  444 +
  445 + return products;
  446 + }
  447 + catch (Exception ex)
  448 + {
  449 + _logger.LogError(ex, "获取产品下拉列表失败");
  450 + throw NCCException.Oh($"获取产品下拉列表失败:{ex.Message}");
  451 + }
  452 + }
  453 + #endregion
  454 +
  455 + #region 作废产品
  456 + /// <summary>
  457 + /// 作废产品
  458 + /// </summary>
  459 + /// <remarks>
  460 + /// 作废产品,将产品的上架状态改为下架(OnShelfStatus = 0)
  461 + /// 注意:如果产品有已使用的库存,需要先检查是否允许作废
  462 + /// </remarks>
  463 + /// <param name="id">产品ID</param>
  464 + /// <param name="remarks">作废备注</param>
  465 + /// <returns>作废结果</returns>
  466 + /// <response code="200">作废成功</response>
  467 + /// <response code="400">产品不存在或已被作废</response>
  468 + /// <response code="500">服务器错误</response>
  469 + [HttpPut("Cancel/{id}")]
  470 + public async Task CancelAsync([FromRoute] string id, [FromQuery] string remarks = null)
  471 + {
  472 + try
  473 + {
  474 + if (string.IsNullOrWhiteSpace(id))
  475 + {
  476 + throw NCCException.Oh("产品ID不能为空");
  477 + }
  478 +
  479 + var product = await _db.Queryable<LqProductEntity>()
  480 + .Where(x => x.Id == id)
  481 + .FirstAsync();
  482 +
  483 + if (product == null)
  484 + {
  485 + throw NCCException.Oh("产品不存在");
  486 + }
  487 +
  488 + // 计算该产品的总库存数量(所有有效库存的总和)
  489 + var totalInventory = await _db.Queryable<LqInventoryEntity>()
  490 + .Where(x => x.ProductId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  491 + .SumAsync(x => (int?)x.Quantity) ?? 0;
  492 +
  493 + // 计算该产品的已使用数量
  494 + var totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
  495 + .Where(x => x.ProductId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  496 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  497 +
  498 + // 作废后的总库存(作废后库存为0)
  499 + var inventoryAfterCancel = 0;
  500 +
  501 + // 判断:如果已使用的数量超过了作废后的总数量,不允许作废
  502 + if (totalUsage > inventoryAfterCancel)
  503 + {
  504 + throw NCCException.Oh($"不允许作废该产品,已使用数量({totalUsage})超过了作废后的总数量({inventoryAfterCancel})");
  505 + }
  506 +
  507 + // 作废产品(将上架状态改为下架)
  508 + product.OnShelfStatus = 0;
  509 + if (!string.IsNullOrEmpty(remarks))
  510 + {
  511 + product.Remark = string.IsNullOrEmpty(product.Remark)
  512 + ? $"作废备注:{remarks}"
  513 + : $"{product.Remark}\n作废备注:{remarks}";
  514 + }
  515 + product.UpdateUser = _userManager.UserId;
  516 + product.UpdateTime = DateTime.Now;
  517 +
  518 + var isOk = await _db.Updateable(product).ExecuteCommandAsync();
  519 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  520 + }
  521 + catch (Exception ex)
  522 + {
  523 + _logger.LogError(ex, "作废产品失败");
  524 + throw NCCException.Oh($"作废失败:{ex.Message}");
  525 + }
  526 + }
  527 + #endregion
  528 +
  529 + #region 查看产品的所有库存和使用记录
  530 + /// <summary>
  531 + /// 查看产品的所有库存和使用记录
  532 + /// </summary>
  533 + /// <remarks>
  534 + /// 返回产品的详细信息、所有库存记录和使用记录
  535 + ///
  536 + /// 示例请求:
  537 + /// ```
  538 + /// GET /api/Extend/LqProduct/GetInventoryDetail?productId=产品ID
  539 + /// ```
  540 + /// </remarks>
  541 + /// <param name="productId">产品ID</param>
  542 + /// <returns>产品库存详情(包含库存列表和使用记录列表)</returns>
  543 + /// <response code="200">查询成功</response>
  544 + /// <response code="400">产品不存在</response>
  545 + /// <response code="500">服务器错误</response>
  546 + [HttpGet("GetInventoryDetail")]
  547 + public async Task<dynamic> GetInventoryDetailAsync([FromQuery] string productId)
  548 + {
  549 + try
  550 + {
  551 + if (string.IsNullOrWhiteSpace(productId))
  552 + {
  553 + throw NCCException.Oh("产品ID不能为空");
  554 + }
  555 +
  556 + // 获取产品信息
  557 + var productInfo = await GetInfoAsync(productId);
  558 + if (productInfo == null)
  559 + {
  560 + throw NCCException.Oh("产品不存在");
  561 + }
  562 +
  563 + // 获取该产品的所有库存记录
  564 + var inventoryList = await _db.Queryable<LqInventoryEntity>()
  565 + .Where(x => x.ProductId == productId)
  566 + .OrderBy(x => x.CreateTime, OrderByType.Desc)
  567 + .Select(x => new ProductInventoryItem
  568 + {
  569 + id = x.Id,
  570 + quantity = x.Quantity,
  571 + stockInTime = x.StockInTime,
  572 + productionDate = x.ProductionDate,
  573 + shelfLife = x.ShelfLife,
  574 + batchNumber = x.BatchNumber,
  575 + isEffective = x.IsEffective,
  576 + createTime = x.CreateTime
  577 + })
  578 + .ToListAsync();
  579 +
  580 + // 获取该产品的所有使用记录
  581 + var usageList = await _db.Queryable<LqInventoryUsageEntity, LqMdxxEntity>(
  582 + (usage, store) => usage.StoreId == store.Id)
  583 + .Where((usage, store) => usage.ProductId == productId)
  584 + .OrderBy((usage, store) => usage.UsageTime, OrderByType.Desc)
  585 + .Select((usage, store) => new ProductUsageItem
  586 + {
  587 + id = usage.Id,
  588 + storeId = usage.StoreId,
  589 + storeName = store.Dm,
  590 + usageTime = usage.UsageTime,
  591 + usageQuantity = usage.UsageQuantity,
  592 + relatedConsumeId = usage.RelatedConsumeId,
  593 + createTime = usage.CreateTime,
  594 + isEffective = usage.IsEffective
  595 + })
  596 + .ToListAsync();
  597 +
  598 + // 计算总库存、已使用数量、可用库存
  599 + var totalInventory = inventoryList.Where(x => x.isEffective == StatusEnum.有效.GetHashCode()).Sum(x => x.quantity);
  600 + var totalUsage = usageList.Where(x => x.isEffective == StatusEnum.有效.GetHashCode()).Sum(x => x.usageQuantity);
  601 + var availableInventory = totalInventory - totalUsage;
  602 +
  603 + var result = new LqProductInventoryDetailOutput
  604 + {
  605 + productInfo = productInfo as LqProductInfoOutput,
  606 + inventoryList = inventoryList,
  607 + usageList = usageList,
  608 + totalInventory = totalInventory,
  609 + totalUsage = totalUsage,
  610 + availableInventory = availableInventory
  611 + };
  612 +
  613 + return result;
  614 + }
  615 + catch (Exception ex)
  616 + {
  617 + _logger.LogError(ex, "获取产品库存详情失败");
  618 + throw NCCException.Oh($"获取产品库存详情失败:{ex.Message}");
  619 + }
  620 + }
  621 + #endregion
  622 +
  623 + #region 产品上下架
  624 + /// <summary>
  625 + /// 产品上下架
  626 + /// </summary>
  627 + /// <remarks>
  628 + /// 设置产品的上架状态,1表示上架,0表示下架
  629 + ///
  630 + /// 示例请求:
  631 + /// ```json
  632 + /// {
  633 + /// "productId": "产品ID",
  634 + /// "onShelfStatus": 1
  635 + /// }
  636 + /// ```
  637 + ///
  638 + /// 参数说明:
  639 + /// - productId: 产品ID(必填)
  640 + /// - onShelfStatus: 上架状态,1表示上架,0表示下架(必填)
  641 + /// </remarks>
  642 + /// <param name="input">上下架输入</param>
  643 + /// <returns>操作结果</returns>
  644 + /// <response code="200">操作成功</response>
  645 + /// <response code="400">产品不存在或参数错误</response>
  646 + /// <response code="500">服务器错误</response>
  647 + [HttpPut("ToggleShelf")]
  648 + public async Task ToggleShelfAsync([FromBody] LqProductToggleShelfInput input)
  649 + {
  650 + try
  651 + {
  652 + if (string.IsNullOrWhiteSpace(input.ProductId))
  653 + {
  654 + throw NCCException.Oh("产品ID不能为空");
  655 + }
  656 +
  657 + if (input.OnShelfStatus != 0 && input.OnShelfStatus != 1)
  658 + {
  659 + throw NCCException.Oh("上架状态只能是0(下架)或1(上架)");
  660 + }
  661 +
  662 + var product = await _db.Queryable<LqProductEntity>()
  663 + .Where(x => x.Id == input.ProductId)
  664 + .FirstAsync();
  665 +
  666 + if (product == null)
  667 + {
  668 + throw NCCException.Oh("产品不存在");
  669 + }
  670 +
  671 + // 更新上架状态
  672 + product.OnShelfStatus = input.OnShelfStatus;
  673 + product.UpdateUser = _userManager.UserId;
  674 + product.UpdateTime = DateTime.Now;
  675 +
  676 + var isOk = await _db.Updateable(product)
  677 + .UpdateColumns(x => new { x.OnShelfStatus, x.UpdateUser, x.UpdateTime })
  678 + .ExecuteCommandAsync();
  679 +
  680 + if (!(isOk > 0))
  681 + {
  682 + throw NCCException.Oh(ErrorCode.COM1000);
  683 + }
  684 + }
  685 + catch (Exception ex)
  686 + {
  687 + _logger.LogError(ex, "产品上下架操作失败");
  688 + throw NCCException.Oh($"产品上下架操作失败:{ex.Message}");
  689 + }
  690 + }
  691 + #endregion
  692 + }
  693 +}
  694 +
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
@@ -3512,39 +3512,59 @@ namespace NCC.Extend.LqStatistics @@ -3512,39 +3512,59 @@ namespace NCC.Extend.LqStatistics
3512 } 3512 }
3513 3513
3514 /// <summary> 3514 /// <summary>
3515 - /// 统计人头(月度去重客户数 3515 + /// 统计人头(从人次记录表统计,按健康师+月份+客户+数量去重后累加
3516 /// </summary> 3516 /// </summary>
3517 private async Task<int> GetHeadCount(string userId, string month) 3517 private async Task<int> GetHeadCount(string userId, string month)
3518 { 3518 {
  3519 + // 按健康师+月份+客户+数量去重,然后累加数量
  3520 + // 注意:userId 和 month 都是内部参数,相对安全
3519 var sql = $@" 3521 var sql = $@"
3520 - SELECT COUNT(DISTINCT hyhk.hy) as Count  
3521 - FROM lq_xh_jksyj jksyj  
3522 - INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id  
3523 - WHERE jksyj.jkszh = '{userId}'  
3524 - AND jksyj.F_IsEffective = 1  
3525 - AND hyhk.F_IsEffective = 1  
3526 - AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '{month}'"; 3522 + SELECT COALESCE(SUM(F_Quantity), 0) as Count
  3523 + FROM (
  3524 + SELECT
  3525 + F_PersonId,
  3526 + F_WorkMonth,
  3527 + F_MemberId,
  3528 + F_Quantity
  3529 + FROM lq_person_times_record
  3530 + WHERE F_PersonId = '{userId}'
  3531 + AND F_PersonType = '健康师'
  3532 + AND F_WorkMonth = '{month}'
  3533 + AND F_IsEffective = 1
  3534 + GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity
  3535 + ) as distinct_records";
3527 3536
3528 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql); 3537 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql);
3529 - return Convert.ToInt32(result.FirstOrDefault()?.Count ?? 0); 3538 + var count = result.FirstOrDefault()?.Count;
  3539 + return count != null ? Convert.ToInt32(count) : 0;
3530 } 3540 }
3531 3541
3532 /// <summary> 3542 /// <summary>
3533 - /// 统计人次(日度去重客户数 3543 + /// 统计人次(从人次记录表统计,按健康师+日期+客户+数量去重后累加
3534 /// </summary> 3544 /// </summary>
3535 private async Task<int> GetPersonCount(string userId, string month) 3545 private async Task<int> GetPersonCount(string userId, string month)
3536 { 3546 {
  3547 + // 按健康师+日期+客户+数量去重,然后累加数量
  3548 + // 注意:userId 和 month 都是内部参数,相对安全
3537 var sql = $@" 3549 var sql = $@"
3538 - SELECT COUNT(DISTINCT CONCAT(hyhk.hy, '-', DATE(hyhk.hksj))) as Count  
3539 - FROM lq_xh_jksyj jksyj  
3540 - INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id  
3541 - WHERE jksyj.jkszh = '{userId}'  
3542 - AND jksyj.F_IsEffective = 1  
3543 - AND hyhk.F_IsEffective = 1  
3544 - AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '{month}'"; 3550 + SELECT COALESCE(SUM(F_Quantity), 0) as Count
  3551 + FROM (
  3552 + SELECT
  3553 + F_PersonId,
  3554 + F_WorkDate,
  3555 + F_MemberId,
  3556 + F_Quantity
  3557 + FROM lq_person_times_record
  3558 + WHERE F_PersonId = '{userId}'
  3559 + AND F_PersonType = '健康师'
  3560 + AND F_WorkMonth = '{month}'
  3561 + AND F_IsEffective = 1
  3562 + GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity
  3563 + ) as distinct_records";
3545 3564
3546 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql); 3565 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql);
3547 - return Convert.ToInt32(result.FirstOrDefault()?.Count ?? 0); 3566 + var count = result.FirstOrDefault()?.Count;
  3567 + return count != null ? Convert.ToInt32(count) : 0;
3548 } 3568 }
3549 3569
3550 /// <summary> 3570 /// <summary>
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
@@ -1003,10 +1003,12 @@ namespace NCC.Extend.LqXhHyhk @@ -1003,10 +1003,12 @@ namespace NCC.Extend.LqXhHyhk
1003 } 1003 }
1004 } 1004 }
1005 } 1005 }
  1006 + // 剔除所有T区健康师(姓名中包含"T区"的健康师)
  1007 + var NoTallJksyjEntities = allJksyjEntities.Where(x => x.Jks != null && x.Jks != "" && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList();
1006 //获取这次耗卡有多少个健康师 1008 //获取这次耗卡有多少个健康师
1007 - var jksCount = allJksyjEntities.GroupBy(x => x.Jks).Count(); 1009 + var jksCount = NoTallJksyjEntities.GroupBy(x => x.Jks).Count();
1008 //添加到人次表里面去 1010 //添加到人次表里面去
1009 - foreach (var item in allJksyjEntities.Select(x => x.Jks).Distinct()) 1011 + foreach (var item in NoTallJksyjEntities.Select(x => x.Jks).Distinct())
1010 { 1012 {
1011 LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity 1013 LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
1012 { 1014 {
@@ -1015,7 +1017,7 @@ namespace NCC.Extend.LqXhHyhk @@ -1015,7 +1017,7 @@ namespace NCC.Extend.LqXhHyhk
1015 BusinessType = "耗卡", 1017 BusinessType = "耗卡",
1016 PersonType = "健康师", 1018 PersonType = "健康师",
1017 PersonId = item, 1019 PersonId = item,
1018 - PersonName = allJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(), 1020 + PersonName = NoTallJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(),
1019 MemberId = entity.Hy, 1021 MemberId = entity.Hy,
1020 MemberName = memberInfo.Khmc, 1022 MemberName = memberInfo.Khmc,
1021 WorkDate = input.hksj, 1023 WorkDate = input.hksj,
@@ -1026,10 +1028,12 @@ namespace NCC.Extend.LqXhHyhk @@ -1026,10 +1028,12 @@ namespace NCC.Extend.LqXhHyhk
1026 }; 1028 };
1027 allPersonTimesRecordEntities.Add(personTimesRecordEntity); 1029 allPersonTimesRecordEntities.Add(personTimesRecordEntity);
1028 } 1030 }
  1031 + //剔除所有T区科技部老师(姓名中包含"T区"的科技部老师)
  1032 + var NoTallKjbsyjEntities = allKjbsyjEntities.Where(x => x.Kjbls != null && x.Kjbls != "" && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList();
1029 //获取这次耗卡有多少个科技部老师 1033 //获取这次耗卡有多少个科技部老师
1030 - var kjbCount = allKjbsyjEntities.GroupBy(x => x.Kjbls).Count(); 1034 + var kjbCount = NoTallKjbsyjEntities.GroupBy(x => x.Kjbls).Count();
1031 //添加到人次表里面去 1035 //添加到人次表里面去
1032 - foreach (var item in allKjbsyjEntities.Select(x => x.Kjbls).Distinct()) 1036 + foreach (var item in NoTallKjbsyjEntities.Select(x => x.Kjbls).Distinct())
1033 { 1037 {
1034 LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity 1038 LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
1035 { 1039 {
@@ -1038,7 +1042,7 @@ namespace NCC.Extend.LqXhHyhk @@ -1038,7 +1042,7 @@ namespace NCC.Extend.LqXhHyhk
1038 BusinessType = "耗卡", 1042 BusinessType = "耗卡",
1039 PersonType = "科技部老师", 1043 PersonType = "科技部老师",
1040 PersonId = item, 1044 PersonId = item,
1041 - PersonName = allKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(), 1045 + PersonName = NoTallKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(),
1042 MemberId = entity.Hy, 1046 MemberId = entity.Hy,
1043 MemberName = memberInfo.Khmc, 1047 MemberName = memberInfo.Khmc,
1044 WorkDate = input.hksj, 1048 WorkDate = input.hksj,
@@ -1360,10 +1364,12 @@ namespace NCC.Extend.LqXhHyhk @@ -1360,10 +1364,12 @@ namespace NCC.Extend.LqXhHyhk
1360 } 1364 }
1361 } 1365 }
1362 } 1366 }
  1367 + //剔除所有T区健康师(姓名中包含"T区"的健康师)
  1368 + var NoTallJksyjEntities = allJksyjEntities.Where(x => x.Jks != null && x.Jks != "" && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList();
1363 //获取这次耗卡有多少个健康师 1369 //获取这次耗卡有多少个健康师
1364 - var jksCount = allJksyjEntities.GroupBy(x => x.Jks).Count(); 1370 + var jksCount = NoTallJksyjEntities.GroupBy(x => x.Jks).Count();
1365 //添加到人次表里面去 1371 //添加到人次表里面去
1366 - foreach (var item in allJksyjEntities.Select(x => x.Jks).Distinct()) 1372 + foreach (var item in NoTallJksyjEntities.Select(x => x.Jks).Distinct())
1367 { 1373 {
1368 LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity 1374 LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
1369 { 1375 {
@@ -1372,7 +1378,7 @@ namespace NCC.Extend.LqXhHyhk @@ -1372,7 +1378,7 @@ namespace NCC.Extend.LqXhHyhk
1372 BusinessType = "耗卡", 1378 BusinessType = "耗卡",
1373 PersonType = "健康师", 1379 PersonType = "健康师",
1374 PersonId = item, 1380 PersonId = item,
1375 - PersonName = allJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(), 1381 + PersonName = NoTallJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(),
1376 MemberId = entity.Hy, 1382 MemberId = entity.Hy,
1377 MemberName = memberInfo.Khmc, 1383 MemberName = memberInfo.Khmc,
1378 WorkDate = input.hksj, 1384 WorkDate = input.hksj,
@@ -1383,10 +1389,12 @@ namespace NCC.Extend.LqXhHyhk @@ -1383,10 +1389,12 @@ namespace NCC.Extend.LqXhHyhk
1383 }; 1389 };
1384 allPersonTimesRecordEntities.Add(personTimesRecordEntity); 1390 allPersonTimesRecordEntities.Add(personTimesRecordEntity);
1385 } 1391 }
  1392 + //剔除所有T区科技部老师(姓名中包含"T区"的科技部老师)
  1393 + var NoTallKjbsyjEntities = allKjbsyjEntities.Where(x => x.Kjbls != null && x.Kjbls != "" && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList();
1386 //获取这次耗卡有多少个科技部老师 1394 //获取这次耗卡有多少个科技部老师
1387 - var kjbCount = allKjbsyjEntities.GroupBy(x => x.Kjbls).Count(); 1395 + var kjbCount = NoTallKjbsyjEntities.GroupBy(x => x.Kjbls).Count();
1388 //添加到人次表里面去 1396 //添加到人次表里面去
1389 - foreach (var item in allKjbsyjEntities.Select(x => x.Kjbls).Distinct()) 1397 + foreach (var item in NoTallKjbsyjEntities.Select(x => x.Kjbls).Distinct())
1390 { 1398 {
1391 LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity 1399 LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
1392 { 1400 {
@@ -1395,7 +1403,7 @@ namespace NCC.Extend.LqXhHyhk @@ -1395,7 +1403,7 @@ namespace NCC.Extend.LqXhHyhk
1395 BusinessType = "耗卡", 1403 BusinessType = "耗卡",
1396 PersonType = "科技部老师", 1404 PersonType = "科技部老师",
1397 PersonId = item, 1405 PersonId = item,
1398 - PersonName = allKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(), 1406 + PersonName = NoTallKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(),
1399 MemberId = entity.Hy, 1407 MemberId = entity.Hy,
1400 MemberName = memberInfo.Khmc, 1408 MemberName = memberInfo.Khmc,
1401 WorkDate = input.hksj, 1409 WorkDate = input.hksj,
@@ -1677,5 +1685,214 @@ namespace NCC.Extend.LqXhHyhk @@ -1677,5 +1685,214 @@ namespace NCC.Extend.LqXhHyhk
1677 } 1685 }
1678 #endregion 1686 #endregion
1679 1687
  1688 + #region 同步耗卡数据到人次记录表(同步的时候才用)
  1689 + /// <summary>
  1690 + /// 同步耗卡数据到人次记录表(同步的时候才用)
  1691 + /// </summary>
  1692 + /// <remarks>
  1693 + /// 将耗卡记录中的健康师和科技老师业绩数据同步到人次记录表(lq_person_times_record)
  1694 + ///
  1695 + /// 同步逻辑:
  1696 + /// 1. 查询所有有效的耗卡记录
  1697 + /// 2. 对于每条耗卡记录,查询关联的健康师业绩和科技老师业绩
  1698 + /// 3. 对健康师和科技老师进行去重(按人员ID)
  1699 + /// 4. 为每个去重后的健康师和科技老师创建人次记录
  1700 + /// 5. 计算人次数量:1 / 健康师(或科技老师)数量
  1701 + ///
  1702 + /// 字段映射:
  1703 + /// - F_BusinessId: 耗卡ID(lq_xh_hyhk.F_Id)
  1704 + /// - F_BusinessType: "耗卡"
  1705 + /// - F_PersonType: "健康师" 或 "科技老师"
  1706 + /// - F_PersonId: 健康师ID(lq_xh_jksyj.jks)或科技老师ID(lq_xh_kjbsyj.kjbls)
  1707 + /// - F_PersonName: 健康师姓名(lq_xh_jksyj.jksxm)或科技老师姓名(lq_xh_kjbsyj.kjblsxm)
  1708 + /// - F_MemberId: 客户ID(lq_xh_hyhk.hy)
  1709 + /// - F_MemberName: 客户姓名(lq_xh_hyhk.hymc)
  1710 + /// - F_WorkDate: 耗卡时间的日期部分(lq_xh_hyhk.hksj)
  1711 + /// - F_WorkMonth: 耗卡时间的月份部分,格式:YYYYMM(lq_xh_hyhk.hksj)
  1712 + /// - F_Quantity: 人次数量 = 1 / 健康师(或科技老师)数量
  1713 + ///
  1714 + /// 注意事项:
  1715 + /// - 只同步有效的耗卡记录(F_IsEffective = 1)
  1716 + /// - 只同步有效的健康师业绩和科技老师业绩(F_IsEffective = 1)
  1717 + /// - 如果已存在相同业务ID的记录,会先删除再重新插入
  1718 + /// </remarks>
  1719 + /// <param name="consumeId">可选,指定耗卡ID,如果为空则同步所有耗卡数据</param>
  1720 + /// <returns>同步结果,包含同步的记录数</returns>
  1721 + /// <response code="200">同步成功</response>
  1722 + /// <response code="500">服务器内部错误</response>
  1723 + [HttpPost("sync-person-times-record")]
  1724 + public async Task<dynamic> SyncPersonTimesRecord([FromQuery] string consumeId = null)
  1725 + {
  1726 + try
  1727 + {
  1728 + _logger.LogInformation($"开始同步耗卡数据到人次记录表,耗卡ID: {consumeId ?? "全部"}");
  1729 + // 1. 查询耗卡记录
  1730 + var consumeQuery = _db.Queryable<LqXhHyhkEntity>().Where(x => x.IsEffective == StatusEnum.有效.GetHashCode());
  1731 + if (!string.IsNullOrEmpty(consumeId))
  1732 + {
  1733 + consumeQuery = consumeQuery.Where(x => x.Id == consumeId);
  1734 + }
  1735 + var consumeList = await consumeQuery.ToListAsync();
  1736 + if (!consumeList.Any())
  1737 + {
  1738 + return new { success = true, message = "没有需要同步的耗卡记录", count = 0 };
  1739 + }
  1740 + _logger.LogInformation($"找到 {consumeList.Count} 条耗卡记录需要同步");
  1741 + // 2. 批量查询关联数据
  1742 + var consumeIds = consumeList.Select(x => x.Id).ToList();
  1743 + // 查询健康师业绩
  1744 + var jksyjList = await _db.Queryable<LqXhJksyjEntity>().Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
  1745 +
  1746 + // 查询科技老师业绩
  1747 + var kjbsyjList = await _db.Queryable<LqXhKjbsyjEntity>().Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
  1748 +
  1749 + // 查询已存在的人次记录(按BusinessId去重,避免重复添加)
  1750 + var existingBusinessIds = await _db.Queryable<LqPersonTimesRecordEntity>()
  1751 + .Where(x => consumeIds.Contains(x.BusinessId) && x.BusinessType == "耗卡" && x.IsEffective == StatusEnum.有效.GetHashCode())
  1752 + .Select(x => x.BusinessId)
  1753 + .Distinct()
  1754 + .ToListAsync();
  1755 +
  1756 + // 3. 构建人次记录列表
  1757 + var personTimesRecords = new List<LqPersonTimesRecordEntity>();
  1758 +
  1759 + foreach (var consume in consumeList)
  1760 + {
  1761 + // 如果该耗卡记录已存在,跳过
  1762 + if (existingBusinessIds.Contains(consume.Id))
  1763 + {
  1764 + _logger.LogInformation($"耗卡记录 {consume.Id} 已存在人次记录,跳过");
  1765 + continue;
  1766 + }
  1767 +
  1768 + if (consume.Hksj == null)
  1769 + {
  1770 + _logger.LogWarning($"耗卡记录 {consume.Id} 的耗卡时间为空,跳过");
  1771 + continue;
  1772 + }
  1773 + var workDate = consume.Hksj.Value.Date; // 工作日期(用于人次统计)
  1774 + var workMonth = consume.Hksj.Value.ToString("yyyyMM"); // 工作月份(用于人头统计)
  1775 +
  1776 + // 处理健康师业绩:去重后计算人次数量(剔除T区健康师)
  1777 + var consumeJksyjList = jksyjList.Where(x => x.Glkdbh == consume.Id
  1778 + && !string.IsNullOrEmpty(x.Jks)
  1779 + && !string.IsNullOrEmpty(x.Jksxm)
  1780 + && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList();
  1781 + if (consumeJksyjList.Any())
  1782 + {
  1783 + // 按健康师ID去重
  1784 + var distinctJksyjList = consumeJksyjList
  1785 + .GroupBy(x => x.Jks)
  1786 + .Select(g => g.First())
  1787 + .ToList();
  1788 +
  1789 + // 计算人次数量:1 / 健康师数量
  1790 + var jksQuantity = distinctJksyjList.Count > 0 ? 1.0m / distinctJksyjList.Count : 0;
  1791 +
  1792 + foreach (var jksyj in distinctJksyjList)
  1793 + {
  1794 + personTimesRecords.Add(new LqPersonTimesRecordEntity
  1795 + {
  1796 + Id = YitIdHelper.NextId().ToString(),
  1797 + BusinessId = consume.Id,
  1798 + BusinessType = "耗卡",
  1799 + PersonType = "健康师",
  1800 + PersonId = jksyj.Jks,
  1801 + PersonName = jksyj.Jksxm,
  1802 + MemberId = consume.Hy,
  1803 + MemberName = consume.Hymc,
  1804 + WorkDate = workDate,
  1805 + WorkMonth = workMonth,
  1806 + Quantity = jksQuantity,
  1807 + CreateTime = DateTime.Now,
  1808 + IsEffective = StatusEnum.有效.GetHashCode()
  1809 + });
  1810 + }
  1811 + }
  1812 +
  1813 + // 处理科技老师业绩:去重后计算人次数量(剔除T区科技老师)
  1814 + var consumeKjbsyjList = kjbsyjList.Where(x => x.Glkdbh == consume.Id
  1815 + && !string.IsNullOrEmpty(x.Kjbls)
  1816 + && !string.IsNullOrEmpty(x.Kjblsxm)
  1817 + && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList();
  1818 + if (consumeKjbsyjList.Any())
  1819 + {
  1820 + // 按科技老师ID去重
  1821 + var distinctKjbsyjList = consumeKjbsyjList
  1822 + .GroupBy(x => x.Kjbls)
  1823 + .Select(g => g.First())
  1824 + .ToList();
  1825 +
  1826 + // 计算人次数量:1 / 科技老师数量
  1827 + var kjbsQuantity = distinctKjbsyjList.Count > 0 ? 1.0m / distinctKjbsyjList.Count : 0;
  1828 +
  1829 + foreach (var kjbsyj in distinctKjbsyjList)
  1830 + {
  1831 + personTimesRecords.Add(new LqPersonTimesRecordEntity
  1832 + {
  1833 + Id = YitIdHelper.NextId().ToString(),
  1834 + BusinessId = consume.Id,
  1835 + BusinessType = "耗卡",
  1836 + PersonType = "科技老师",
  1837 + PersonId = kjbsyj.Kjbls,
  1838 + PersonName = kjbsyj.Kjblsxm,
  1839 + MemberId = consume.Hy,
  1840 + MemberName = consume.Hymc,
  1841 + WorkDate = workDate,
  1842 + WorkMonth = workMonth,
  1843 + Quantity = kjbsQuantity,
  1844 + CreateTime = DateTime.Now,
  1845 + IsEffective = StatusEnum.有效.GetHashCode()
  1846 + });
  1847 + }
  1848 + }
  1849 + }
  1850 +
  1851 + // 4. 使用事务保存数据
  1852 + var result = await _db.Ado.UseTranAsync(async () =>
  1853 + {
  1854 + // 如果指定了耗卡ID,删除该耗卡的旧记录(用于重新同步)
  1855 + // 如果没有指定耗卡ID,不删除任何记录(因为已经在构建记录列表时跳过了已存在的记录)
  1856 + if (!string.IsNullOrEmpty(consumeId))
  1857 + {
  1858 + await _db.Deleteable<LqPersonTimesRecordEntity>()
  1859 + .Where(x => x.BusinessId == consumeId && x.BusinessType == "耗卡")
  1860 + .ExecuteCommandAsync();
  1861 + }
  1862 +
  1863 + // 批量插入新记录
  1864 + if (personTimesRecords.Any())
  1865 + {
  1866 + // 分批插入,每批1000条
  1867 + var batchSize = 1000;
  1868 + for (int i = 0; i < personTimesRecords.Count; i += batchSize)
  1869 + {
  1870 + var batch = personTimesRecords.Skip(i).Take(batchSize).ToList();
  1871 + await _db.Insertable(batch).ExecuteCommandAsync();
  1872 + }
  1873 + }
  1874 +
  1875 + return personTimesRecords.Count;
  1876 + });
  1877 +
  1878 + if (result.IsSuccess)
  1879 + {
  1880 + _logger.LogInformation($"同步完成,共同步 {result.Data} 条人次记录");
  1881 + return new { success = true, message = "同步成功", count = result.Data };
  1882 + }
  1883 + else
  1884 + {
  1885 + _logger.LogError($"同步失败: {result.ErrorMessage}");
  1886 + throw NCCException.Oh(ErrorCode.COM1000, $"同步失败: {result.ErrorMessage}");
  1887 + }
  1888 + }
  1889 + catch (Exception ex)
  1890 + {
  1891 + _logger.LogError(ex, $"同步耗卡数据到人次记录表失败: {ex.ToString()}");
  1892 + throw NCCException.Oh(ErrorCode.COM1005, $"同步耗卡数据到人次记录表失败: {ex.Message}");
  1893 + }
  1894 + }
  1895 + #endregion
  1896 +
1680 } 1897 }
1681 } 1898 }
sql/拆分库存表为产品表和库存表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 拆分库存表为产品表和库存表
  3 +-- ============================================
  4 +-- 说明:将 lq_inventory 表拆分为 lq_product(产品表)和 lq_inventory(库存表)
  5 +--
  6 +-- 注意:此脚本只创建新表,不迁移数据
  7 +-- ============================================
  8 +
  9 +-- ============================================
  10 +-- 1. 创建产品表(lq_product)
  11 +-- ============================================
  12 +CREATE TABLE IF NOT EXISTS `lq_product` (
  13 + `F_Id` VARCHAR(50) NOT NULL COMMENT '产品ID',
  14 + `F_ProductName` VARCHAR(200) NULL COMMENT '产品名称',
  15 + `F_Price` DECIMAL(18,2) NULL DEFAULT 0 COMMENT '价格',
  16 + `F_ProductCategory` VARCHAR(50) NULL COMMENT '产品类别',
  17 + `F_DepartmentId` VARCHAR(50) NULL COMMENT '归属部门ID',
  18 + `F_StandardUnit` VARCHAR(20) NULL COMMENT '标准单位',
  19 + `F_OnShelfStatus` INT NULL DEFAULT 1 COMMENT '上架状态(1:上架 0:下架)',
  20 + `F_StatisticsCategory` VARCHAR(50) NULL COMMENT '统计分类',
  21 + `F_Warehouse` VARCHAR(100) NULL COMMENT '归属仓库',
  22 + `F_UnitConversion` VARCHAR(200) NULL COMMENT '单位换算',
  23 + `F_SupplierName` VARCHAR(200) NULL COMMENT '供应商名称',
  24 + `F_ContractSignDate` DATETIME NULL COMMENT '合同签订日期',
  25 + `F_ContractEndDate` DATETIME NULL COMMENT '合同结束日期',
  26 + `F_Remark` VARCHAR(1000) NULL COMMENT '备注',
  27 + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID',
  28 + `F_CreateTime` DATETIME NULL COMMENT '创建时间',
  29 + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID',
  30 + `F_UpdateTime` DATETIME NULL COMMENT '更新时间',
  31 + PRIMARY KEY (`F_Id`),
  32 + INDEX `idx_product_category` (`F_ProductCategory`) COMMENT '产品类别索引',
  33 + INDEX `idx_department` (`F_DepartmentId`) COMMENT '部门索引',
  34 + INDEX `idx_onshelf_status` (`F_OnShelfStatus`) COMMENT '上架状态索引'
  35 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品表';
  36 +
  37 +-- ============================================
  38 +-- 2. 修改库存表结构(lq_inventory)
  39 +-- ============================================
  40 +-- 注意:MySQL不支持 IF NOT EXISTS 语法,需要先检查字段是否存在
  41 +-- 如果字段已存在,执行对应的ALTER语句会报错,可以忽略
  42 +
  43 +-- 2.1 添加产品ID字段
  44 +-- 如果字段已存在,会报错,可以忽略
  45 +ALTER TABLE `lq_inventory`
  46 +ADD COLUMN `F_ProductId` VARCHAR(50) NULL COMMENT '产品ID(关联产品表)' AFTER `F_Id`;
  47 +
  48 +-- 2.2 添加新字段
  49 +-- 如果字段已存在,会报错,可以忽略
  50 +ALTER TABLE `lq_inventory`
  51 +ADD COLUMN `F_StockInTime` DATETIME NULL COMMENT '入库时间' AFTER `F_Quantity`;
  52 +
  53 +ALTER TABLE `lq_inventory`
  54 +ADD COLUMN `F_ProductionDate` DATETIME NULL COMMENT '生产日期' AFTER `F_StockInTime`;
  55 +
  56 +ALTER TABLE `lq_inventory`
  57 +ADD COLUMN `F_ShelfLife` INT NULL COMMENT '保质期(天数)' AFTER `F_ProductionDate`;
  58 +
  59 +ALTER TABLE `lq_inventory`
  60 +ADD COLUMN `F_BatchNumber` VARCHAR(100) NULL COMMENT '批次号' AFTER `F_ShelfLife`;
  61 +
  62 +-- 2.3 删除产品相关字段(可选,执行前请先备份数据)
  63 +-- 注意:如果字段不存在,会报错,可以忽略
  64 +-- ALTER TABLE `lq_inventory` DROP COLUMN `F_ProductName`;
  65 +-- ALTER TABLE `lq_inventory` DROP COLUMN `F_Price`;
  66 +-- ALTER TABLE `lq_inventory` DROP COLUMN `F_ProductCategory`;
  67 +-- ALTER TABLE `lq_inventory` DROP COLUMN `F_DepartmentId`;
  68 +-- ALTER TABLE `lq_inventory` DROP COLUMN `F_StandardUnit`;
  69 +
  70 +-- 2.4 添加产品ID索引
  71 +-- 如果索引已存在,会报错,可以忽略
  72 +ALTER TABLE `lq_inventory`
  73 +ADD INDEX `idx_product_id` (`F_ProductId`) COMMENT '产品ID索引';
  74 +
  75 +-- ============================================
  76 +-- 注意事项
  77 +-- ============================================
  78 +-- 1. 此脚本只创建新表,不迁移数据
  79 +-- 2. 如果需要删除旧字段,请先备份数据
  80 +-- 3. 建议在测试环境先验证
  81 +-- 4. 执行ALTER语句前,请确认表结构