Commit 273e501a67e35ce6595cb3443ae60f8b374df2e6
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 | 9 | public class LqInventoryCrInput |
| 10 | 10 | { |
| 11 | 11 | /// <summary> |
| 12 | - /// 产品名称 | |
| 12 | + /// 产品ID | |
| 13 | 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 | 19 | /// <summary> |
| 20 | - /// 价格 | |
| 20 | + /// 库存数量 | |
| 21 | 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 | 27 | /// <summary> |
| 28 | - /// 数量 | |
| 28 | + /// 入库时间 | |
| 29 | 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 | 33 | /// <summary> |
| 35 | - /// 产品类别 | |
| 34 | + /// 生产日期 | |
| 36 | 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 | 39 | /// <summary> |
| 43 | - /// 归属部门ID | |
| 40 | + /// 保质期(天数) | |
| 44 | 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 | 46 | /// <summary> |
| 51 | - /// 标准单位 | |
| 47 | + /// 批次号 | |
| 52 | 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 | 15 | public string id { get; set; } |
| 16 | 16 | |
| 17 | 17 | /// <summary> |
| 18 | + /// 产品ID | |
| 19 | + /// </summary> | |
| 20 | + [Display(Name = "产品ID")] | |
| 21 | + public string productId { get; set; } | |
| 22 | + | |
| 23 | + /// <summary> | |
| 18 | 24 | /// 产品名称 |
| 19 | 25 | /// </summary> |
| 20 | 26 | [Display(Name = "产品名称")] |
| 21 | 27 | public string productName { get; set; } |
| 22 | 28 | |
| 23 | 29 | /// <summary> |
| 24 | - /// 价格 | |
| 30 | + /// 产品价格 | |
| 25 | 31 | /// </summary> |
| 26 | - [Display(Name = "价格")] | |
| 27 | - public decimal price { get; set; } | |
| 32 | + [Display(Name = "产品价格")] | |
| 33 | + public decimal productPrice { get; set; } | |
| 28 | 34 | |
| 29 | 35 | /// <summary> |
| 30 | - /// 数量 | |
| 36 | + /// 库存数量 | |
| 31 | 37 | /// </summary> |
| 32 | - [Display(Name = "数量")] | |
| 38 | + [Display(Name = "库存数量")] | |
| 33 | 39 | public int quantity { get; set; } |
| 34 | 40 | |
| 35 | 41 | /// <summary> |
| 36 | - /// 产品类别 | |
| 42 | + /// 已使用数量 | |
| 43 | + /// </summary> | |
| 44 | + [Display(Name = "已使用数量")] | |
| 45 | + public int usedQuantity { get; set; } | |
| 46 | + | |
| 47 | + /// <summary> | |
| 48 | + /// 可用数量(库存数量 - 已使用数量) | |
| 37 | 49 | /// </summary> |
| 38 | - [Display(Name = "产品类别")] | |
| 39 | - public string productCategory { get; set; } | |
| 50 | + [Display(Name = "可用数量")] | |
| 51 | + public int availableQuantity { get; set; } | |
| 40 | 52 | |
| 41 | 53 | /// <summary> |
| 42 | - /// 归属部门ID | |
| 54 | + /// 入库时间 | |
| 43 | 55 | /// </summary> |
| 44 | - [Display(Name = "归属部门ID")] | |
| 45 | - public string departmentId { get; set; } | |
| 56 | + [Display(Name = "入库时间")] | |
| 57 | + public DateTime? stockInTime { get; set; } | |
| 46 | 58 | |
| 47 | 59 | /// <summary> |
| 48 | - /// 部门名称 | |
| 60 | + /// 生产日期 | |
| 49 | 61 | /// </summary> |
| 50 | - [Display(Name = "部门名称")] | |
| 51 | - public string departmentName { get; set; } | |
| 62 | + [Display(Name = "生产日期")] | |
| 63 | + public DateTime? productionDate { get; set; } | |
| 52 | 64 | |
| 53 | 65 | /// <summary> |
| 54 | - /// 标准单位 | |
| 66 | + /// 保质期(天数) | |
| 55 | 67 | /// </summary> |
| 56 | - [Display(Name = "标准单位")] | |
| 57 | - public string standardUnit { get; set; } | |
| 68 | + [Display(Name = "保质期")] | |
| 69 | + public int? shelfLife { get; set; } | |
| 58 | 70 | |
| 59 | 71 | /// <summary> |
| 60 | - /// 总价值 | |
| 72 | + /// 批次号 | |
| 61 | 73 | /// </summary> |
| 62 | - [Display(Name = "总价值")] | |
| 63 | - public decimal totalValue { get; set; } | |
| 74 | + [Display(Name = "批次号")] | |
| 75 | + public string batchNumber { get; set; } | |
| 64 | 76 | |
| 65 | 77 | /// <summary> |
| 66 | 78 | /// 创建人ID | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListQueryInput.cs
| ... | ... | @@ -10,34 +10,22 @@ namespace NCC.Extend.Entitys.Dto.LqInventory |
| 10 | 10 | public class LqInventoryListQueryInput : PageInputBase |
| 11 | 11 | { |
| 12 | 12 | /// <summary> |
| 13 | - /// 产品名称 | |
| 13 | + /// 产品ID | |
| 14 | 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 | 18 | /// <summary> |
| 25 | - /// 归属部门ID | |
| 19 | + /// 产品名称(模糊查询) | |
| 26 | 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 | 24 | /// <summary> |
| 37 | - /// 价格最大值 | |
| 25 | + /// 批次号 | |
| 38 | 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 | 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 | 18 | public string Id { get; set; } |
| 19 | 19 | |
| 20 | 20 | /// <summary> |
| 21 | - /// 产品名称 | |
| 21 | + /// 产品ID(关联产品表) | |
| 22 | 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 | 26 | /// <summary> |
| 27 | - /// 价格 | |
| 27 | + /// 库存数量 | |
| 28 | 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 | 32 | /// <summary> |
| 33 | - /// 数量 | |
| 33 | + /// 入库时间 | |
| 34 | 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 | 38 | /// <summary> |
| 39 | - /// 产品类别 | |
| 39 | + /// 生产日期 | |
| 40 | 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 | 44 | /// <summary> |
| 45 | - /// 归属部门ID | |
| 45 | + /// 保质期(天数) | |
| 46 | 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 | 50 | /// <summary> |
| 51 | - /// 标准单位 | |
| 51 | + /// 批次号 | |
| 52 | + /// </summary> | |
| 53 | + [SugarColumn(ColumnName = "F_BatchNumber")] | |
| 54 | + public string BatchNumber { get; set; } | |
| 55 | + | |
| 56 | + /// <summary> | |
| 57 | + /// 是否有效(1:有效 0:无效) | |
| 52 | 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 | 62 | /// <summary> |
| 57 | 63 | /// 创建人ID |
| ... | ... | @@ -76,11 +82,5 @@ namespace NCC.Extend.Entitys.lq_inventory |
| 76 | 82 | /// </summary> |
| 77 | 83 | [SugarColumn(ColumnName = "F_UpdateTime")] |
| 78 | 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 | 9 | /// </summary> |
| 10 | 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 | 129 | } |
| 130 | 130 | |
| 131 | 131 | // SQL查询:获取门店每日运营数据(只统计zxzt为"开店"的门店) |
| 132 | - // 优化:使用 LEFT JOIN 一次性统计所有项目数,避免多个子查询 | |
| 132 | + // 注意:避免笛卡尔积问题,消耗业绩和项目数分别统计,避免重复计算 | |
| 133 | 133 | var sql = $@" |
| 134 | 134 | SELECT |
| 135 | 135 | consume.Md as StoreId, |
| ... | ... | @@ -138,23 +138,40 @@ namespace NCC.Extend |
| 138 | 138 | COALESCE(COUNT(DISTINCT consume.Hy), 0) as HeadCount, |
| 139 | 139 | -- 人次(日度去重客户数) |
| 140 | 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 | 173 | FROM lq_xh_hyhk consume |
| 154 | 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 | 175 | WHERE consume.F_IsEffective = 1 |
| 159 | 176 | AND consume.Hksj >= '{startDate:yyyy-MM-dd} 00:00:00' |
| 160 | 177 | AND consume.Hksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
| 1 | 1 | using System; |
| 2 | +using System.Linq; | |
| 2 | 3 | using System.Threading.Tasks; |
| 3 | 4 | using Microsoft.AspNetCore.Mvc; |
| 4 | 5 | using Microsoft.Extensions.Logging; |
| ... | ... | @@ -10,6 +11,8 @@ using NCC.DynamicApiController; |
| 10 | 11 | using NCC.Extend.Entitys.Dto.LqInventory; |
| 11 | 12 | using NCC.Extend.Entitys.Enum; |
| 12 | 13 | using NCC.Extend.Entitys.lq_inventory; |
| 14 | +using NCC.Extend.Entitys.lq_inventory_usage; | |
| 15 | +using NCC.Extend.Entitys.lq_product; | |
| 13 | 16 | using NCC.Extend.Interfaces.LqInventory; |
| 14 | 17 | using NCC.FriendlyException; |
| 15 | 18 | using NCC.System.Entitys.Permission; |
| ... | ... | @@ -42,39 +45,58 @@ namespace NCC.Extend |
| 42 | 45 | _db = db; |
| 43 | 46 | } |
| 44 | 47 | |
| 45 | - #region 创建库存 | |
| 48 | + #region 添加库存信息 | |
| 46 | 49 | /// <summary> |
| 47 | - /// 创建库存 | |
| 50 | + /// 添加库存信息 | |
| 48 | 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 | 67 | /// <param name="input">创建输入</param> |
| 50 | 68 | /// <returns>创建结果</returns> |
| 69 | + /// <response code="200">创建成功</response> | |
| 70 | + /// <response code="400">产品不存在或参数错误</response> | |
| 71 | + /// <response code="500">服务器错误</response> | |
| 51 | 72 | [HttpPost("Create")] |
| 52 | 73 | public async Task CreateAsync([FromBody] LqInventoryCrInput input) |
| 53 | 74 | { |
| 54 | 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 | 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 | 88 | var inventoryEntity = new LqInventoryEntity |
| 67 | 89 | { |
| 68 | 90 | Id = YitIdHelper.NextId().ToString(), |
| 69 | - ProductName = input.ProductName, | |
| 70 | - Price = input.Price, | |
| 91 | + ProductId = input.ProductId, | |
| 71 | 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 | 98 | CreateUser = _userManager.UserId, |
| 76 | - CreateTime = DateTime.Now, | |
| 77 | - IsEffective = StatusEnum.有效.GetHashCode() | |
| 99 | + CreateTime = DateTime.Now | |
| 78 | 100 | }; |
| 79 | 101 | |
| 80 | 102 | var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync(); |
| ... | ... | @@ -88,9 +110,9 @@ namespace NCC.Extend |
| 88 | 110 | } |
| 89 | 111 | #endregion |
| 90 | 112 | |
| 91 | - #region 更新库存 | |
| 113 | + #region 更新库存信息 | |
| 92 | 114 | /// <summary> |
| 93 | - /// 更新库存 | |
| 115 | + /// 更新库存信息 | |
| 94 | 116 | /// </summary> |
| 95 | 117 | /// <param name="input">更新输入</param> |
| 96 | 118 | /// <returns>更新结果</returns> |
| ... | ... | @@ -99,7 +121,6 @@ namespace NCC.Extend |
| 99 | 121 | { |
| 100 | 122 | try |
| 101 | 123 | { |
| 102 | - // 检查库存记录是否存在 | |
| 103 | 124 | var existingInventory = await _db.Queryable<LqInventoryEntity>() |
| 104 | 125 | .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) |
| 105 | 126 | .FirstAsync(); |
| ... | ... | @@ -109,23 +130,23 @@ namespace NCC.Extend |
| 109 | 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 | 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 | 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 | 150 | existingInventory.UpdateUser = _userManager.UserId; |
| 130 | 151 | existingInventory.UpdateTime = DateTime.Now; |
| 131 | 152 | |
| ... | ... | @@ -144,6 +165,9 @@ namespace NCC.Extend |
| 144 | 165 | /// <summary> |
| 145 | 166 | /// 获取库存列表 |
| 146 | 167 | /// </summary> |
| 168 | + /// <remarks> | |
| 169 | + /// 返回库存列表,包含产品信息、已使用数量、可用数量等 | |
| 170 | + /// </remarks> | |
| 147 | 171 | /// <param name="input">查询输入</param> |
| 148 | 172 | /// <returns>库存列表</returns> |
| 149 | 173 | [HttpGet("GetList")] |
| ... | ... | @@ -151,60 +175,98 @@ namespace NCC.Extend |
| 151 | 175 | { |
| 152 | 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 | 204 | createUserName = "", |
| 179 | - createTime = x.CreateTime, | |
| 180 | - updateUser = x.UpdateUser, | |
| 205 | + createTime = inv.CreateTime, | |
| 206 | + updateUser = inv.UpdateUser, | |
| 181 | 207 | updateUserName = "", |
| 182 | - updateTime = x.UpdateTime, | |
| 183 | - isEffective = x.IsEffective | |
| 208 | + updateTime = inv.UpdateTime, | |
| 209 | + isEffective = inv.IsEffective | |
| 184 | 210 | }) |
| 185 | 211 | .MergeTable() |
| 186 | - .OrderBy(sidx + " " + input.sort) | |
| 212 | + .OrderBy(sidx + " " + sort) | |
| 187 | 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 | 270 | return PageResult<LqInventoryListOutput>.SqlSugarPageResult(data); |
| 209 | 271 | } |
| 210 | 272 | catch (Exception ex) |
| ... | ... | @@ -230,52 +292,75 @@ namespace NCC.Extend |
| 230 | 292 | { |
| 231 | 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 | 322 | if (inventory == null) |
| 237 | 323 | { |
| 238 | 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 | 365 | catch (Exception ex) |
| 281 | 366 | { |
| ... | ... | @@ -285,6 +370,77 @@ namespace NCC.Extend |
| 285 | 370 | } |
| 286 | 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 | 11 | using NCC.Extend.Entitys.Dto.LqInventoryUsage; |
| 12 | 12 | using NCC.Extend.Entitys.Enum; |
| 13 | 13 | using NCC.Extend.Entitys.lq_inventory; |
| 14 | +using NCC.Extend.Entitys.lq_product; | |
| 14 | 15 | using NCC.Extend.Entitys.lq_inventory_usage; |
| 15 | 16 | using NCC.Extend.Entitys.lq_mdxx; |
| 16 | 17 | using NCC.Extend.Interfaces.LqInventoryUsage; |
| ... | ... | @@ -57,17 +58,32 @@ namespace NCC.Extend |
| 57 | 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 | 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 | 89 | _db.Ado.BeginTran(); |
| ... | ... | @@ -89,12 +105,7 @@ namespace NCC.Extend |
| 89 | 105 | var isOk = await _db.Insertable(usageEntity).ExecuteCommandAsync(); |
| 90 | 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 | 109 | _db.Ado.CommitTran(); |
| 99 | 110 | } |
| 100 | 111 | catch (Exception ex) |
| ... | ... | @@ -140,17 +151,8 @@ namespace NCC.Extend |
| 140 | 151 | |
| 141 | 152 | var isOk = await _db.Updateable(usageRecord).ExecuteCommandAsync(); |
| 142 | 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 | 156 | _db.Ado.CommitTran(); |
| 155 | 157 | } |
| 156 | 158 | catch (Exception ex) |
| ... | ... | @@ -175,82 +177,77 @@ namespace NCC.Extend |
| 175 | 177 | { |
| 176 | 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 | 202 | createUserName = "", |
| 200 | - createTime = x.CreateTime, | |
| 201 | - updateUser = x.UpdateUser, | |
| 203 | + createTime = usage.CreateTime, | |
| 204 | + updateUser = usage.UpdateUser, | |
| 202 | 205 | updateUserName = "", |
| 203 | - updateTime = x.UpdateTime, | |
| 204 | - isEffective = x.IsEffective | |
| 206 | + updateTime = usage.UpdateTime, | |
| 207 | + isEffective = usage.IsEffective | |
| 205 | 208 | }) |
| 206 | 209 | .MergeTable() |
| 207 | 210 | .OrderBy(sidx + " " + input.sort) |
| 208 | 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 | 237 | if (!string.IsNullOrWhiteSpace(input.ProductName) || !string.IsNullOrWhiteSpace(input.ProductCategory)) |
| 242 | 238 | { |
| 243 | 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 | 242 | ).ToList(); |
| 247 | 243 | } |
| 248 | 244 | |
| 249 | - // 应用门店名称的过滤条件 | |
| 245 | + // 应用门店名称的过滤条件(在内存中过滤,因为已经在Select中获取了) | |
| 250 | 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 | 251 | return PageResult<LqInventoryUsageListOutput>.SqlSugarPageResult(data); |
| 255 | 252 | } |
| 256 | 253 | catch (Exception ex) |
| ... | ... | @@ -273,7 +270,7 @@ namespace NCC.Extend |
| 273 | 270 | try |
| 274 | 271 | { |
| 275 | 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 | 274 | .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) |
| 278 | 275 | .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) |
| 279 | 276 | .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) |
| ... | ... | @@ -321,18 +318,18 @@ namespace NCC.Extend |
| 321 | 318 | try |
| 322 | 319 | { |
| 323 | 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 | 323 | .Where((usage, product, store) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) |
| 327 | 324 | .Where((usage, product, store) => usage.IsEffective == StatusEnum.有效.GetHashCode()) |
| 328 | 325 | .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product, store) => usage.ProductId == input.ProductId) |
| 329 | 326 | .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product, store) => usage.StoreId == input.StoreId) |
| 330 | 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 | 329 | .Select((usage, product, store) => new StoreUsageStatisticsOutput |
| 333 | 330 | { |
| 334 | 331 | StoreId = usage.StoreId, |
| 335 | - StoreName = store.RealName, | |
| 332 | + StoreName = store.Dm, | |
| 336 | 333 | TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity), |
| 337 | 334 | TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price), |
| 338 | 335 | UsageCount = SqlFunc.AggregateCount(usage.Id), |
| ... | ... | @@ -369,7 +366,7 @@ namespace NCC.Extend |
| 369 | 366 | { |
| 370 | 367 | // 先获取基础数据 |
| 371 | 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 | 370 | .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) |
| 374 | 371 | .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) |
| 375 | 372 | .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId) |
| ... | ... | @@ -450,7 +447,7 @@ namespace NCC.Extend |
| 450 | 447 | try |
| 451 | 448 | { |
| 452 | 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 | 451 | .Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime) |
| 455 | 452 | .Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode()) |
| 456 | 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 | 3678 | } |
| 3679 | 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 | 3512 | } |
| 3513 | 3513 | |
| 3514 | 3514 | /// <summary> |
| 3515 | - /// 统计人头(月度去重客户数) | |
| 3515 | + /// 统计人头(从人次记录表统计,按健康师+月份+客户+数量去重后累加) | |
| 3516 | 3516 | /// </summary> |
| 3517 | 3517 | private async Task<int> GetHeadCount(string userId, string month) |
| 3518 | 3518 | { |
| 3519 | + // 按健康师+月份+客户+数量去重,然后累加数量 | |
| 3520 | + // 注意:userId 和 month 都是内部参数,相对安全 | |
| 3519 | 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 | 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 | 3542 | /// <summary> |
| 3533 | - /// 统计人次(日度去重客户数) | |
| 3543 | + /// 统计人次(从人次记录表统计,按健康师+日期+客户+数量去重后累加) | |
| 3534 | 3544 | /// </summary> |
| 3535 | 3545 | private async Task<int> GetPersonCount(string userId, string month) |
| 3536 | 3546 | { |
| 3547 | + // 按健康师+日期+客户+数量去重,然后累加数量 | |
| 3548 | + // 注意:userId 和 month 都是内部参数,相对安全 | |
| 3537 | 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 | 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 | 3570 | /// <summary> | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
| ... | ... | @@ -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 | 1013 | LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity |
| 1012 | 1014 | { |
| ... | ... | @@ -1015,7 +1017,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1015 | 1017 | BusinessType = "耗卡", |
| 1016 | 1018 | PersonType = "健康师", |
| 1017 | 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 | 1021 | MemberId = entity.Hy, |
| 1020 | 1022 | MemberName = memberInfo.Khmc, |
| 1021 | 1023 | WorkDate = input.hksj, |
| ... | ... | @@ -1026,10 +1028,12 @@ namespace NCC.Extend.LqXhHyhk |
| 1026 | 1028 | }; |
| 1027 | 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 | 1038 | LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity |
| 1035 | 1039 | { |
| ... | ... | @@ -1038,7 +1042,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1038 | 1042 | BusinessType = "耗卡", |
| 1039 | 1043 | PersonType = "科技部老师", |
| 1040 | 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 | 1046 | MemberId = entity.Hy, |
| 1043 | 1047 | MemberName = memberInfo.Khmc, |
| 1044 | 1048 | WorkDate = input.hksj, |
| ... | ... | @@ -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 | 1374 | LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity |
| 1369 | 1375 | { |
| ... | ... | @@ -1372,7 +1378,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1372 | 1378 | BusinessType = "耗卡", |
| 1373 | 1379 | PersonType = "健康师", |
| 1374 | 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 | 1382 | MemberId = entity.Hy, |
| 1377 | 1383 | MemberName = memberInfo.Khmc, |
| 1378 | 1384 | WorkDate = input.hksj, |
| ... | ... | @@ -1383,10 +1389,12 @@ namespace NCC.Extend.LqXhHyhk |
| 1383 | 1389 | }; |
| 1384 | 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 | 1399 | LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity |
| 1392 | 1400 | { |
| ... | ... | @@ -1395,7 +1403,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1395 | 1403 | BusinessType = "耗卡", |
| 1396 | 1404 | PersonType = "科技部老师", |
| 1397 | 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 | 1407 | MemberId = entity.Hy, |
| 1400 | 1408 | MemberName = memberInfo.Khmc, |
| 1401 | 1409 | WorkDate = input.hksj, |
| ... | ... | @@ -1677,5 +1685,214 @@ namespace NCC.Extend.LqXhHyhk |
| 1677 | 1685 | } |
| 1678 | 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语句前,请确认表结构 | ... | ... |