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语句前,请确认表结构