diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryCrInput.cs
index 351665e..22a5859 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryCrInput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryCrInput.cs
@@ -9,50 +9,45 @@ namespace NCC.Extend.Entitys.Dto.LqInventory
public class LqInventoryCrInput
{
///
- /// 产品名称
+ /// 产品ID
///
- [Required(ErrorMessage = "产品名称不能为空")]
- [StringLength(100, ErrorMessage = "产品名称长度不能超过100个字符")]
- [Display(Name = "产品名称")]
- public string ProductName { get; set; }
+ [Required(ErrorMessage = "产品ID不能为空")]
+ [StringLength(50, ErrorMessage = "产品ID长度不能超过50个字符")]
+ [Display(Name = "产品ID")]
+ public string ProductId { get; set; }
///
- /// 价格
+ /// 库存数量
///
- [Required(ErrorMessage = "价格不能为空")]
- [Range(0, double.MaxValue, ErrorMessage = "价格不能小于0")]
- [Display(Name = "价格")]
- public decimal Price { get; set; }
+ [Required(ErrorMessage = "库存数量不能为空")]
+ [Range(1, int.MaxValue, ErrorMessage = "库存数量必须大于0")]
+ [Display(Name = "库存数量")]
+ public int Quantity { get; set; }
///
- /// 数量
+ /// 入库时间
///
- [Range(0, int.MaxValue, ErrorMessage = "数量不能小于0")]
- [Display(Name = "数量")]
- public int Quantity { get; set; } = 0;
+ [Display(Name = "入库时间")]
+ public DateTime? StockInTime { get; set; }
///
- /// 产品类别
+ /// 生产日期
///
- [Required(ErrorMessage = "产品类别不能为空")]
- [StringLength(50, ErrorMessage = "产品类别长度不能超过50个字符")]
- [Display(Name = "产品类别")]
- public string ProductCategory { get; set; }
+ [Display(Name = "生产日期")]
+ public DateTime? ProductionDate { get; set; }
///
- /// 归属部门ID
+ /// 保质期(天数)
///
- [Required(ErrorMessage = "归属部门ID不能为空")]
- [StringLength(50, ErrorMessage = "归属部门ID长度不能超过50个字符")]
- [Display(Name = "归属部门ID")]
- public string DepartmentId { get; set; }
+ [Range(0, int.MaxValue, ErrorMessage = "保质期不能小于0")]
+ [Display(Name = "保质期")]
+ public int? ShelfLife { get; set; }
///
- /// 标准单位
+ /// 批次号
///
- [Required(ErrorMessage = "标准单位不能为空")]
- [StringLength(20, ErrorMessage = "标准单位长度不能超过20个字符")]
- [Display(Name = "标准单位")]
- public string StandardUnit { get; set; }
+ [StringLength(100, ErrorMessage = "批次号长度不能超过100个字符")]
+ [Display(Name = "批次号")]
+ public string BatchNumber { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListOutput.cs
index 5edfb11..10da131 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListOutput.cs
@@ -15,52 +15,64 @@ namespace NCC.Extend.Entitys.Dto.LqInventory
public string id { get; set; }
///
+ /// 产品ID
+ ///
+ [Display(Name = "产品ID")]
+ public string productId { get; set; }
+
+ ///
/// 产品名称
///
[Display(Name = "产品名称")]
public string productName { get; set; }
///
- /// 价格
+ /// 产品价格
///
- [Display(Name = "价格")]
- public decimal price { get; set; }
+ [Display(Name = "产品价格")]
+ public decimal productPrice { get; set; }
///
- /// 数量
+ /// 库存数量
///
- [Display(Name = "数量")]
+ [Display(Name = "库存数量")]
public int quantity { get; set; }
///
- /// 产品类别
+ /// 已使用数量
+ ///
+ [Display(Name = "已使用数量")]
+ public int usedQuantity { get; set; }
+
+ ///
+ /// 可用数量(库存数量 - 已使用数量)
///
- [Display(Name = "产品类别")]
- public string productCategory { get; set; }
+ [Display(Name = "可用数量")]
+ public int availableQuantity { get; set; }
///
- /// 归属部门ID
+ /// 入库时间
///
- [Display(Name = "归属部门ID")]
- public string departmentId { get; set; }
+ [Display(Name = "入库时间")]
+ public DateTime? stockInTime { get; set; }
///
- /// 部门名称
+ /// 生产日期
///
- [Display(Name = "部门名称")]
- public string departmentName { get; set; }
+ [Display(Name = "生产日期")]
+ public DateTime? productionDate { get; set; }
///
- /// 标准单位
+ /// 保质期(天数)
///
- [Display(Name = "标准单位")]
- public string standardUnit { get; set; }
+ [Display(Name = "保质期")]
+ public int? shelfLife { get; set; }
///
- /// 总价值
+ /// 批次号
///
- [Display(Name = "总价值")]
- public decimal totalValue { get; set; }
+ [Display(Name = "批次号")]
+ public string batchNumber { get; set; }
///
/// 创建人ID
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListQueryInput.cs
index 1650106..c2a85aa 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListQueryInput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventory/LqInventoryListQueryInput.cs
@@ -10,34 +10,22 @@ namespace NCC.Extend.Entitys.Dto.LqInventory
public class LqInventoryListQueryInput : PageInputBase
{
///
- /// 产品名称
+ /// 产品ID
///
- [Display(Name = "产品名称", Description = "根据产品名称筛选")]
- public string ProductName { get; set; }
-
- ///
- /// 产品类别
- ///
- [Display(Name = "产品类别", Description = "根据产品类别筛选")]
- public string ProductCategory { get; set; }
+ [Display(Name = "产品ID", Description = "根据产品ID筛选")]
+ public string ProductId { get; set; }
///
- /// 归属部门ID
+ /// 产品名称(模糊查询)
///
- [Display(Name = "归属部门ID", Description = "根据归属部门ID筛选")]
- public string DepartmentId { get; set; }
-
- ///
- /// 价格最小值
- ///
- [Display(Name = "价格最小值", Description = "价格范围的最小值")]
- public decimal? PriceMin { get; set; }
+ [Display(Name = "产品名称", Description = "根据产品名称筛选")]
+ public string ProductName { get; set; }
///
- /// 价格最大值
+ /// 批次号
///
- [Display(Name = "价格最大值", Description = "价格范围的最大值")]
- public decimal? PriceMax { get; set; }
+ [Display(Name = "批次号", Description = "根据批次号筛选")]
+ public string BatchNumber { get; set; }
///
/// 数量最小值
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductCrInput.cs
new file mode 100644
index 0000000..242d116
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductCrInput.cs
@@ -0,0 +1,102 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace NCC.Extend.Entitys.Dto.LqProduct
+{
+ ///
+ /// 产品创建输入
+ ///
+ public class LqProductCrInput
+ {
+ ///
+ /// 产品名称
+ ///
+ [Required(ErrorMessage = "产品名称不能为空")]
+ [StringLength(200, ErrorMessage = "产品名称长度不能超过200个字符")]
+ [Display(Name = "产品名称")]
+ public string ProductName { get; set; }
+
+ ///
+ /// 价格
+ ///
+ [Required(ErrorMessage = "价格不能为空")]
+ [Range(0, double.MaxValue, ErrorMessage = "价格不能小于0")]
+ [Display(Name = "价格")]
+ public decimal Price { get; set; }
+
+ ///
+ /// 产品类别
+ ///
+ [StringLength(50, ErrorMessage = "产品类别长度不能超过50个字符")]
+ [Display(Name = "产品类别")]
+ public string ProductCategory { get; set; }
+
+ ///
+ /// 归属部门ID
+ ///
+ [StringLength(50, ErrorMessage = "归属部门ID长度不能超过50个字符")]
+ [Display(Name = "归属部门ID")]
+ public string DepartmentId { get; set; }
+
+ ///
+ /// 标准单位
+ ///
+ [StringLength(20, ErrorMessage = "标准单位长度不能超过20个字符")]
+ [Display(Name = "标准单位")]
+ public string StandardUnit { get; set; }
+
+ ///
+ /// 上架状态(1:上架 0:下架)
+ ///
+ [Display(Name = "上架状态")]
+ public int OnShelfStatus { get; set; } = 1;
+
+ ///
+ /// 统计分类
+ ///
+ [StringLength(50, ErrorMessage = "统计分类长度不能超过50个字符")]
+ [Display(Name = "统计分类")]
+ public string StatisticsCategory { get; set; }
+
+ ///
+ /// 归属仓库
+ ///
+ [StringLength(100, ErrorMessage = "归属仓库长度不能超过100个字符")]
+ [Display(Name = "归属仓库")]
+ public string Warehouse { get; set; }
+
+ ///
+ /// 单位换算
+ ///
+ [StringLength(200, ErrorMessage = "单位换算长度不能超过200个字符")]
+ [Display(Name = "单位换算")]
+ public string UnitConversion { get; set; }
+
+ ///
+ /// 供应商名称
+ ///
+ [StringLength(200, ErrorMessage = "供应商名称长度不能超过200个字符")]
+ [Display(Name = "供应商名称")]
+ public string SupplierName { get; set; }
+
+ ///
+ /// 合同签订日期
+ ///
+ [Display(Name = "合同签订日期")]
+ public DateTime? ContractSignDate { get; set; }
+
+ ///
+ /// 合同结束日期
+ ///
+ [Display(Name = "合同结束日期")]
+ public DateTime? ContractEndDate { get; set; }
+
+ ///
+ /// 备注
+ ///
+ [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
+ [Display(Name = "备注")]
+ public string Remark { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInfoOutput.cs
new file mode 100644
index 0000000..c813659
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInfoOutput.cs
@@ -0,0 +1,116 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqProduct
+{
+ ///
+ /// 产品详情输出
+ ///
+ public class LqProductInfoOutput
+ {
+ ///
+ /// 产品ID
+ ///
+ public string id { get; set; }
+
+ ///
+ /// 产品名称
+ ///
+ public string productName { get; set; }
+
+ ///
+ /// 价格
+ ///
+ public decimal price { get; set; }
+
+ ///
+ /// 产品类别
+ ///
+ public string productCategory { get; set; }
+
+ ///
+ /// 归属部门ID
+ ///
+ public string departmentId { get; set; }
+
+ ///
+ /// 部门名称
+ ///
+ public string departmentName { get; set; }
+
+ ///
+ /// 标准单位
+ ///
+ public string standardUnit { get; set; }
+
+ ///
+ /// 上架状态(1:上架 0:下架)
+ ///
+ public int onShelfStatus { get; set; }
+
+ ///
+ /// 统计分类
+ ///
+ public string statisticsCategory { get; set; }
+
+ ///
+ /// 归属仓库
+ ///
+ public string warehouse { get; set; }
+
+ ///
+ /// 单位换算
+ ///
+ public string unitConversion { get; set; }
+
+ ///
+ /// 供应商名称
+ ///
+ public string supplierName { get; set; }
+
+ ///
+ /// 合同签订日期
+ ///
+ public DateTime? contractSignDate { get; set; }
+
+ ///
+ /// 合同结束日期
+ ///
+ public DateTime? contractEndDate { get; set; }
+
+ ///
+ /// 备注
+ ///
+ public string remark { get; set; }
+
+ ///
+ /// 创建人ID
+ ///
+ public string createUser { get; set; }
+
+ ///
+ /// 创建人姓名
+ ///
+ public string createUserName { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime createTime { get; set; }
+
+ ///
+ /// 更新人ID
+ ///
+ public string updateUser { get; set; }
+
+ ///
+ /// 更新人姓名
+ ///
+ public string updateUserName { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime? updateTime { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInventoryDetailOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInventoryDetailOutput.cs
new file mode 100644
index 0000000..c29f51a
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInventoryDetailOutput.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+
+namespace NCC.Extend.Entitys.Dto.LqProduct
+{
+ ///
+ /// 产品库存详情输出
+ ///
+ public class LqProductInventoryDetailOutput
+ {
+ ///
+ /// 产品信息
+ ///
+ public LqProductInfoOutput productInfo { get; set; }
+
+ ///
+ /// 库存列表
+ ///
+ public List inventoryList { get; set; }
+
+ ///
+ /// 使用记录列表
+ ///
+ public List usageList { get; set; }
+
+ ///
+ /// 总库存数量
+ ///
+ public int totalInventory { get; set; }
+
+ ///
+ /// 已使用数量
+ ///
+ public int totalUsage { get; set; }
+
+ ///
+ /// 可用库存
+ ///
+ public int availableInventory { get; set; }
+ }
+
+ ///
+ /// 产品库存项
+ ///
+ public class ProductInventoryItem
+ {
+ ///
+ /// 库存ID
+ ///
+ public string id { get; set; }
+
+ ///
+ /// 库存数量
+ ///
+ public int quantity { get; set; }
+
+ ///
+ /// 入库时间
+ ///
+ public DateTime? stockInTime { get; set; }
+
+ ///
+ /// 生产日期
+ ///
+ public DateTime? productionDate { get; set; }
+
+ ///
+ /// 保质期(天数)
+ ///
+ public int? shelfLife { get; set; }
+
+ ///
+ /// 批次号
+ ///
+ public string batchNumber { get; set; }
+
+ ///
+ /// 是否有效
+ ///
+ public int isEffective { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime createTime { get; set; }
+ }
+
+ ///
+ /// 产品使用记录项
+ ///
+ public class ProductUsageItem
+ {
+ ///
+ /// 使用记录ID
+ ///
+ public string id { get; set; }
+
+ ///
+ /// 门店ID
+ ///
+ public string storeId { get; set; }
+
+ ///
+ /// 门店名称
+ ///
+ public string storeName { get; set; }
+
+ ///
+ /// 使用时间
+ ///
+ public DateTime usageTime { get; set; }
+
+ ///
+ /// 使用数量
+ ///
+ public int usageQuantity { get; set; }
+
+ ///
+ /// 关联消耗ID
+ ///
+ public string relatedConsumeId { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime createTime { get; set; }
+
+ ///
+ /// 是否有效
+ ///
+ public int isEffective { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductListOutput.cs
new file mode 100644
index 0000000..ed76fd6
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductListOutput.cs
@@ -0,0 +1,121 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqProduct
+{
+ ///
+ /// 产品列表输出
+ ///
+ public class LqProductListOutput
+ {
+ ///
+ /// 产品ID
+ ///
+ public string id { get; set; }
+
+ ///
+ /// 产品名称
+ ///
+ public string productName { get; set; }
+
+ ///
+ /// 价格
+ ///
+ public decimal price { get; set; }
+
+ ///
+ /// 产品类别
+ ///
+ public string productCategory { get; set; }
+
+ ///
+ /// 归属部门ID
+ ///
+ public string departmentId { get; set; }
+
+ ///
+ /// 部门名称
+ ///
+ public string departmentName { get; set; }
+
+ ///
+ /// 标准单位
+ ///
+ public string standardUnit { get; set; }
+
+ ///
+ /// 上架状态(1:上架 0:下架)
+ ///
+ public int onShelfStatus { get; set; }
+
+ ///
+ /// 统计分类
+ ///
+ public string statisticsCategory { get; set; }
+
+ ///
+ /// 归属仓库
+ ///
+ public string warehouse { get; set; }
+
+ ///
+ /// 单位换算
+ ///
+ public string unitConversion { get; set; }
+
+ ///
+ /// 供应商名称
+ ///
+ public string supplierName { get; set; }
+
+ ///
+ /// 合同签订日期
+ ///
+ public DateTime? contractSignDate { get; set; }
+
+ ///
+ /// 合同结束日期
+ ///
+ public DateTime? contractEndDate { get; set; }
+
+ ///
+ /// 备注
+ ///
+ public string remark { get; set; }
+
+ ///
+ /// 现有库存数量(所有有效库存的总和)
+ ///
+ public int currentInventory { get; set; }
+
+ ///
+ /// 创建人ID
+ ///
+ public string createUser { get; set; }
+
+ ///
+ /// 创建人姓名
+ ///
+ public string createUserName { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime createTime { get; set; }
+
+ ///
+ /// 更新人ID
+ ///
+ public string updateUser { get; set; }
+
+ ///
+ /// 更新人姓名
+ ///
+ public string updateUserName { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime? updateTime { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductListQueryInput.cs
new file mode 100644
index 0000000..234c2d4
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductListQueryInput.cs
@@ -0,0 +1,56 @@
+using NCC.Common.Filter;
+
+namespace NCC.Extend.Entitys.Dto.LqProduct
+{
+ ///
+ /// 产品列表查询输入
+ ///
+ public class LqProductListQueryInput : PageInputBase
+ {
+ ///
+ /// 产品名称(模糊查询)
+ ///
+ public string ProductName { get; set; }
+
+ ///
+ /// 产品类别
+ ///
+ public string ProductCategory { get; set; }
+
+ ///
+ /// 归属部门ID
+ ///
+ public string DepartmentId { get; set; }
+
+ ///
+ /// 上架状态(1:上架 0:下架)
+ ///
+ public int? OnShelfStatus { get; set; }
+
+ ///
+ /// 统计分类
+ ///
+ public string StatisticsCategory { get; set; }
+
+ ///
+ /// 归属仓库
+ ///
+ public string Warehouse { get; set; }
+
+ ///
+ /// 供应商名称(模糊查询)
+ ///
+ public string SupplierName { get; set; }
+
+ ///
+ /// 价格最小值
+ ///
+ public decimal? PriceMin { get; set; }
+
+ ///
+ /// 价格最大值
+ ///
+ public decimal? PriceMax { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductSelectOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductSelectOutput.cs
new file mode 100644
index 0000000..cdc8f09
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductSelectOutput.cs
@@ -0,0 +1,34 @@
+namespace NCC.Extend.Entitys.Dto.LqProduct
+{
+ ///
+ /// 产品下拉选择输出
+ ///
+ public class LqProductSelectOutput
+ {
+ ///
+ /// 产品ID
+ ///
+ public string id { get; set; }
+
+ ///
+ /// 产品名称
+ ///
+ public string productName { get; set; }
+
+ ///
+ /// 价格
+ ///
+ public decimal price { get; set; }
+
+ ///
+ /// 标准单位
+ ///
+ public string standardUnit { get; set; }
+
+ ///
+ /// 现有库存数量
+ ///
+ public int currentInventory { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductToggleShelfInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductToggleShelfInput.cs
new file mode 100644
index 0000000..829242f
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductToggleShelfInput.cs
@@ -0,0 +1,26 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace NCC.Extend.Entitys.Dto.LqProduct
+{
+ ///
+ /// 产品上下架输入
+ ///
+ public class LqProductToggleShelfInput
+ {
+ ///
+ /// 产品ID
+ ///
+ [Required(ErrorMessage = "产品ID不能为空")]
+ [Display(Name = "产品ID")]
+ public string ProductId { get; set; }
+
+ ///
+ /// 上架状态(1:上架 0:下架)
+ ///
+ [Required(ErrorMessage = "上架状态不能为空")]
+ [Range(0, 1, ErrorMessage = "上架状态只能是0(下架)或1(上架)")]
+ [Display(Name = "上架状态")]
+ public int OnShelfStatus { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductUpInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductUpInput.cs
new file mode 100644
index 0000000..cb4bdc6
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductUpInput.cs
@@ -0,0 +1,109 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace NCC.Extend.Entitys.Dto.LqProduct
+{
+ ///
+ /// 产品更新输入
+ ///
+ public class LqProductUpInput
+ {
+ ///
+ /// 产品ID
+ ///
+ [Required(ErrorMessage = "产品ID不能为空")]
+ [Display(Name = "产品ID")]
+ public string Id { get; set; }
+
+ ///
+ /// 产品名称
+ ///
+ [Required(ErrorMessage = "产品名称不能为空")]
+ [StringLength(200, ErrorMessage = "产品名称长度不能超过200个字符")]
+ [Display(Name = "产品名称")]
+ public string ProductName { get; set; }
+
+ ///
+ /// 价格
+ ///
+ [Required(ErrorMessage = "价格不能为空")]
+ [Range(0, double.MaxValue, ErrorMessage = "价格不能小于0")]
+ [Display(Name = "价格")]
+ public decimal Price { get; set; }
+
+ ///
+ /// 产品类别
+ ///
+ [StringLength(50, ErrorMessage = "产品类别长度不能超过50个字符")]
+ [Display(Name = "产品类别")]
+ public string ProductCategory { get; set; }
+
+ ///
+ /// 归属部门ID
+ ///
+ [StringLength(50, ErrorMessage = "归属部门ID长度不能超过50个字符")]
+ [Display(Name = "归属部门ID")]
+ public string DepartmentId { get; set; }
+
+ ///
+ /// 标准单位
+ ///
+ [StringLength(20, ErrorMessage = "标准单位长度不能超过20个字符")]
+ [Display(Name = "标准单位")]
+ public string StandardUnit { get; set; }
+
+ ///
+ /// 上架状态(1:上架 0:下架)
+ ///
+ [Display(Name = "上架状态")]
+ public int OnShelfStatus { get; set; } = 1;
+
+ ///
+ /// 统计分类
+ ///
+ [StringLength(50, ErrorMessage = "统计分类长度不能超过50个字符")]
+ [Display(Name = "统计分类")]
+ public string StatisticsCategory { get; set; }
+
+ ///
+ /// 归属仓库
+ ///
+ [StringLength(100, ErrorMessage = "归属仓库长度不能超过100个字符")]
+ [Display(Name = "归属仓库")]
+ public string Warehouse { get; set; }
+
+ ///
+ /// 单位换算
+ ///
+ [StringLength(200, ErrorMessage = "单位换算长度不能超过200个字符")]
+ [Display(Name = "单位换算")]
+ public string UnitConversion { get; set; }
+
+ ///
+ /// 供应商名称
+ ///
+ [StringLength(200, ErrorMessage = "供应商名称长度不能超过200个字符")]
+ [Display(Name = "供应商名称")]
+ public string SupplierName { get; set; }
+
+ ///
+ /// 合同签订日期
+ ///
+ [Display(Name = "合同签订日期")]
+ public DateTime? ContractSignDate { get; set; }
+
+ ///
+ /// 合同结束日期
+ ///
+ [Display(Name = "合同结束日期")]
+ public DateTime? ContractEndDate { get; set; }
+
+ ///
+ /// 备注
+ ///
+ [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
+ [Display(Name = "备注")]
+ public string Remark { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory/LqInventoryEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory/LqInventoryEntity.cs
index 075781b..d9387ae 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory/LqInventoryEntity.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory/LqInventoryEntity.cs
@@ -18,40 +18,46 @@ namespace NCC.Extend.Entitys.lq_inventory
public string Id { get; set; }
///
- /// 产品名称
+ /// 产品ID(关联产品表)
///
- [SugarColumn(ColumnName = "F_ProductName")]
- public string ProductName { get; set; }
+ [SugarColumn(ColumnName = "F_ProductId")]
+ public string ProductId { get; set; }
///
- /// 价格
+ /// 库存数量
///
- [SugarColumn(ColumnName = "F_Price")]
- public decimal Price { get; set; }
+ [SugarColumn(ColumnName = "F_Quantity")]
+ public int Quantity { get; set; } = 0;
///
- /// 数量
+ /// 入库时间
///
- [SugarColumn(ColumnName = "F_Quantity")]
- public int Quantity { get; set; } = 0;
+ [SugarColumn(ColumnName = "F_StockInTime")]
+ public DateTime? StockInTime { get; set; }
///
- /// 产品类别
+ /// 生产日期
///
- [SugarColumn(ColumnName = "F_ProductCategory")]
- public string ProductCategory { get; set; }
+ [SugarColumn(ColumnName = "F_ProductionDate")]
+ public DateTime? ProductionDate { get; set; }
///
- /// 归属部门ID
+ /// 保质期(天数)
///
- [SugarColumn(ColumnName = "F_DepartmentId")]
- public string DepartmentId { get; set; }
+ [SugarColumn(ColumnName = "F_ShelfLife")]
+ public int? ShelfLife { get; set; }
///
- /// 标准单位
+ /// 批次号
+ ///
+ [SugarColumn(ColumnName = "F_BatchNumber")]
+ public string BatchNumber { get; set; }
+
+ ///
+ /// 是否有效(1:有效 0:无效)
///
- [SugarColumn(ColumnName = "F_StandardUnit")]
- public string StandardUnit { get; set; }
+ [SugarColumn(ColumnName = "F_IsEffective")]
+ public int IsEffective { get; set; } = 1;
///
/// 创建人ID
@@ -76,11 +82,5 @@ namespace NCC.Extend.Entitys.lq_inventory
///
[SugarColumn(ColumnName = "F_UpdateTime")]
public DateTime? UpdateTime { get; set; }
-
- ///
- /// 是否有效(1:有效 0:无效)
- ///
- [SugarColumn(ColumnName = "F_IsEffective")]
- public int IsEffective { get; set; } = 1;
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_product/LqProductEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_product/LqProductEntity.cs
new file mode 100644
index 0000000..32e7e10
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_product/LqProductEntity.cs
@@ -0,0 +1,123 @@
+using System;
+using NCC.Common.Const;
+using SqlSugar;
+
+namespace NCC.Extend.Entitys.lq_product
+{
+ ///
+ /// 产品表
+ ///
+ [SugarTable("lq_product")]
+ [Tenant(ClaimConst.TENANT_ID)]
+ public class LqProductEntity
+ {
+ ///
+ /// 产品ID
+ ///
+ [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
+ public string Id { get; set; }
+
+ ///
+ /// 产品名称
+ ///
+ [SugarColumn(ColumnName = "F_ProductName")]
+ public string ProductName { get; set; }
+
+ ///
+ /// 价格
+ ///
+ [SugarColumn(ColumnName = "F_Price")]
+ public decimal Price { get; set; }
+
+ ///
+ /// 产品类别
+ ///
+ [SugarColumn(ColumnName = "F_ProductCategory")]
+ public string ProductCategory { get; set; }
+
+ ///
+ /// 归属部门ID
+ ///
+ [SugarColumn(ColumnName = "F_DepartmentId")]
+ public string DepartmentId { get; set; }
+
+ ///
+ /// 标准单位
+ ///
+ [SugarColumn(ColumnName = "F_StandardUnit")]
+ public string StandardUnit { get; set; }
+
+ ///
+ /// 上架状态(1:上架 0:下架)
+ ///
+ [SugarColumn(ColumnName = "F_OnShelfStatus")]
+ public int OnShelfStatus { get; set; } = 1;
+
+ ///
+ /// 统计分类
+ ///
+ [SugarColumn(ColumnName = "F_StatisticsCategory")]
+ public string StatisticsCategory { get; set; }
+
+ ///
+ /// 归属仓库
+ ///
+ [SugarColumn(ColumnName = "F_Warehouse")]
+ public string Warehouse { get; set; }
+
+ ///
+ /// 单位换算
+ ///
+ [SugarColumn(ColumnName = "F_UnitConversion")]
+ public string UnitConversion { get; set; }
+
+ ///
+ /// 供应商名称
+ ///
+ [SugarColumn(ColumnName = "F_SupplierName")]
+ public string SupplierName { get; set; }
+
+ ///
+ /// 合同签订日期
+ ///
+ [SugarColumn(ColumnName = "F_ContractSignDate")]
+ public DateTime? ContractSignDate { get; set; }
+
+ ///
+ /// 合同结束日期
+ ///
+ [SugarColumn(ColumnName = "F_ContractEndDate")]
+ public DateTime? ContractEndDate { get; set; }
+
+ ///
+ /// 备注
+ ///
+ [SugarColumn(ColumnName = "F_Remark")]
+ public string Remark { get; set; }
+
+ ///
+ /// 创建人ID
+ ///
+ [SugarColumn(ColumnName = "F_CreateUser")]
+ public string CreateUser { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ [SugarColumn(ColumnName = "F_CreateTime")]
+ public DateTime CreateTime { get; set; }
+
+ ///
+ /// 更新人ID
+ ///
+ [SugarColumn(ColumnName = "F_UpdateUser")]
+ public string UpdateUser { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ [SugarColumn(ColumnName = "F_UpdateTime")]
+ public DateTime? UpdateTime { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqInventoryUsage/ILqInventoryUsageService.cs b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqInventoryUsage/ILqInventoryUsageService.cs
index 45a3cf4..3fcbeb2 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqInventoryUsage/ILqInventoryUsageService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqInventoryUsage/ILqInventoryUsageService.cs
@@ -9,54 +9,6 @@ namespace NCC.Extend.Interfaces.LqInventoryUsage
///
public interface ILqInventoryUsageService
{
- ///
- /// 添加库存使用记录
- ///
- /// 创建输入
- ///
- Task CreateAsync(LqInventoryUsageCrInput input);
- ///
- /// 作废库存使用记录
- ///
- /// 使用记录ID
- /// 作废备注
- ///
- Task CancelAsync(string id, string remarks = null);
-
- ///
- /// 获取使用记录列表
- ///
- /// 查询输入
- ///
- Task GetListAsync(LqInventoryUsageListQueryInput input);
-
- ///
- /// 统计时间周期内每个产品的使用数量
- ///
- /// 统计查询输入
- ///
- Task GetProductUsageStatisticsAsync(LqInventoryUsageStatisticsInput input);
-
- ///
- /// 统计时间周期内每个门店的使用情况
- ///
- /// 统计查询输入
- ///
- Task GetStoreUsageStatisticsAsync(LqInventoryUsageStatisticsInput input);
-
- ///
- /// 统计时间周期内使用趋势
- ///
- /// 统计查询输入
- ///
- Task GetUsageTrendStatisticsAsync(LqInventoryUsageStatisticsInput input);
-
- ///
- /// 统计产品使用排行榜
- ///
- /// 统计查询输入
- ///
- Task GetProductUsageRankingAsync(LqInventoryUsageStatisticsInput input);
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqProduct/ILqProductService.cs b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqProduct/ILqProductService.cs
new file mode 100644
index 0000000..bfd4239
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqProduct/ILqProductService.cs
@@ -0,0 +1,64 @@
+using System.Threading.Tasks;
+using NCC.Extend.Entitys.Dto.LqProduct;
+using NCC.Common.Filter;
+
+namespace NCC.Extend.Interfaces.LqProduct
+{
+ ///
+ /// 产品服务接口
+ ///
+ public interface ILqProductService
+ {
+ ///
+ /// 添加产品信息
+ ///
+ /// 创建输入
+ ///
+ Task CreateAsync(LqProductCrInput input);
+
+ ///
+ /// 更新产品信息
+ ///
+ /// 更新输入
+ ///
+ Task UpdateAsync(LqProductUpInput input);
+
+ ///
+ /// 获取产品列表(包含现有库存)
+ ///
+ /// 查询输入
+ ///
+ Task GetListAsync(LqProductListQueryInput input);
+
+ ///
+ /// 获取产品详情
+ ///
+ /// 产品ID
+ ///
+ Task GetInfoAsync(string id);
+
+ ///
+ /// 获取产品下拉列表
+ ///
+ /// 产品名称(可选,模糊查询)
+ /// 上架状态(可选,1:上架 0:下架)
+ ///
+ Task GetSelectListAsync(string productName = null, int? onShelfStatus = 1);
+
+ ///
+ /// 作废产品
+ ///
+ /// 产品ID
+ /// 作废备注
+ ///
+ Task CancelAsync(string id, string remarks = null);
+
+ ///
+ /// 产品上下架
+ ///
+ /// 上下架输入
+ ///
+ Task ToggleShelfAsync(LqProductToggleShelfInput input);
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
index 19701a8..557b462 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
@@ -129,7 +129,7 @@ namespace NCC.Extend
}
// SQL查询:获取门店每日运营数据(只统计zxzt为"开店"的门店)
- // 优化:使用 LEFT JOIN 一次性统计所有项目数,避免多个子查询
+ // 注意:避免笛卡尔积问题,消耗业绩和项目数分别统计,避免重复计算
var sql = $@"
SELECT
consume.Md as StoreId,
@@ -138,23 +138,40 @@ namespace NCC.Extend
COALESCE(COUNT(DISTINCT consume.Hy), 0) as HeadCount,
-- 人次(日度去重客户数)
COALESCE(COUNT(DISTINCT CONCAT(consume.Hy, '-', DATE_FORMAT(consume.Hksj, '%Y-%m-%d'))), 0) as PersonCount,
- -- 项目数(消耗的项目总数,从品项明细表统计)
- COALESCE(SUM(project.F_ProjectNumber), 0) as ProjectCount,
- -- 消耗总项目数(健康师业绩表统计,包含原始+加班+陪同)
- COALESCE(SUM(COALESCE(jksyj.F_kdpxNumber, 0)), 0) as TotalProjectCount,
- -- 消耗原始项目数(健康师业绩表统计)
- COALESCE(SUM(COALESCE(jksyj.F_OriginalKdpxNumber, jksyj.F_kdpxNumber, 0)), 0) as OriginalProjectCount,
- -- 消耗加班项目数(健康师业绩表统计)
- COALESCE(SUM(COALESCE(jksyj.F_OvertimeKdpxNumber, 0)), 0) as OvertimeProjectCount,
- -- 消耗陪同项目数(健康师业绩表统计)
- COALESCE(SUM(COALESCE(jksyj.F_AccompaniedProjectNumber, 0)), 0) as AccompaniedProjectCount,
- -- 消耗业绩(总金额)
- COALESCE(SUM(project.F_TotalPrice), 0) as ConsumePerformance
+ -- 项目数(消耗的项目总数,从品项明细表统计原始项目数,与健康师业绩表的原始项目数保持一致)
+ COALESCE(SUM((
+ SELECT COALESCE(SUM(COALESCE(px.F_OriginalProjectNumber, px.F_ProjectNumber, 0)), 0)
+ FROM lq_xh_pxmx px
+ WHERE px.F_ConsumeInfoId = consume.F_Id AND px.F_IsEffective = 1
+ )), 0) as ProjectCount,
+ -- 消耗总项目数(健康师业绩表统计,包含原始+加班+陪同,使用SUM聚合子查询结果)
+ COALESCE(SUM((
+ SELECT COALESCE(SUM(COALESCE(j.F_kdpxNumber, 0)), 0)
+ FROM lq_xh_jksyj j
+ WHERE j.glkdbh = consume.F_Id AND j.F_IsEffective = 1
+ )), 0) as TotalProjectCount,
+ -- 消耗原始项目数(健康师业绩表统计,使用SUM聚合子查询结果)
+ COALESCE(SUM((
+ SELECT COALESCE(SUM(COALESCE(j.F_OriginalKdpxNumber, j.F_kdpxNumber, 0)), 0)
+ FROM lq_xh_jksyj j
+ WHERE j.glkdbh = consume.F_Id AND j.F_IsEffective = 1
+ )), 0) as OriginalProjectCount,
+ -- 消耗加班项目数(健康师业绩表统计,使用SUM聚合子查询结果)
+ COALESCE(SUM((
+ SELECT COALESCE(SUM(COALESCE(j.F_OvertimeKdpxNumber, 0)), 0)
+ FROM lq_xh_jksyj j
+ WHERE j.glkdbh = consume.F_Id AND j.F_IsEffective = 1
+ )), 0) as OvertimeProjectCount,
+ -- 消耗陪同项目数(健康师业绩表统计,使用SUM聚合子查询结果)
+ COALESCE(SUM((
+ SELECT COALESCE(SUM(COALESCE(j.F_AccompaniedProjectNumber, 0)), 0)
+ FROM lq_xh_jksyj j
+ WHERE j.glkdbh = consume.F_Id AND j.F_IsEffective = 1
+ )), 0) as AccompaniedProjectCount,
+ -- 消耗业绩(总金额,直接从主表xfje字段统计,避免子查询和JOIN导致的性能问题)
+ COALESCE(SUM(consume.xfje), 0) as ConsumePerformance
FROM lq_xh_hyhk consume
INNER JOIN lq_mdxx store ON consume.Md = store.F_Id AND store.zxzt = '开店'
- LEFT JOIN lq_xh_pxmx project ON consume.F_Id = project.F_ConsumeInfoId AND project.F_IsEffective = 1
- LEFT JOIN lq_xh_jksyj jksyj ON jksyj.glkdbh = consume.F_Id
- AND jksyj.F_IsEffective = 1
WHERE consume.F_IsEffective = 1
AND consume.Hksj >= '{startDate:yyyy-MM-dd} 00:00:00'
AND consume.Hksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
index 1aa2892..1079d21 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
@@ -10,6 +11,8 @@ using NCC.DynamicApiController;
using NCC.Extend.Entitys.Dto.LqInventory;
using NCC.Extend.Entitys.Enum;
using NCC.Extend.Entitys.lq_inventory;
+using NCC.Extend.Entitys.lq_inventory_usage;
+using NCC.Extend.Entitys.lq_product;
using NCC.Extend.Interfaces.LqInventory;
using NCC.FriendlyException;
using NCC.System.Entitys.Permission;
@@ -42,39 +45,58 @@ namespace NCC.Extend
_db = db;
}
- #region 创建库存
+ #region 添加库存信息
///
- /// 创建库存
+ /// 添加库存信息
///
+ ///
+ /// 针对某个产品添加库存记录
+ ///
+ /// 示例请求:
+ /// ```json
+ /// {
+ /// "productId": "产品ID",
+ /// "quantity": 100,
+ /// "stockInTime": "2024-01-01",
+ /// "productionDate": "2023-12-01",
+ /// "shelfLife": 365,
+ /// "batchNumber": "批次号001"
+ /// }
+ /// ```
+ ///
/// 创建输入
/// 创建结果
+ /// 创建成功
+ /// 产品不存在或参数错误
+ /// 服务器错误
[HttpPost("Create")]
public async Task CreateAsync([FromBody] LqInventoryCrInput input)
{
try
{
- // 检查产品名称是否已存在
- var existingProduct = await _db.Queryable()
- .Where(x => x.ProductName == input.ProductName && x.IsEffective == StatusEnum.有效.GetHashCode())
+ // 验证产品是否存在
+ var product = await _db.Queryable()
+ .Where(x => x.Id == input.ProductId)
.FirstAsync();
- if (existingProduct != null)
+
+ if (product == null)
{
- throw NCCException.Oh($"产品名称'{input.ProductName}'已存在");
+ throw NCCException.Oh("产品不存在");
}
// 创建库存记录
var inventoryEntity = new LqInventoryEntity
{
Id = YitIdHelper.NextId().ToString(),
- ProductName = input.ProductName,
- Price = input.Price,
+ ProductId = input.ProductId,
Quantity = input.Quantity,
- ProductCategory = input.ProductCategory,
- DepartmentId = input.DepartmentId,
- StandardUnit = input.StandardUnit,
+ StockInTime = input.StockInTime ?? DateTime.Now,
+ ProductionDate = input.ProductionDate,
+ ShelfLife = input.ShelfLife,
+ BatchNumber = input.BatchNumber,
+ IsEffective = StatusEnum.有效.GetHashCode(),
CreateUser = _userManager.UserId,
- CreateTime = DateTime.Now,
- IsEffective = StatusEnum.有效.GetHashCode()
+ CreateTime = DateTime.Now
};
var isOk = await _db.Insertable(inventoryEntity).ExecuteCommandAsync();
@@ -88,9 +110,9 @@ namespace NCC.Extend
}
#endregion
- #region 更新库存
+ #region 更新库存信息
///
- /// 更新库存
+ /// 更新库存信息
///
/// 更新输入
/// 更新结果
@@ -99,7 +121,6 @@ namespace NCC.Extend
{
try
{
- // 检查库存记录是否存在
var existingInventory = await _db.Queryable()
.Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
.FirstAsync();
@@ -109,23 +130,23 @@ namespace NCC.Extend
throw NCCException.Oh("库存记录不存在或已失效");
}
- // 检查产品名称是否与其他记录重复
- var duplicateProduct = await _db.Queryable()
- .Where(x => x.ProductName == input.ProductName && x.Id != input.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ // 验证产品是否存在
+ var product = await _db.Queryable()
+ .Where(x => x.Id == input.ProductId)
.FirstAsync();
- if (duplicateProduct != null)
+ if (product == null)
{
- throw NCCException.Oh($"产品名称'{input.ProductName}'已存在");
+ throw NCCException.Oh("产品不存在");
}
// 更新库存记录
- existingInventory.ProductName = input.ProductName;
- existingInventory.Price = input.Price;
+ existingInventory.ProductId = input.ProductId;
existingInventory.Quantity = input.Quantity;
- existingInventory.ProductCategory = input.ProductCategory;
- existingInventory.DepartmentId = input.DepartmentId;
- existingInventory.StandardUnit = input.StandardUnit;
+ existingInventory.StockInTime = input.StockInTime;
+ existingInventory.ProductionDate = input.ProductionDate;
+ existingInventory.ShelfLife = input.ShelfLife;
+ existingInventory.BatchNumber = input.BatchNumber;
existingInventory.UpdateUser = _userManager.UserId;
existingInventory.UpdateTime = DateTime.Now;
@@ -144,6 +165,9 @@ namespace NCC.Extend
///
/// 获取库存列表
///
+ ///
+ /// 返回库存列表,包含产品信息、已使用数量、可用数量等
+ ///
/// 查询输入
/// 库存列表
[HttpGet("GetList")]
@@ -151,60 +175,98 @@ namespace NCC.Extend
{
try
{
- var sidx = input.sidx == null ? "id" : input.sidx;
-
- // 查询库存信息
- var data = await _db.Queryable()
- .WhereIF(!string.IsNullOrWhiteSpace(input.ProductName), x => x.ProductName.Contains(input.ProductName))
- .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), x => x.ProductCategory.Contains(input.ProductCategory))
- .WhereIF(!string.IsNullOrWhiteSpace(input.DepartmentId), x => x.DepartmentId == input.DepartmentId)
- .WhereIF(input.PriceMin.HasValue, x => x.Price >= input.PriceMin.Value)
- .WhereIF(input.PriceMax.HasValue, x => x.Price <= input.PriceMax.Value)
- .WhereIF(input.QuantityMin.HasValue, x => x.Quantity >= input.QuantityMin.Value)
- .WhereIF(input.QuantityMax.HasValue, x => x.Quantity <= input.QuantityMax.Value)
- .WhereIF(input.IsEffective.HasValue, x => x.IsEffective == input.IsEffective.Value)
- .Select(x => new LqInventoryListOutput
+ var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx;
+ var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
+
+ // 查询库存信息,关联产品表
+ var data = await _db.Queryable(
+ (inv, prod) => inv.ProductId == prod.Id)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (inv, prod) => inv.ProductId == input.ProductId)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.ProductName), (inv, prod) => prod.ProductName.Contains(input.ProductName))
+ .WhereIF(!string.IsNullOrWhiteSpace(input.BatchNumber), (inv, prod) => inv.BatchNumber != null && inv.BatchNumber.Contains(input.BatchNumber))
+ .WhereIF(input.QuantityMin.HasValue, (inv, prod) => inv.Quantity >= input.QuantityMin.Value)
+ .WhereIF(input.QuantityMax.HasValue, (inv, prod) => inv.Quantity <= input.QuantityMax.Value)
+ .WhereIF(input.IsEffective.HasValue, (inv, prod) => inv.IsEffective == input.IsEffective.Value)
+ .Select((inv, prod) => new LqInventoryListOutput
{
- id = x.Id,
- productName = x.ProductName,
- price = x.Price,
- quantity = x.Quantity,
- productCategory = x.ProductCategory,
- departmentId = x.DepartmentId,
- departmentName = "",
- standardUnit = x.StandardUnit,
- totalValue = x.Price * x.Quantity,
- createUser = x.CreateUser,
+ id = inv.Id,
+ productId = inv.ProductId,
+ productName = prod.ProductName,
+ productPrice = prod.Price,
+ quantity = inv.Quantity,
+ usedQuantity = 0, // 稍后计算
+ availableQuantity = inv.Quantity, // 稍后计算
+ stockInTime = inv.StockInTime,
+ productionDate = inv.ProductionDate,
+ shelfLife = inv.ShelfLife,
+ batchNumber = inv.BatchNumber,
+ createUser = inv.CreateUser,
createUserName = "",
- createTime = x.CreateTime,
- updateUser = x.UpdateUser,
+ createTime = inv.CreateTime,
+ updateUser = inv.UpdateUser,
updateUserName = "",
- updateTime = x.UpdateTime,
- isEffective = x.IsEffective
+ updateTime = inv.UpdateTime,
+ isEffective = inv.IsEffective
})
.MergeTable()
- .OrderBy(sidx + " " + input.sort)
+ .OrderBy(sidx + " " + sort)
.ToPagedListAsync(input.currentPage, input.pageSize);
- // 补充用户名称信息
- foreach (var item in data.list)
+ // 批量查询每个库存的已使用数量
+ var inventoryIds = data.list.Select(x => x.id).ToList();
+ if (inventoryIds.Any())
{
- if (!string.IsNullOrEmpty(item.departmentId))
- {
- var deptUser = await _db.Queryable().Where(u => u.Id == item.departmentId).FirstAsync();
- item.departmentName = deptUser?.RealName ?? "";
- }
- if (!string.IsNullOrEmpty(item.createUser))
+ // 注意:库存使用记录表中存储的是产品ID,不是库存ID
+ // 这里需要根据业务逻辑调整,如果使用记录需要关联到具体库存批次,需要修改使用记录表结构
+ // 暂时按产品ID统计已使用数量
+ var productIds = data.list.Select(x => x.productId).Distinct().ToList();
+ var usageDict = await _db.Queryable()
+ .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .GroupBy(x => x.ProductId)
+ .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
+ .ToListAsync();
+
+ var usageDictMap = usageDict.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
+
+ // 计算每个库存的已使用数量和可用数量
+ // 注意:这里假设同一产品的所有库存共享使用记录,如果需要按批次区分,需要修改使用记录表
+ foreach (var item in data.list)
{
- var createUser = await _db.Queryable().Where(u => u.Id == item.createUser).FirstAsync();
- item.createUserName = createUser?.RealName ?? "";
+ var totalUsage = usageDictMap.ContainsKey(item.productId) ? usageDictMap[item.productId] : 0;
+ // 按比例分配已使用数量(简化处理,实际可能需要更复杂的逻辑)
+ var productInventoryList = data.list.Where(x => x.productId == item.productId).ToList();
+ var totalProductQuantity = productInventoryList.Sum(x => x.quantity);
+ if (totalProductQuantity > 0)
+ {
+ item.usedQuantity = (int)(item.quantity * 1.0m / totalProductQuantity * totalUsage);
+ }
+ item.availableQuantity = item.quantity - item.usedQuantity;
}
- if (!string.IsNullOrEmpty(item.updateUser))
+ }
+
+ // 补充用户名称信息
+ var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser })
+ .Where(x => !string.IsNullOrEmpty(x))
+ .Distinct()
+ .ToList();
+
+ if (userIds.Any())
+ {
+ var userList = await _db.Queryable()
+ .Where(x => userIds.Contains(x.Id))
+ .Select(x => new { x.Id, x.RealName })
+ .ToListAsync();
+ var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
+
+ foreach (var item in data.list)
{
- var updateUser = await _db.Queryable().Where(u => u.Id == item.updateUser).FirstAsync();
- item.updateUserName = updateUser?.RealName ?? "";
+ if (!string.IsNullOrEmpty(item.createUser) && userDict.ContainsKey(item.createUser))
+ item.createUserName = userDict[item.createUser];
+ if (!string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser))
+ item.updateUserName = userDict[item.updateUser];
}
}
+
return PageResult.SqlSugarPageResult(data);
}
catch (Exception ex)
@@ -230,52 +292,75 @@ namespace NCC.Extend
{
throw NCCException.Oh("库存ID不能为空");
}
- // 查询库存信息
- var inventory = await _db.Queryable().Where(x => x.Id == id).FirstAsync();
+
+ var inventory = await _db.Queryable(
+ (inv, prod) => inv.ProductId == prod.Id)
+ .Where((inv, prod) => inv.Id == id)
+ .Select((inv, prod) => new LqInventoryInfoOutput
+ {
+ id = inv.Id,
+ productId = inv.ProductId,
+ productName = prod.ProductName,
+ productPrice = prod.Price,
+ quantity = inv.Quantity,
+ usedQuantity = 0, // 稍后计算
+ availableQuantity = inv.Quantity, // 稍后计算
+ stockInTime = inv.StockInTime,
+ productionDate = inv.ProductionDate,
+ shelfLife = inv.ShelfLife,
+ batchNumber = inv.BatchNumber,
+ createUser = inv.CreateUser,
+ createUserName = "",
+ createTime = inv.CreateTime,
+ updateUser = inv.UpdateUser,
+ updateUserName = "",
+ updateTime = inv.UpdateTime,
+ isEffective = inv.IsEffective
+ })
+ .FirstAsync();
if (inventory == null)
{
throw NCCException.Oh("库存记录不存在");
}
- var result = new LqInventoryInfoOutput
- {
- id = inventory.Id,
- productName = inventory.ProductName,
- price = inventory.Price,
- quantity = inventory.Quantity,
- productCategory = inventory.ProductCategory,
- departmentId = inventory.DepartmentId,
- departmentName = "",
- standardUnit = inventory.StandardUnit,
- totalValue = inventory.Price * inventory.Quantity,
- createUser = inventory.CreateUser,
- createUserName = "",
- createTime = inventory.CreateTime,
- updateUser = inventory.UpdateUser,
- updateUserName = "",
- updateTime = inventory.UpdateTime,
- isEffective = inventory.IsEffective
- };
+ // 计算已使用数量
+ var totalUsage = await _db.Queryable()
+ .Where(x => x.ProductId == inventory.productId && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
- // 补充用户名称信息
- if (!string.IsNullOrEmpty(result.departmentId))
- {
- var deptUser = await _db.Queryable().Where(u => u.Id == result.departmentId).FirstAsync();
- result.departmentName = deptUser?.RealName ?? "";
- }
- if (!string.IsNullOrEmpty(result.createUser))
+ // 计算该产品的总库存
+ var totalInventory = await _db.Queryable()
+ .Where(x => x.ProductId == inventory.productId && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .SumAsync(x => (int?)x.Quantity) ?? 0;
+
+ if (totalInventory > 0)
{
- var createUser = await _db.Queryable().Where(u => u.Id == result.createUser).FirstAsync();
- result.createUserName = createUser?.RealName ?? "";
+ inventory.usedQuantity = (int)(inventory.quantity * 1.0m / totalInventory * totalUsage);
}
- if (!string.IsNullOrEmpty(result.updateUser))
+ inventory.availableQuantity = inventory.quantity - inventory.usedQuantity;
+
+ // 补充用户名称信息
+ var userIds = new[] { inventory.createUser, inventory.updateUser }
+ .Where(x => !string.IsNullOrEmpty(x))
+ .Distinct()
+ .ToList();
+
+ if (userIds.Any())
{
- var updateUser = await _db.Queryable().Where(u => u.Id == result.updateUser).FirstAsync();
- result.updateUserName = updateUser?.RealName ?? "";
+ var userList = await _db.Queryable()
+ .Where(x => userIds.Contains(x.Id))
+ .Select(x => new { x.Id, x.RealName })
+ .ToListAsync();
+ var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
+
+ if (!string.IsNullOrEmpty(inventory.createUser) && userDict.ContainsKey(inventory.createUser))
+ inventory.createUserName = userDict[inventory.createUser];
+ if (!string.IsNullOrEmpty(inventory.updateUser) && userDict.ContainsKey(inventory.updateUser))
+ inventory.updateUserName = userDict[inventory.updateUser];
}
- return result;
+ return inventory;
}
catch (Exception ex)
{
@@ -285,6 +370,77 @@ namespace NCC.Extend
}
#endregion
+ #region 作废库存
+ ///
+ /// 作废库存
+ ///
+ ///
+ /// 作废库存记录,需要判断已使用数量是否超过作废后的总数量
+ /// 如果已使用数量 > 作废后的总数量,则不允许作废
+ ///
+ /// 示例请求:
+ /// ```
+ /// PUT /api/Extend/LqInventory/Cancel/{id}?remarks=作废备注
+ /// ```
+ ///
+ /// 库存ID
+ /// 作废备注
+ /// 作废结果
+ /// 作废成功
+ /// 库存不存在或已使用数量超过作废后的总数量
+ /// 服务器错误
+ [HttpPut("Cancel/{id}")]
+ public async Task CancelAsync([FromRoute] string id, [FromQuery] string remarks = null)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ throw NCCException.Oh("库存ID不能为空");
+ }
+
+ var inventory = await _db.Queryable()
+ .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .FirstAsync();
+
+ if (inventory == null)
+ {
+ throw NCCException.Oh("库存记录不存在或已失效");
+ }
+
+ // 计算该产品的总库存(包括当前库存)
+ var totalInventory = await _db.Queryable()
+ .Where(x => x.ProductId == inventory.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .SumAsync(x => (int?)x.Quantity) ?? 0;
+
+ // 计算该产品的已使用数量
+ var totalUsage = await _db.Queryable()
+ .Where(x => x.ProductId == inventory.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
+
+ // 作废后的总数量 = 当前总库存 - 当前库存数量
+ var inventoryAfterCancel = totalInventory - inventory.Quantity;
+
+ // 判断:如果已使用数量 > 作废后的总数量,则不允许作废
+ if (totalUsage > inventoryAfterCancel)
+ {
+ throw NCCException.Oh($"不允许作废:已使用数量({totalUsage})超过作废后的总数量({inventoryAfterCancel})");
+ }
+
+ // 作废库存
+ inventory.IsEffective = StatusEnum.无效.GetHashCode();
+ inventory.UpdateUser = _userManager.UserId;
+ inventory.UpdateTime = DateTime.Now;
+ var isOk = await _db.Updateable(inventory).ExecuteCommandAsync();
+ if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "作废库存失败");
+ throw NCCException.Oh($"作废失败:{ex.Message}");
+ }
+ }
+ #endregion
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
index 531e997..3cfaf01 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
@@ -11,6 +11,7 @@ using NCC.DynamicApiController;
using NCC.Extend.Entitys.Dto.LqInventoryUsage;
using NCC.Extend.Entitys.Enum;
using NCC.Extend.Entitys.lq_inventory;
+using NCC.Extend.Entitys.lq_product;
using NCC.Extend.Entitys.lq_inventory_usage;
using NCC.Extend.Entitys.lq_mdxx;
using NCC.Extend.Interfaces.LqInventoryUsage;
@@ -57,17 +58,32 @@ namespace NCC.Extend
try
{
// 验证产品是否存在
- var product = await _db.Queryable().Where(x => x.Id == input.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync();
+ var product = await _db.Queryable()
+ .Where(x => x.Id == input.ProductId)
+ .FirstAsync();
if (product == null)
{
- throw NCCException.Oh("产品不存在或已失效");
+ throw NCCException.Oh("产品不存在");
}
+ // 计算该产品的总库存数量(所有有效库存的总和)
+ var totalInventory = await _db.Queryable()
+ .Where(x => x.ProductId == input.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .SumAsync(x => (int?)x.Quantity) ?? 0;
+
+ // 计算该产品的已使用数量
+ var totalUsage = await _db.Queryable()
+ .Where(x => x.ProductId == input.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
+
+ // 计算可用库存
+ var availableInventory = totalInventory - totalUsage;
+
// 检查库存数量是否足够
- if (product.Quantity < input.UsageQuantity)
+ if (availableInventory < input.UsageQuantity)
{
- throw NCCException.Oh($"库存不足,当前库存:{product.Quantity},需要数量:{input.UsageQuantity}");
+ throw NCCException.Oh($"库存不足,当前可用库存:{availableInventory},需要数量:{input.UsageQuantity}");
}
_db.Ado.BeginTran();
@@ -89,12 +105,7 @@ namespace NCC.Extend
var isOk = await _db.Insertable(usageEntity).ExecuteCommandAsync();
if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
- // 更新库存数量
- product.Quantity -= input.UsageQuantity;
- product.UpdateUser = _userManager.UserId;
- product.UpdateTime = DateTime.Now;
- var updateOk = await _db.Updateable(product).ExecuteCommandAsync();
- if (!(updateOk > 0)) throw NCCException.Oh(ErrorCode.COM1001);
+ // 注意:新结构下不需要更新库存表的数量,因为库存数量是固定的,使用记录只是记录使用情况
_db.Ado.CommitTran();
}
catch (Exception ex)
@@ -140,17 +151,8 @@ namespace NCC.Extend
var isOk = await _db.Updateable(usageRecord).ExecuteCommandAsync();
if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1003);
- // 恢复库存数量
- var product = await _db.Queryable().Where(x => x.Id == usageRecord.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync();
- if (product != null)
- {
- product.Quantity += usageRecord.UsageQuantity;
- product.UpdateUser = _userManager.UserId;
- product.UpdateTime = DateTime.Now;
- var updateOk = await _db.Updateable(product).ExecuteCommandAsync();
- if (!(updateOk > 0)) throw NCCException.Oh(ErrorCode.COM1001);
- }
+ // 注意:新结构下不需要恢复库存表的数量,因为库存数量是固定的,使用记录只是记录使用情况
_db.Ado.CommitTran();
}
catch (Exception ex)
@@ -175,82 +177,77 @@ namespace NCC.Extend
{
var sidx = input.sidx == null ? "id" : input.sidx;
- // 查询使用记录信息
- var data = await _db.Queryable()
- .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), x => x.ProductId == input.ProductId)
- .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), x => x.StoreId == input.StoreId)
- .WhereIF(input.UsageStartTime.HasValue, x => x.UsageTime >= input.UsageStartTime.Value)
- .WhereIF(input.UsageEndTime.HasValue, x => x.UsageTime <= input.UsageEndTime.Value)
- .WhereIF(!string.IsNullOrWhiteSpace(input.RelatedConsumeId), x => x.RelatedConsumeId == input.RelatedConsumeId)
- .WhereIF(input.IsEffective.HasValue, x => x.IsEffective == input.IsEffective.Value)
- .Select(x => new LqInventoryUsageListOutput
+ // 查询使用记录信息,关联产品表
+ var data = await _db.Queryable(
+ (usage, product) => usage.ProductId == product.Id)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product) => usage.StoreId == input.StoreId)
+ .WhereIF(input.UsageStartTime.HasValue, (usage, product) => usage.UsageTime >= input.UsageStartTime.Value)
+ .WhereIF(input.UsageEndTime.HasValue, (usage, product) => usage.UsageTime <= input.UsageEndTime.Value)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.RelatedConsumeId), (usage, product) => usage.RelatedConsumeId == input.RelatedConsumeId)
+ .WhereIF(input.IsEffective.HasValue, (usage, product) => usage.IsEffective == input.IsEffective.Value)
+ .Select((usage, product) => new LqInventoryUsageListOutput
{
- id = x.Id,
- productId = x.ProductId,
- productName = "",
- productCategory = "",
- productPrice = 0,
- storeId = x.StoreId,
- storeName = SqlFunc.Subqueryable().Where(u => u.Id == x.StoreId).Select(u => u.Dm),
- usageTime = x.UsageTime,
- usageQuantity = x.UsageQuantity,
- relatedConsumeId = x.RelatedConsumeId,
- createUser = x.CreateUser,
+ id = usage.Id,
+ productId = usage.ProductId,
+ productName = product.ProductName,
+ productCategory = product.ProductCategory,
+ productPrice = product.Price,
+ storeId = usage.StoreId,
+ storeName = SqlFunc.Subqueryable().Where(u => u.Id == usage.StoreId).Select(u => u.Dm),
+ usageTime = usage.UsageTime,
+ usageQuantity = usage.UsageQuantity,
+ relatedConsumeId = usage.RelatedConsumeId,
+ createUser = usage.CreateUser,
createUserName = "",
- createTime = x.CreateTime,
- updateUser = x.UpdateUser,
+ createTime = usage.CreateTime,
+ updateUser = usage.UpdateUser,
updateUserName = "",
- updateTime = x.UpdateTime,
- isEffective = x.IsEffective
+ updateTime = usage.UpdateTime,
+ isEffective = usage.IsEffective
})
.MergeTable()
.OrderBy(sidx + " " + input.sort)
.ToPagedListAsync(input.currentPage, input.pageSize);
- // 补充产品信息和用户信息
- foreach (var item in data.list)
+ // 补充用户信息
+ var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser })
+ .Where(x => !string.IsNullOrEmpty(x))
+ .Distinct()
+ .ToList();
+
+ if (userIds.Any())
{
- if (!string.IsNullOrEmpty(item.productId))
- {
- var product = await _db.Queryable().Where(p => p.Id == item.productId).FirstAsync();
- if (product != null)
- {
- item.productName = product.ProductName;
- item.productCategory = product.ProductCategory;
- item.productPrice = product.Price;
- }
- }
- if (!string.IsNullOrEmpty(item.storeId))
- {
- var store = await _db.Queryable().Where(u => u.Id == item.storeId).FirstAsync();
- item.storeName = store?.RealName ?? "";
- }
- if (!string.IsNullOrEmpty(item.createUser))
- {
- var createUser = await _db.Queryable().Where(u => u.Id == item.createUser).FirstAsync();
- item.createUserName = createUser?.RealName ?? "";
- }
- if (!string.IsNullOrEmpty(item.updateUser))
+ var userList = await _db.Queryable()
+ .Where(x => userIds.Contains(x.Id))
+ .Select(x => new { x.Id, x.RealName })
+ .ToListAsync();
+ var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
+
+ foreach (var item in data.list)
{
- var updateUser = await _db.Queryable().Where(u => u.Id == item.updateUser).FirstAsync();
- item.updateUserName = updateUser?.RealName ?? "";
+ if (!string.IsNullOrEmpty(item.createUser) && userDict.ContainsKey(item.createUser))
+ item.createUserName = userDict[item.createUser];
+ if (!string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser))
+ item.updateUserName = userDict[item.updateUser];
}
}
- // 应用产品名称和分类的过滤条件
+ // 应用产品名称和分类的过滤条件(在内存中过滤,因为已经在Select中获取了)
if (!string.IsNullOrWhiteSpace(input.ProductName) || !string.IsNullOrWhiteSpace(input.ProductCategory))
{
data.list = data.list.Where(x =>
- (string.IsNullOrWhiteSpace(input.ProductName) || x.productName.Contains(input.ProductName)) &&
- (string.IsNullOrWhiteSpace(input.ProductCategory) || x.productCategory.Contains(input.ProductCategory))
+ (string.IsNullOrWhiteSpace(input.ProductName) || (x.productName != null && x.productName.Contains(input.ProductName))) &&
+ (string.IsNullOrWhiteSpace(input.ProductCategory) || (x.productCategory != null && x.productCategory.Contains(input.ProductCategory)))
).ToList();
}
- // 应用门店名称的过滤条件
+ // 应用门店名称的过滤条件(在内存中过滤,因为已经在Select中获取了)
if (!string.IsNullOrWhiteSpace(input.StoreName))
{
- data.list = data.list.Where(x => x.storeName.Contains(input.StoreName)).ToList();
+ data.list = data.list.Where(x => x.storeName != null && x.storeName.Contains(input.StoreName)).ToList();
}
+
return PageResult.SqlSugarPageResult(data);
}
catch (Exception ex)
@@ -273,7 +270,7 @@ namespace NCC.Extend
try
{
var data = await _db.Queryable()
- .LeftJoin((usage, product) => usage.ProductId == product.Id)
+ .LeftJoin((usage, product) => usage.ProductId == product.Id)
.Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime)
.Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId)
@@ -321,18 +318,18 @@ namespace NCC.Extend
try
{
var data = await _db.Queryable()
- .LeftJoin((usage, product) => usage.ProductId == product.Id)
- .LeftJoin((usage, product, store) => usage.StoreId == store.Id)
+ .LeftJoin((usage, product) => usage.ProductId == product.Id)
+ .LeftJoin((usage, product, store) => usage.StoreId == store.Id)
.Where((usage, product, store) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime)
.Where((usage, product, store) => usage.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product, store) => usage.ProductId == input.ProductId)
.WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (usage, product, store) => usage.StoreId == input.StoreId)
.WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), (usage, product, store) => product.ProductCategory == input.ProductCategory)
- .GroupBy((usage, product, store) => new { usage.StoreId, store.RealName })
+ .GroupBy((usage, product, store) => new { usage.StoreId, store.Dm })
.Select((usage, product, store) => new StoreUsageStatisticsOutput
{
StoreId = usage.StoreId,
- StoreName = store.RealName,
+ StoreName = store.Dm,
TotalUsageQuantity = SqlFunc.AggregateSum(usage.UsageQuantity),
TotalUsageAmount = SqlFunc.AggregateSum(usage.UsageQuantity * product.Price),
UsageCount = SqlFunc.AggregateCount(usage.Id),
@@ -369,7 +366,7 @@ namespace NCC.Extend
{
// 先获取基础数据
var baseData = await _db.Queryable()
- .LeftJoin((usage, product) => usage.ProductId == product.Id)
+ .LeftJoin((usage, product) => usage.ProductId == product.Id)
.Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime)
.Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId)
@@ -450,7 +447,7 @@ namespace NCC.Extend
try
{
var data = await _db.Queryable()
- .LeftJoin((usage, product) => usage.ProductId == product.Id)
+ .LeftJoin((usage, product) => usage.ProductId == product.Id)
.Where((usage, product) => usage.UsageTime >= input.StartTime && usage.UsageTime <= input.EndTime)
.Where((usage, product) => usage.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(!string.IsNullOrWhiteSpace(input.ProductId), (usage, product) => usage.ProductId == input.ProductId)
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
index a9beab8..eab8a42 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -3678,5 +3678,188 @@ namespace NCC.Extend.LqKdKdjlb
}
#endregion
+ #region 批量处理历史开单数据的升单类型
+ ///
+ /// 批量处理历史开单数据的升单类型
+ ///
+ ///
+ /// 批量更新历史开单记录的升单类型字段(升生美、升科美、升医美)
+ ///
+ /// 处理逻辑:
+ /// 1. 查询所有有效的开单记录
+ /// 2. 对于每条开单记录,应用升单判断逻辑:
+ /// - 判断当前开单是否包含医美/科美/生美品项
+ /// - 判断该会员在当前开单日期之前是否有对应类型的开单记录(重要:只看该开单之前的记录)
+ /// - 更新升单类型字段
+ /// 3. 批量更新开单记录
+ ///
+ /// 升单判断规则:
+ /// - 升医美:当前开单包含医美品项 + 该会员在当前开单日期之前有医美类型的开单记录 + 当前开单医美品项金额>=1000
+ /// - 升科美:当前开单包含科美品项 + 该会员在当前开单日期之前有科美类型的开单记录
+ /// - 升生美:当前开单包含生美品项 + 该会员在当前开单日期之前有生美类型的开单记录
+ ///
+ /// 处理结果,包含处理的记录数和成功数
+ /// 处理成功
+ /// 服务器错误
+ [HttpPost("batch-update-upgrade-type")]
+ public async Task BatchUpdateUpgradeType()
+ {
+ try
+ {
+ // 1. 查询所有有效的开单记录
+ var billingList = await _db.Queryable().Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
+ if (!billingList.Any())
+ {
+ return new { success = true, message = "没有需要处理的开单记录", totalCount = 0, successCount = 0 };
+ }
+ _logger.LogInformation($"找到 {billingList.Count} 条开单记录需要处理");
+ // 2. 批量查询关联数据
+ var billingIds = billingList.Select(x => x.Id).ToList();
+ var memberIds = billingList.Select(x => x.Kdhy).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
+ // 查询所有品项明细
+ var pxmxList = await _db.Queryable().Where(x => billingIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
+ // 查询所有项目资料(用于获取品项分类)
+ var itemIds = pxmxList.Where(x => !string.IsNullOrEmpty(x.Px)).Select(x => x.Px).Distinct().ToList();
+ var itemCategoryDict = new Dictionary();
+ if (itemIds.Any())
+ {
+ var itemCategories = await _db.Queryable().Where(x => itemIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode()).Select(x => new { x.Id, x.Qt2 }).ToListAsync();
+ itemCategoryDict = itemCategories.ToDictionary(k => k.Id, v => v.Qt2 ?? "");
+ }
+
+ // 按开单ID分组品项明细
+ var pxmxGrouped = pxmxList.GroupBy(x => x.Glkdbh).ToDictionary(g => g.Key, g => g.ToList());
+
+ // 3. 批量处理,每批500条
+ const int batchSize = 500;
+ var totalBatches = (int)Math.Ceiling((double)billingList.Count / batchSize);
+ var successCount = 0;
+ var updateList = new List();
+
+ for (int batchIndex = 0; batchIndex < totalBatches; batchIndex++)
+ {
+ var batchBillingList = billingList.Skip(batchIndex * batchSize).Take(batchSize).ToList();
+
+ foreach (var billing in batchBillingList)
+ {
+ try
+ {
+ // 获取该开单的品项明细
+ var currentPxmxList = pxmxGrouped.ContainsKey(billing.Id) ? pxmxGrouped[billing.Id] : new List();
+
+ if (!currentPxmxList.Any())
+ {
+ // 如果没有品项明细,设置为"否"
+ billing.UpgradeMedicalBeauty = "否";
+ billing.UpgradeTechBeauty = "否";
+ billing.UpgradeLifeBeauty = "否";
+ updateList.Add(billing);
+ continue;
+ }
+
+ // 判断当前开单是否包含医美/科美/生美品项
+ var hasMedicalItem = currentPxmxList.Any(x =>
+ !string.IsNullOrEmpty(x.Px) && itemCategoryDict.ContainsKey(x.Px) && itemCategoryDict[x.Px] == "医美");
+ var medicalItemAmount = currentPxmxList
+ .Where(x => !string.IsNullOrEmpty(x.Px) && itemCategoryDict.ContainsKey(x.Px) && itemCategoryDict[x.Px] == "医美")
+ .Sum(x => x.ActualPrice);
+
+ var hasTechItem = currentPxmxList.Any(x =>
+ !string.IsNullOrEmpty(x.Px) && itemCategoryDict.ContainsKey(x.Px) && itemCategoryDict[x.Px] == "科美");
+
+ var hasLifeItem = currentPxmxList.Any(x =>
+ !string.IsNullOrEmpty(x.Px) && itemCategoryDict.ContainsKey(x.Px) && itemCategoryDict[x.Px] == "生美");
+
+ // 判断该会员在当前开单日期之前是否有对应类型的开单记录
+ if (!string.IsNullOrEmpty(billing.Kdhy) && billing.Kdrq != null)
+ {
+ var currentBillingDate = billing.Kdrq.Value;
+
+ // 查询该会员在当前开单日期之前的开单记录
+ var previousBillingIds = await _db.Queryable()
+ .Where(x => x.Kdhy == billing.Kdhy
+ && x.IsEffective == StatusEnum.有效.GetHashCode()
+ && x.Kdrq < currentBillingDate)
+ .Select(x => x.Id)
+ .ToListAsync();
+
+ if (previousBillingIds.Any())
+ {
+ // 查询这些开单的品项明细
+ var previousPxmxList = await _db.Queryable()
+ .Where(x => previousBillingIds.Contains(x.Glkdbh)
+ && x.IsEffective == StatusEnum.有效.GetHashCode()
+ && x.ActualPrice > 0
+ && x.Px != "61")
+ .Select(x => x.ItemCategory)
+ .Distinct()
+ .ToListAsync();
+
+ var hasPreviousMedical = previousPxmxList.Contains("医美");
+ var hasPreviousTech = previousPxmxList.Contains("科美");
+ var hasPreviousLife = previousPxmxList.Contains("生美");
+
+ // 判断升医美
+ billing.UpgradeMedicalBeauty = (hasMedicalItem && hasPreviousMedical && medicalItemAmount >= 1000) ? "是" : "否";
+
+ // 判断升科美
+ billing.UpgradeTechBeauty = (hasTechItem && hasPreviousTech) ? "是" : "否";
+
+ // 判断升生美
+ billing.UpgradeLifeBeauty = (hasLifeItem && hasPreviousLife) ? "是" : "否";
+ }
+ else
+ {
+ billing.UpgradeMedicalBeauty = "否";
+ billing.UpgradeTechBeauty = "否";
+ billing.UpgradeLifeBeauty = "否";
+ }
+ }
+ else
+ {
+ billing.UpgradeMedicalBeauty = "否";
+ billing.UpgradeTechBeauty = "否";
+ billing.UpgradeLifeBeauty = "否";
+ }
+
+ updateList.Add(billing);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"处理开单记录 {billing.Id} 失败: {ex.Message}");
+ }
+ }
+
+ // 批量更新
+ if (updateList.Any())
+ {
+ await _db.Updateable(updateList)
+ .UpdateColumns(it => new { it.UpgradeMedicalBeauty, it.UpgradeTechBeauty, it.UpgradeLifeBeauty })
+ .ExecuteCommandAsync();
+ successCount += updateList.Count;
+ updateList.Clear();
+ }
+
+ _logger.LogInformation($"已处理 {Math.Min((batchIndex + 1) * batchSize, billingList.Count)} / {billingList.Count} 条记录");
+ }
+
+ _logger.LogInformation($"批量处理完成,共处理 {billingList.Count} 条记录,成功 {successCount} 条");
+
+ return new
+ {
+ success = true,
+ message = "批量处理完成",
+ totalCount = billingList.Count,
+ successCount = successCount
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"批量处理历史开单数据的升单类型失败: {ex.ToString()}");
+ throw NCCException.Oh(ErrorCode.COM1005, $"批量处理历史开单数据的升单类型失败: {ex.Message}");
+ }
+ }
+ #endregion
+
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs
new file mode 100644
index 0000000..0f188de
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs
@@ -0,0 +1,694 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using NCC.Common.Core.Manager;
+using NCC.Common.Enum;
+using NCC.Common.Filter;
+using NCC.Dependency;
+using NCC.DynamicApiController;
+using NCC.Extend.Entitys.Dto.LqProduct;
+using NCC.Extend.Entitys.Enum;
+using NCC.Extend.Entitys.lq_inventory;
+using NCC.Extend.Entitys.lq_inventory_usage;
+using NCC.Extend.Entitys.lq_mdxx;
+using NCC.Extend.Entitys.lq_product;
+using NCC.Extend.Interfaces.LqProduct;
+using NCC.FriendlyException;
+using NCC.System.Entitys.Permission;
+using SqlSugar;
+using Yitter.IdGenerator;
+
+namespace NCC.Extend
+{
+ ///
+ /// 产品服务
+ ///
+ [ApiDescriptionSettings(Tag = "绿纤产品管理", Name = "LqProduct", Order = 200)]
+ [Route("api/Extend/LqProduct")]
+ public class LqProductService : IDynamicApiController, ITransient, ILqProductService
+ {
+ private readonly IUserManager _userManager;
+ private readonly ILogger _logger;
+ private readonly ISqlSugarClient _db;
+
+ ///
+ /// 构造函数
+ ///
+ /// 用户管理器
+ /// 日志记录器
+ /// 数据库客户端
+ public LqProductService(IUserManager userManager, ILogger logger, ISqlSugarClient db)
+ {
+ _userManager = userManager;
+ _logger = logger;
+ _db = db;
+ }
+
+ #region 添加产品信息
+ ///
+ /// 添加产品信息
+ ///
+ ///
+ /// 创建新产品,包含产品的基本信息、价格、类别等
+ ///
+ /// 示例请求:
+ /// ```json
+ /// {
+ /// "productName": "产品名称",
+ /// "price": 100.00,
+ /// "productCategory": "产品类别",
+ /// "departmentId": "部门ID",
+ /// "standardUnit": "标准单位",
+ /// "onShelfStatus": 1,
+ /// "statisticsCategory": "统计分类",
+ /// "warehouse": "归属仓库",
+ /// "supplierName": "供应商名称"
+ /// }
+ /// ```
+ ///
+ /// 创建输入
+ /// 创建结果
+ /// 创建成功
+ /// 请求参数错误
+ /// 服务器错误
+ [HttpPost("Create")]
+ public async Task CreateAsync([FromBody] LqProductCrInput input)
+ {
+ try
+ {
+ // 检查产品名称是否已存在
+ var existingProduct = await _db.Queryable()
+ .Where(x => x.ProductName == input.ProductName)
+ .FirstAsync();
+
+ if (existingProduct != null)
+ {
+ throw NCCException.Oh($"产品名称'{input.ProductName}'已存在");
+ }
+
+ // 创建产品
+ var productEntity = new LqProductEntity
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ ProductName = input.ProductName,
+ Price = input.Price,
+ ProductCategory = input.ProductCategory,
+ DepartmentId = input.DepartmentId,
+ StandardUnit = input.StandardUnit,
+ OnShelfStatus = input.OnShelfStatus,
+ StatisticsCategory = input.StatisticsCategory,
+ Warehouse = input.Warehouse,
+ UnitConversion = input.UnitConversion,
+ SupplierName = input.SupplierName,
+ ContractSignDate = input.ContractSignDate,
+ ContractEndDate = input.ContractEndDate,
+ Remark = input.Remark,
+ CreateUser = _userManager.UserId,
+ CreateTime = DateTime.Now
+ };
+
+ var isOk = await _db.Insertable(productEntity).ExecuteCommandAsync();
+ if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "创建产品失败");
+ throw NCCException.Oh($"创建失败:{ex.Message}");
+ }
+ }
+ #endregion
+
+ #region 更新产品信息
+ ///
+ /// 更新产品信息
+ ///
+ /// 更新输入
+ /// 更新结果
+ [HttpPut("Update")]
+ public async Task UpdateAsync([FromBody] LqProductUpInput input)
+ {
+ try
+ {
+ var existingProduct = await _db.Queryable()
+ .Where(x => x.Id == input.Id)
+ .FirstAsync();
+
+ if (existingProduct == null)
+ {
+ throw NCCException.Oh("产品不存在");
+ }
+
+ // 检查产品名称是否与其他产品重复
+ var duplicateProduct = await _db.Queryable()
+ .Where(x => x.ProductName == input.ProductName && x.Id != input.Id)
+ .FirstAsync();
+
+ if (duplicateProduct != null)
+ {
+ throw NCCException.Oh($"产品名称'{input.ProductName}'已存在");
+ }
+
+ // 更新产品信息
+ existingProduct.ProductName = input.ProductName;
+ existingProduct.Price = input.Price;
+ existingProduct.ProductCategory = input.ProductCategory;
+ existingProduct.DepartmentId = input.DepartmentId;
+ existingProduct.StandardUnit = input.StandardUnit;
+ existingProduct.OnShelfStatus = input.OnShelfStatus;
+ existingProduct.StatisticsCategory = input.StatisticsCategory;
+ existingProduct.Warehouse = input.Warehouse;
+ existingProduct.UnitConversion = input.UnitConversion;
+ existingProduct.SupplierName = input.SupplierName;
+ existingProduct.ContractSignDate = input.ContractSignDate;
+ existingProduct.ContractEndDate = input.ContractEndDate;
+ existingProduct.Remark = input.Remark;
+ existingProduct.UpdateUser = _userManager.UserId;
+ existingProduct.UpdateTime = DateTime.Now;
+
+ var isOk = await _db.Updateable(existingProduct).ExecuteCommandAsync();
+ if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "更新产品失败");
+ throw NCCException.Oh($"更新失败:{ex.Message}");
+ }
+ }
+ #endregion
+
+ #region 获取产品列表(包含现有库存)
+ ///
+ /// 获取产品列表(包含现有库存)
+ ///
+ ///
+ /// 返回产品列表,每个产品包含当前库存数量(所有有效库存的总和)
+ ///
+ /// 示例请求:
+ /// ```json
+ /// {
+ /// "currentPage": 1,
+ /// "pageSize": 10,
+ /// "sidx": "createTime",
+ /// "sort": "desc",
+ /// "productName": "产品名称",
+ /// "productCategory": "产品类别",
+ /// "onShelfStatus": 1
+ /// }
+ /// ```
+ ///
+ /// 查询输入
+ /// 产品列表(包含现有库存)
+ /// 查询成功
+ /// 服务器错误
+ [HttpGet("GetList")]
+ public async Task GetListAsync([FromQuery] LqProductListQueryInput input)
+ {
+ try
+ {
+ var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx;
+ var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
+
+ // 查询产品信息
+ var data = await _db.Queryable()
+ .WhereIF(!string.IsNullOrWhiteSpace(input.ProductName), x => x.ProductName.Contains(input.ProductName))
+ .WhereIF(!string.IsNullOrWhiteSpace(input.ProductCategory), x => x.ProductCategory == input.ProductCategory)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.DepartmentId), x => x.DepartmentId == input.DepartmentId)
+ .WhereIF(input.OnShelfStatus.HasValue, x => x.OnShelfStatus == input.OnShelfStatus.Value)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.StatisticsCategory), x => x.StatisticsCategory == input.StatisticsCategory)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.Warehouse), x => x.Warehouse == input.Warehouse)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.SupplierName), x => x.SupplierName != null && x.SupplierName.Contains(input.SupplierName))
+ .WhereIF(input.PriceMin.HasValue, x => x.Price >= input.PriceMin.Value)
+ .WhereIF(input.PriceMax.HasValue, x => x.Price <= input.PriceMax.Value)
+ .Select(x => new LqProductListOutput
+ {
+ id = x.Id,
+ productName = x.ProductName,
+ price = x.Price,
+ productCategory = x.ProductCategory,
+ departmentId = x.DepartmentId,
+ departmentName = "",
+ standardUnit = x.StandardUnit,
+ onShelfStatus = x.OnShelfStatus,
+ statisticsCategory = x.StatisticsCategory,
+ warehouse = x.Warehouse,
+ unitConversion = x.UnitConversion,
+ supplierName = x.SupplierName,
+ contractSignDate = x.ContractSignDate,
+ contractEndDate = x.ContractEndDate,
+ remark = x.Remark,
+ currentInventory = 0, // 稍后计算
+ createUser = x.CreateUser,
+ createUserName = "",
+ createTime = x.CreateTime,
+ updateUser = x.UpdateUser,
+ updateUserName = "",
+ updateTime = x.UpdateTime
+ })
+ .MergeTable()
+ .OrderBy(sidx + " " + sort)
+ .ToPagedListAsync(input.currentPage, input.pageSize);
+
+ // 批量查询每个产品的库存数量
+ var productIds = data.list.Select(x => x.id).ToList();
+ if (productIds.Any())
+ {
+ var inventoryDict = await _db.Queryable()
+ .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .GroupBy(x => x.ProductId)
+ .Select(x => new { ProductId = x.ProductId, TotalQuantity = SqlFunc.AggregateSum((decimal?)x.Quantity) })
+ .ToListAsync();
+
+ var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0);
+
+ // 填充库存数量
+ foreach (var item in data.list)
+ {
+ item.currentInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0;
+ }
+ }
+
+ // 补充用户名称信息
+ var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser, x.departmentId })
+ .Where(x => !string.IsNullOrEmpty(x))
+ .Distinct()
+ .ToList();
+
+ if (userIds.Any())
+ {
+ var userList = await _db.Queryable()
+ .Where(x => userIds.Contains(x.Id))
+ .Select(x => new { x.Id, x.RealName })
+ .ToListAsync();
+ var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
+
+ foreach (var item in data.list)
+ {
+ if (!string.IsNullOrEmpty(item.createUser) && userDict.ContainsKey(item.createUser))
+ item.createUserName = userDict[item.createUser];
+ if (!string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser))
+ item.updateUserName = userDict[item.updateUser];
+ if (!string.IsNullOrEmpty(item.departmentId) && userDict.ContainsKey(item.departmentId))
+ item.departmentName = userDict[item.departmentId];
+ }
+ }
+
+ return PageResult.SqlSugarPageResult(data);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取产品列表失败");
+ throw NCCException.Oh($"获取产品列表失败:{ex.Message}");
+ }
+ }
+ #endregion
+
+ #region 获取产品详情
+ ///
+ /// 获取产品详情
+ ///
+ /// 产品ID
+ /// 产品详情
+ [HttpGet("GetInfo")]
+ public async Task GetInfoAsync([FromQuery] string id)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ throw NCCException.Oh("产品ID不能为空");
+ }
+
+ var product = await _db.Queryable()
+ .Where(x => x.Id == id)
+ .FirstAsync();
+
+ if (product == null)
+ {
+ throw NCCException.Oh("产品不存在");
+ }
+
+ var result = new LqProductInfoOutput
+ {
+ id = product.Id,
+ productName = product.ProductName,
+ price = product.Price,
+ productCategory = product.ProductCategory,
+ departmentId = product.DepartmentId,
+ departmentName = "",
+ standardUnit = product.StandardUnit,
+ onShelfStatus = product.OnShelfStatus,
+ statisticsCategory = product.StatisticsCategory,
+ warehouse = product.Warehouse,
+ unitConversion = product.UnitConversion,
+ supplierName = product.SupplierName,
+ contractSignDate = product.ContractSignDate,
+ contractEndDate = product.ContractEndDate,
+ remark = product.Remark,
+ createUser = product.CreateUser,
+ createUserName = "",
+ createTime = product.CreateTime,
+ updateUser = product.UpdateUser,
+ updateUserName = "",
+ updateTime = product.UpdateTime
+ };
+
+ // 补充用户名称信息
+ var userIds = new[] { result.createUser, result.updateUser, result.departmentId }
+ .Where(x => !string.IsNullOrEmpty(x))
+ .Distinct()
+ .ToList();
+
+ if (userIds.Any())
+ {
+ var userList = await _db.Queryable()
+ .Where(x => userIds.Contains(x.Id))
+ .Select(x => new { x.Id, x.RealName })
+ .ToListAsync();
+ var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
+
+ if (!string.IsNullOrEmpty(result.createUser) && userDict.ContainsKey(result.createUser))
+ result.createUserName = userDict[result.createUser];
+ if (!string.IsNullOrEmpty(result.updateUser) && userDict.ContainsKey(result.updateUser))
+ result.updateUserName = userDict[result.updateUser];
+ if (!string.IsNullOrEmpty(result.departmentId) && userDict.ContainsKey(result.departmentId))
+ result.departmentName = userDict[result.departmentId];
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取产品详情失败");
+ throw NCCException.Oh($"获取产品详情失败:{ex.Message}");
+ }
+ }
+ #endregion
+
+ #region 获取产品下拉列表
+ ///
+ /// 获取产品下拉列表
+ ///
+ ///
+ /// 用于下拉选择的产品列表,只返回上架的产品
+ ///
+ /// 示例请求:
+ /// ```
+ /// GET /api/Extend/LqProduct/GetSelectList?productName=产品
+ /// ```
+ ///
+ /// 产品名称(可选,模糊查询)
+ /// 上架状态(可选,1:上架 0:下架,默认1)
+ /// 产品下拉列表
+ /// 查询成功
+ /// 服务器错误
+ [HttpGet("GetSelectList")]
+ public async Task GetSelectListAsync([FromQuery] string productName = null, [FromQuery] int? onShelfStatus = 1)
+ {
+ try
+ {
+ var query = _db.Queryable()
+ .WhereIF(!string.IsNullOrWhiteSpace(productName), x => x.ProductName.Contains(productName))
+ .WhereIF(onShelfStatus.HasValue, x => x.OnShelfStatus == onShelfStatus.Value);
+
+ var products = await query
+ .Select(x => new LqProductSelectOutput
+ {
+ id = x.Id,
+ productName = x.ProductName,
+ price = x.Price,
+ standardUnit = x.StandardUnit,
+ currentInventory = 0 // 稍后计算
+ })
+ .ToListAsync();
+
+ // 批量查询每个产品的库存数量
+ var productIds = products.Select(x => x.id).ToList();
+ if (productIds.Any())
+ {
+ var inventoryDict = await _db.Queryable()
+ .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .GroupBy(x => x.ProductId)
+ .Select(x => new { ProductId = x.ProductId, TotalQuantity = SqlFunc.AggregateSum((decimal?)x.Quantity) })
+ .ToListAsync();
+
+ var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0);
+
+ // 填充库存数量
+ foreach (var item in products)
+ {
+ item.currentInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0;
+ }
+ }
+
+ return products;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取产品下拉列表失败");
+ throw NCCException.Oh($"获取产品下拉列表失败:{ex.Message}");
+ }
+ }
+ #endregion
+
+ #region 作废产品
+ ///
+ /// 作废产品
+ ///
+ ///
+ /// 作废产品,将产品的上架状态改为下架(OnShelfStatus = 0)
+ /// 注意:如果产品有已使用的库存,需要先检查是否允许作废
+ ///
+ /// 产品ID
+ /// 作废备注
+ /// 作废结果
+ /// 作废成功
+ /// 产品不存在或已被作废
+ /// 服务器错误
+ [HttpPut("Cancel/{id}")]
+ public async Task CancelAsync([FromRoute] string id, [FromQuery] string remarks = null)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ throw NCCException.Oh("产品ID不能为空");
+ }
+
+ var product = await _db.Queryable()
+ .Where(x => x.Id == id)
+ .FirstAsync();
+
+ if (product == null)
+ {
+ throw NCCException.Oh("产品不存在");
+ }
+
+ // 计算该产品的总库存数量(所有有效库存的总和)
+ var totalInventory = await _db.Queryable()
+ .Where(x => x.ProductId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .SumAsync(x => (int?)x.Quantity) ?? 0;
+
+ // 计算该产品的已使用数量
+ var totalUsage = await _db.Queryable()
+ .Where(x => x.ProductId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
+
+ // 作废后的总库存(作废后库存为0)
+ var inventoryAfterCancel = 0;
+
+ // 判断:如果已使用的数量超过了作废后的总数量,不允许作废
+ if (totalUsage > inventoryAfterCancel)
+ {
+ throw NCCException.Oh($"不允许作废该产品,已使用数量({totalUsage})超过了作废后的总数量({inventoryAfterCancel})");
+ }
+
+ // 作废产品(将上架状态改为下架)
+ product.OnShelfStatus = 0;
+ if (!string.IsNullOrEmpty(remarks))
+ {
+ product.Remark = string.IsNullOrEmpty(product.Remark)
+ ? $"作废备注:{remarks}"
+ : $"{product.Remark}\n作废备注:{remarks}";
+ }
+ product.UpdateUser = _userManager.UserId;
+ product.UpdateTime = DateTime.Now;
+
+ var isOk = await _db.Updateable(product).ExecuteCommandAsync();
+ if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "作废产品失败");
+ throw NCCException.Oh($"作废失败:{ex.Message}");
+ }
+ }
+ #endregion
+
+ #region 查看产品的所有库存和使用记录
+ ///
+ /// 查看产品的所有库存和使用记录
+ ///
+ ///
+ /// 返回产品的详细信息、所有库存记录和使用记录
+ ///
+ /// 示例请求:
+ /// ```
+ /// GET /api/Extend/LqProduct/GetInventoryDetail?productId=产品ID
+ /// ```
+ ///
+ /// 产品ID
+ /// 产品库存详情(包含库存列表和使用记录列表)
+ /// 查询成功
+ /// 产品不存在
+ /// 服务器错误
+ [HttpGet("GetInventoryDetail")]
+ public async Task GetInventoryDetailAsync([FromQuery] string productId)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(productId))
+ {
+ throw NCCException.Oh("产品ID不能为空");
+ }
+
+ // 获取产品信息
+ var productInfo = await GetInfoAsync(productId);
+ if (productInfo == null)
+ {
+ throw NCCException.Oh("产品不存在");
+ }
+
+ // 获取该产品的所有库存记录
+ var inventoryList = await _db.Queryable()
+ .Where(x => x.ProductId == productId)
+ .OrderBy(x => x.CreateTime, OrderByType.Desc)
+ .Select(x => new ProductInventoryItem
+ {
+ id = x.Id,
+ quantity = x.Quantity,
+ stockInTime = x.StockInTime,
+ productionDate = x.ProductionDate,
+ shelfLife = x.ShelfLife,
+ batchNumber = x.BatchNumber,
+ isEffective = x.IsEffective,
+ createTime = x.CreateTime
+ })
+ .ToListAsync();
+
+ // 获取该产品的所有使用记录
+ var usageList = await _db.Queryable(
+ (usage, store) => usage.StoreId == store.Id)
+ .Where((usage, store) => usage.ProductId == productId)
+ .OrderBy((usage, store) => usage.UsageTime, OrderByType.Desc)
+ .Select((usage, store) => new ProductUsageItem
+ {
+ id = usage.Id,
+ storeId = usage.StoreId,
+ storeName = store.Dm,
+ usageTime = usage.UsageTime,
+ usageQuantity = usage.UsageQuantity,
+ relatedConsumeId = usage.RelatedConsumeId,
+ createTime = usage.CreateTime,
+ isEffective = usage.IsEffective
+ })
+ .ToListAsync();
+
+ // 计算总库存、已使用数量、可用库存
+ var totalInventory = inventoryList.Where(x => x.isEffective == StatusEnum.有效.GetHashCode()).Sum(x => x.quantity);
+ var totalUsage = usageList.Where(x => x.isEffective == StatusEnum.有效.GetHashCode()).Sum(x => x.usageQuantity);
+ var availableInventory = totalInventory - totalUsage;
+
+ var result = new LqProductInventoryDetailOutput
+ {
+ productInfo = productInfo as LqProductInfoOutput,
+ inventoryList = inventoryList,
+ usageList = usageList,
+ totalInventory = totalInventory,
+ totalUsage = totalUsage,
+ availableInventory = availableInventory
+ };
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取产品库存详情失败");
+ throw NCCException.Oh($"获取产品库存详情失败:{ex.Message}");
+ }
+ }
+ #endregion
+
+ #region 产品上下架
+ ///
+ /// 产品上下架
+ ///
+ ///
+ /// 设置产品的上架状态,1表示上架,0表示下架
+ ///
+ /// 示例请求:
+ /// ```json
+ /// {
+ /// "productId": "产品ID",
+ /// "onShelfStatus": 1
+ /// }
+ /// ```
+ ///
+ /// 参数说明:
+ /// - productId: 产品ID(必填)
+ /// - onShelfStatus: 上架状态,1表示上架,0表示下架(必填)
+ ///
+ /// 上下架输入
+ /// 操作结果
+ /// 操作成功
+ /// 产品不存在或参数错误
+ /// 服务器错误
+ [HttpPut("ToggleShelf")]
+ public async Task ToggleShelfAsync([FromBody] LqProductToggleShelfInput input)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(input.ProductId))
+ {
+ throw NCCException.Oh("产品ID不能为空");
+ }
+
+ if (input.OnShelfStatus != 0 && input.OnShelfStatus != 1)
+ {
+ throw NCCException.Oh("上架状态只能是0(下架)或1(上架)");
+ }
+
+ var product = await _db.Queryable()
+ .Where(x => x.Id == input.ProductId)
+ .FirstAsync();
+
+ if (product == null)
+ {
+ throw NCCException.Oh("产品不存在");
+ }
+
+ // 更新上架状态
+ product.OnShelfStatus = input.OnShelfStatus;
+ product.UpdateUser = _userManager.UserId;
+ product.UpdateTime = DateTime.Now;
+
+ var isOk = await _db.Updateable(product)
+ .UpdateColumns(x => new { x.OnShelfStatus, x.UpdateUser, x.UpdateTime })
+ .ExecuteCommandAsync();
+
+ if (!(isOk > 0))
+ {
+ throw NCCException.Oh(ErrorCode.COM1000);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "产品上下架操作失败");
+ throw NCCException.Oh($"产品上下架操作失败:{ex.Message}");
+ }
+ }
+ #endregion
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
index 0e09053..d2b781a 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
@@ -3512,39 +3512,59 @@ namespace NCC.Extend.LqStatistics
}
///
- /// 统计人头(月度去重客户数)
+ /// 统计人头(从人次记录表统计,按健康师+月份+客户+数量去重后累加)
///
private async Task GetHeadCount(string userId, string month)
{
+ // 按健康师+月份+客户+数量去重,然后累加数量
+ // 注意:userId 和 month 都是内部参数,相对安全
var sql = $@"
- SELECT COUNT(DISTINCT hyhk.hy) as Count
- FROM lq_xh_jksyj jksyj
- INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
- WHERE jksyj.jkszh = '{userId}'
- AND jksyj.F_IsEffective = 1
- AND hyhk.F_IsEffective = 1
- AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '{month}'";
+ SELECT COALESCE(SUM(F_Quantity), 0) as Count
+ FROM (
+ SELECT
+ F_PersonId,
+ F_WorkMonth,
+ F_MemberId,
+ F_Quantity
+ FROM lq_person_times_record
+ WHERE F_PersonId = '{userId}'
+ AND F_PersonType = '健康师'
+ AND F_WorkMonth = '{month}'
+ AND F_IsEffective = 1
+ GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity
+ ) as distinct_records";
var result = await _db.Ado.SqlQueryAsync(sql);
- return Convert.ToInt32(result.FirstOrDefault()?.Count ?? 0);
+ var count = result.FirstOrDefault()?.Count;
+ return count != null ? Convert.ToInt32(count) : 0;
}
///
- /// 统计人次(日度去重客户数)
+ /// 统计人次(从人次记录表统计,按健康师+日期+客户+数量去重后累加)
///
private async Task GetPersonCount(string userId, string month)
{
+ // 按健康师+日期+客户+数量去重,然后累加数量
+ // 注意:userId 和 month 都是内部参数,相对安全
var sql = $@"
- SELECT COUNT(DISTINCT CONCAT(hyhk.hy, '-', DATE(hyhk.hksj))) as Count
- FROM lq_xh_jksyj jksyj
- INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
- WHERE jksyj.jkszh = '{userId}'
- AND jksyj.F_IsEffective = 1
- AND hyhk.F_IsEffective = 1
- AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '{month}'";
+ SELECT COALESCE(SUM(F_Quantity), 0) as Count
+ FROM (
+ SELECT
+ F_PersonId,
+ F_WorkDate,
+ F_MemberId,
+ F_Quantity
+ FROM lq_person_times_record
+ WHERE F_PersonId = '{userId}'
+ AND F_PersonType = '健康师'
+ AND F_WorkMonth = '{month}'
+ AND F_IsEffective = 1
+ GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity
+ ) as distinct_records";
var result = await _db.Ado.SqlQueryAsync(sql);
- return Convert.ToInt32(result.FirstOrDefault()?.Count ?? 0);
+ var count = result.FirstOrDefault()?.Count;
+ return count != null ? Convert.ToInt32(count) : 0;
}
///
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
index 74b003c..df2e9a2 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
@@ -1003,10 +1003,12 @@ namespace NCC.Extend.LqXhHyhk
}
}
}
+ // 剔除所有T区健康师(姓名中包含"T区"的健康师)
+ var NoTallJksyjEntities = allJksyjEntities.Where(x => x.Jks != null && x.Jks != "" && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList();
//获取这次耗卡有多少个健康师
- var jksCount = allJksyjEntities.GroupBy(x => x.Jks).Count();
+ var jksCount = NoTallJksyjEntities.GroupBy(x => x.Jks).Count();
//添加到人次表里面去
- foreach (var item in allJksyjEntities.Select(x => x.Jks).Distinct())
+ foreach (var item in NoTallJksyjEntities.Select(x => x.Jks).Distinct())
{
LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
{
@@ -1015,7 +1017,7 @@ namespace NCC.Extend.LqXhHyhk
BusinessType = "耗卡",
PersonType = "健康师",
PersonId = item,
- PersonName = allJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(),
+ PersonName = NoTallJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(),
MemberId = entity.Hy,
MemberName = memberInfo.Khmc,
WorkDate = input.hksj,
@@ -1026,10 +1028,12 @@ namespace NCC.Extend.LqXhHyhk
};
allPersonTimesRecordEntities.Add(personTimesRecordEntity);
}
+ //剔除所有T区科技部老师(姓名中包含"T区"的科技部老师)
+ var NoTallKjbsyjEntities = allKjbsyjEntities.Where(x => x.Kjbls != null && x.Kjbls != "" && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList();
//获取这次耗卡有多少个科技部老师
- var kjbCount = allKjbsyjEntities.GroupBy(x => x.Kjbls).Count();
+ var kjbCount = NoTallKjbsyjEntities.GroupBy(x => x.Kjbls).Count();
//添加到人次表里面去
- foreach (var item in allKjbsyjEntities.Select(x => x.Kjbls).Distinct())
+ foreach (var item in NoTallKjbsyjEntities.Select(x => x.Kjbls).Distinct())
{
LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
{
@@ -1038,7 +1042,7 @@ namespace NCC.Extend.LqXhHyhk
BusinessType = "耗卡",
PersonType = "科技部老师",
PersonId = item,
- PersonName = allKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(),
+ PersonName = NoTallKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(),
MemberId = entity.Hy,
MemberName = memberInfo.Khmc,
WorkDate = input.hksj,
@@ -1360,10 +1364,12 @@ namespace NCC.Extend.LqXhHyhk
}
}
}
+ //剔除所有T区健康师(姓名中包含"T区"的健康师)
+ var NoTallJksyjEntities = allJksyjEntities.Where(x => x.Jks != null && x.Jks != "" && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList();
//获取这次耗卡有多少个健康师
- var jksCount = allJksyjEntities.GroupBy(x => x.Jks).Count();
+ var jksCount = NoTallJksyjEntities.GroupBy(x => x.Jks).Count();
//添加到人次表里面去
- foreach (var item in allJksyjEntities.Select(x => x.Jks).Distinct())
+ foreach (var item in NoTallJksyjEntities.Select(x => x.Jks).Distinct())
{
LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
{
@@ -1372,7 +1378,7 @@ namespace NCC.Extend.LqXhHyhk
BusinessType = "耗卡",
PersonType = "健康师",
PersonId = item,
- PersonName = allJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(),
+ PersonName = NoTallJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(),
MemberId = entity.Hy,
MemberName = memberInfo.Khmc,
WorkDate = input.hksj,
@@ -1383,10 +1389,12 @@ namespace NCC.Extend.LqXhHyhk
};
allPersonTimesRecordEntities.Add(personTimesRecordEntity);
}
+ //剔除所有T区科技部老师(姓名中包含"T区"的科技部老师)
+ var NoTallKjbsyjEntities = allKjbsyjEntities.Where(x => x.Kjbls != null && x.Kjbls != "" && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList();
//获取这次耗卡有多少个科技部老师
- var kjbCount = allKjbsyjEntities.GroupBy(x => x.Kjbls).Count();
+ var kjbCount = NoTallKjbsyjEntities.GroupBy(x => x.Kjbls).Count();
//添加到人次表里面去
- foreach (var item in allKjbsyjEntities.Select(x => x.Kjbls).Distinct())
+ foreach (var item in NoTallKjbsyjEntities.Select(x => x.Kjbls).Distinct())
{
LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
{
@@ -1395,7 +1403,7 @@ namespace NCC.Extend.LqXhHyhk
BusinessType = "耗卡",
PersonType = "科技部老师",
PersonId = item,
- PersonName = allKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(),
+ PersonName = NoTallKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(),
MemberId = entity.Hy,
MemberName = memberInfo.Khmc,
WorkDate = input.hksj,
@@ -1677,5 +1685,214 @@ namespace NCC.Extend.LqXhHyhk
}
#endregion
+ #region 同步耗卡数据到人次记录表(同步的时候才用)
+ ///
+ /// 同步耗卡数据到人次记录表(同步的时候才用)
+ ///
+ ///
+ /// 将耗卡记录中的健康师和科技老师业绩数据同步到人次记录表(lq_person_times_record)
+ ///
+ /// 同步逻辑:
+ /// 1. 查询所有有效的耗卡记录
+ /// 2. 对于每条耗卡记录,查询关联的健康师业绩和科技老师业绩
+ /// 3. 对健康师和科技老师进行去重(按人员ID)
+ /// 4. 为每个去重后的健康师和科技老师创建人次记录
+ /// 5. 计算人次数量:1 / 健康师(或科技老师)数量
+ ///
+ /// 字段映射:
+ /// - F_BusinessId: 耗卡ID(lq_xh_hyhk.F_Id)
+ /// - F_BusinessType: "耗卡"
+ /// - F_PersonType: "健康师" 或 "科技老师"
+ /// - F_PersonId: 健康师ID(lq_xh_jksyj.jks)或科技老师ID(lq_xh_kjbsyj.kjbls)
+ /// - F_PersonName: 健康师姓名(lq_xh_jksyj.jksxm)或科技老师姓名(lq_xh_kjbsyj.kjblsxm)
+ /// - F_MemberId: 客户ID(lq_xh_hyhk.hy)
+ /// - F_MemberName: 客户姓名(lq_xh_hyhk.hymc)
+ /// - F_WorkDate: 耗卡时间的日期部分(lq_xh_hyhk.hksj)
+ /// - F_WorkMonth: 耗卡时间的月份部分,格式:YYYYMM(lq_xh_hyhk.hksj)
+ /// - F_Quantity: 人次数量 = 1 / 健康师(或科技老师)数量
+ ///
+ /// 注意事项:
+ /// - 只同步有效的耗卡记录(F_IsEffective = 1)
+ /// - 只同步有效的健康师业绩和科技老师业绩(F_IsEffective = 1)
+ /// - 如果已存在相同业务ID的记录,会先删除再重新插入
+ ///
+ /// 可选,指定耗卡ID,如果为空则同步所有耗卡数据
+ /// 同步结果,包含同步的记录数
+ /// 同步成功
+ /// 服务器内部错误
+ [HttpPost("sync-person-times-record")]
+ public async Task SyncPersonTimesRecord([FromQuery] string consumeId = null)
+ {
+ try
+ {
+ _logger.LogInformation($"开始同步耗卡数据到人次记录表,耗卡ID: {consumeId ?? "全部"}");
+ // 1. 查询耗卡记录
+ var consumeQuery = _db.Queryable().Where(x => x.IsEffective == StatusEnum.有效.GetHashCode());
+ if (!string.IsNullOrEmpty(consumeId))
+ {
+ consumeQuery = consumeQuery.Where(x => x.Id == consumeId);
+ }
+ var consumeList = await consumeQuery.ToListAsync();
+ if (!consumeList.Any())
+ {
+ return new { success = true, message = "没有需要同步的耗卡记录", count = 0 };
+ }
+ _logger.LogInformation($"找到 {consumeList.Count} 条耗卡记录需要同步");
+ // 2. 批量查询关联数据
+ var consumeIds = consumeList.Select(x => x.Id).ToList();
+ // 查询健康师业绩
+ var jksyjList = await _db.Queryable().Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
+
+ // 查询科技老师业绩
+ var kjbsyjList = await _db.Queryable().Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
+
+ // 查询已存在的人次记录(按BusinessId去重,避免重复添加)
+ var existingBusinessIds = await _db.Queryable()
+ .Where(x => consumeIds.Contains(x.BusinessId) && x.BusinessType == "耗卡" && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .Select(x => x.BusinessId)
+ .Distinct()
+ .ToListAsync();
+
+ // 3. 构建人次记录列表
+ var personTimesRecords = new List();
+
+ foreach (var consume in consumeList)
+ {
+ // 如果该耗卡记录已存在,跳过
+ if (existingBusinessIds.Contains(consume.Id))
+ {
+ _logger.LogInformation($"耗卡记录 {consume.Id} 已存在人次记录,跳过");
+ continue;
+ }
+
+ if (consume.Hksj == null)
+ {
+ _logger.LogWarning($"耗卡记录 {consume.Id} 的耗卡时间为空,跳过");
+ continue;
+ }
+ var workDate = consume.Hksj.Value.Date; // 工作日期(用于人次统计)
+ var workMonth = consume.Hksj.Value.ToString("yyyyMM"); // 工作月份(用于人头统计)
+
+ // 处理健康师业绩:去重后计算人次数量(剔除T区健康师)
+ var consumeJksyjList = jksyjList.Where(x => x.Glkdbh == consume.Id
+ && !string.IsNullOrEmpty(x.Jks)
+ && !string.IsNullOrEmpty(x.Jksxm)
+ && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList();
+ if (consumeJksyjList.Any())
+ {
+ // 按健康师ID去重
+ var distinctJksyjList = consumeJksyjList
+ .GroupBy(x => x.Jks)
+ .Select(g => g.First())
+ .ToList();
+
+ // 计算人次数量:1 / 健康师数量
+ var jksQuantity = distinctJksyjList.Count > 0 ? 1.0m / distinctJksyjList.Count : 0;
+
+ foreach (var jksyj in distinctJksyjList)
+ {
+ personTimesRecords.Add(new LqPersonTimesRecordEntity
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ BusinessId = consume.Id,
+ BusinessType = "耗卡",
+ PersonType = "健康师",
+ PersonId = jksyj.Jks,
+ PersonName = jksyj.Jksxm,
+ MemberId = consume.Hy,
+ MemberName = consume.Hymc,
+ WorkDate = workDate,
+ WorkMonth = workMonth,
+ Quantity = jksQuantity,
+ CreateTime = DateTime.Now,
+ IsEffective = StatusEnum.有效.GetHashCode()
+ });
+ }
+ }
+
+ // 处理科技老师业绩:去重后计算人次数量(剔除T区科技老师)
+ var consumeKjbsyjList = kjbsyjList.Where(x => x.Glkdbh == consume.Id
+ && !string.IsNullOrEmpty(x.Kjbls)
+ && !string.IsNullOrEmpty(x.Kjblsxm)
+ && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList();
+ if (consumeKjbsyjList.Any())
+ {
+ // 按科技老师ID去重
+ var distinctKjbsyjList = consumeKjbsyjList
+ .GroupBy(x => x.Kjbls)
+ .Select(g => g.First())
+ .ToList();
+
+ // 计算人次数量:1 / 科技老师数量
+ var kjbsQuantity = distinctKjbsyjList.Count > 0 ? 1.0m / distinctKjbsyjList.Count : 0;
+
+ foreach (var kjbsyj in distinctKjbsyjList)
+ {
+ personTimesRecords.Add(new LqPersonTimesRecordEntity
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ BusinessId = consume.Id,
+ BusinessType = "耗卡",
+ PersonType = "科技老师",
+ PersonId = kjbsyj.Kjbls,
+ PersonName = kjbsyj.Kjblsxm,
+ MemberId = consume.Hy,
+ MemberName = consume.Hymc,
+ WorkDate = workDate,
+ WorkMonth = workMonth,
+ Quantity = kjbsQuantity,
+ CreateTime = DateTime.Now,
+ IsEffective = StatusEnum.有效.GetHashCode()
+ });
+ }
+ }
+ }
+
+ // 4. 使用事务保存数据
+ var result = await _db.Ado.UseTranAsync(async () =>
+ {
+ // 如果指定了耗卡ID,删除该耗卡的旧记录(用于重新同步)
+ // 如果没有指定耗卡ID,不删除任何记录(因为已经在构建记录列表时跳过了已存在的记录)
+ if (!string.IsNullOrEmpty(consumeId))
+ {
+ await _db.Deleteable()
+ .Where(x => x.BusinessId == consumeId && x.BusinessType == "耗卡")
+ .ExecuteCommandAsync();
+ }
+
+ // 批量插入新记录
+ if (personTimesRecords.Any())
+ {
+ // 分批插入,每批1000条
+ var batchSize = 1000;
+ for (int i = 0; i < personTimesRecords.Count; i += batchSize)
+ {
+ var batch = personTimesRecords.Skip(i).Take(batchSize).ToList();
+ await _db.Insertable(batch).ExecuteCommandAsync();
+ }
+ }
+
+ return personTimesRecords.Count;
+ });
+
+ if (result.IsSuccess)
+ {
+ _logger.LogInformation($"同步完成,共同步 {result.Data} 条人次记录");
+ return new { success = true, message = "同步成功", count = result.Data };
+ }
+ else
+ {
+ _logger.LogError($"同步失败: {result.ErrorMessage}");
+ throw NCCException.Oh(ErrorCode.COM1000, $"同步失败: {result.ErrorMessage}");
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"同步耗卡数据到人次记录表失败: {ex.ToString()}");
+ throw NCCException.Oh(ErrorCode.COM1005, $"同步耗卡数据到人次记录表失败: {ex.Message}");
+ }
+ }
+ #endregion
+
}
}
diff --git a/sql/拆分库存表为产品表和库存表.sql b/sql/拆分库存表为产品表和库存表.sql
new file mode 100644
index 0000000..7ee893c
--- /dev/null
+++ b/sql/拆分库存表为产品表和库存表.sql
@@ -0,0 +1,81 @@
+-- ============================================
+-- 拆分库存表为产品表和库存表
+-- ============================================
+-- 说明:将 lq_inventory 表拆分为 lq_product(产品表)和 lq_inventory(库存表)
+--
+-- 注意:此脚本只创建新表,不迁移数据
+-- ============================================
+
+-- ============================================
+-- 1. 创建产品表(lq_product)
+-- ============================================
+CREATE TABLE IF NOT EXISTS `lq_product` (
+ `F_Id` VARCHAR(50) NOT NULL COMMENT '产品ID',
+ `F_ProductName` VARCHAR(200) NULL COMMENT '产品名称',
+ `F_Price` DECIMAL(18,2) NULL DEFAULT 0 COMMENT '价格',
+ `F_ProductCategory` VARCHAR(50) NULL COMMENT '产品类别',
+ `F_DepartmentId` VARCHAR(50) NULL COMMENT '归属部门ID',
+ `F_StandardUnit` VARCHAR(20) NULL COMMENT '标准单位',
+ `F_OnShelfStatus` INT NULL DEFAULT 1 COMMENT '上架状态(1:上架 0:下架)',
+ `F_StatisticsCategory` VARCHAR(50) NULL COMMENT '统计分类',
+ `F_Warehouse` VARCHAR(100) NULL COMMENT '归属仓库',
+ `F_UnitConversion` VARCHAR(200) NULL COMMENT '单位换算',
+ `F_SupplierName` VARCHAR(200) NULL COMMENT '供应商名称',
+ `F_ContractSignDate` DATETIME NULL COMMENT '合同签订日期',
+ `F_ContractEndDate` DATETIME NULL COMMENT '合同结束日期',
+ `F_Remark` VARCHAR(1000) NULL COMMENT '备注',
+ `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID',
+ `F_CreateTime` DATETIME NULL COMMENT '创建时间',
+ `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID',
+ `F_UpdateTime` DATETIME NULL COMMENT '更新时间',
+ PRIMARY KEY (`F_Id`),
+ INDEX `idx_product_category` (`F_ProductCategory`) COMMENT '产品类别索引',
+ INDEX `idx_department` (`F_DepartmentId`) COMMENT '部门索引',
+ INDEX `idx_onshelf_status` (`F_OnShelfStatus`) COMMENT '上架状态索引'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品表';
+
+-- ============================================
+-- 2. 修改库存表结构(lq_inventory)
+-- ============================================
+-- 注意:MySQL不支持 IF NOT EXISTS 语法,需要先检查字段是否存在
+-- 如果字段已存在,执行对应的ALTER语句会报错,可以忽略
+
+-- 2.1 添加产品ID字段
+-- 如果字段已存在,会报错,可以忽略
+ALTER TABLE `lq_inventory`
+ADD COLUMN `F_ProductId` VARCHAR(50) NULL COMMENT '产品ID(关联产品表)' AFTER `F_Id`;
+
+-- 2.2 添加新字段
+-- 如果字段已存在,会报错,可以忽略
+ALTER TABLE `lq_inventory`
+ADD COLUMN `F_StockInTime` DATETIME NULL COMMENT '入库时间' AFTER `F_Quantity`;
+
+ALTER TABLE `lq_inventory`
+ADD COLUMN `F_ProductionDate` DATETIME NULL COMMENT '生产日期' AFTER `F_StockInTime`;
+
+ALTER TABLE `lq_inventory`
+ADD COLUMN `F_ShelfLife` INT NULL COMMENT '保质期(天数)' AFTER `F_ProductionDate`;
+
+ALTER TABLE `lq_inventory`
+ADD COLUMN `F_BatchNumber` VARCHAR(100) NULL COMMENT '批次号' AFTER `F_ShelfLife`;
+
+-- 2.3 删除产品相关字段(可选,执行前请先备份数据)
+-- 注意:如果字段不存在,会报错,可以忽略
+-- ALTER TABLE `lq_inventory` DROP COLUMN `F_ProductName`;
+-- ALTER TABLE `lq_inventory` DROP COLUMN `F_Price`;
+-- ALTER TABLE `lq_inventory` DROP COLUMN `F_ProductCategory`;
+-- ALTER TABLE `lq_inventory` DROP COLUMN `F_DepartmentId`;
+-- ALTER TABLE `lq_inventory` DROP COLUMN `F_StandardUnit`;
+
+-- 2.4 添加产品ID索引
+-- 如果索引已存在,会报错,可以忽略
+ALTER TABLE `lq_inventory`
+ADD INDEX `idx_product_id` (`F_ProductId`) COMMENT '产品ID索引';
+
+-- ============================================
+-- 注意事项
+-- ============================================
+-- 1. 此脚本只创建新表,不迁移数据
+-- 2. 如果需要删除旧字段,请先备份数据
+-- 3. 建议在测试环境先验证
+-- 4. 执行ALTER语句前,请确认表结构