Commit b5ed92da95acbcdd4dd7676b53063b842c51690d

Authored by “wangming”
1 parent e701f6b4

feat: 修复转卡接口和开单品项明细查询接口问题

- 修复GetItemRemainingCount方法中SumAsync返回null的问题
- 修复billing-item-detail-list接口日期参数解析问题,支持startTime/endTime参数
- 新增清洗管理功能:门店消耗品库存、清洗商、清洗流水管理
- 新增只买了女神卡的会员统计功能
- 优化统计接口,使用SqlSugar ORM替代原始SQL
Showing 43 changed files with 3838 additions and 192 deletions
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchCreateInput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +using System.ComponentModel.DataAnnotations;
  4 +
  5 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
  6 +{
  7 + /// <summary>
  8 + /// 库存使用记录批量创建输入
  9 + /// </summary>
  10 + public class LqInventoryUsageBatchCreateInput
  11 + {
  12 + /// <summary>
  13 + /// 使用记录列表
  14 + /// </summary>
  15 + [Required(ErrorMessage = "使用记录列表不能为空")]
  16 + [MinLength(1, ErrorMessage = "至少需要添加一条使用记录")]
  17 + [Display(Name = "使用记录列表")]
  18 + public List<LqInventoryUsageItemInput> UsageItems { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 使用批次ID(可选,不传则自动生成)
  22 + /// </summary>
  23 + [StringLength(50, ErrorMessage = "批次ID长度不能超过50个字符")]
  24 + [Display(Name = "使用批次ID")]
  25 + public string BatchId { get; set; }
  26 + }
  27 +
  28 + /// <summary>
  29 + /// 库存使用记录项输入
  30 + /// </summary>
  31 + public class LqInventoryUsageItemInput
  32 + {
  33 + /// <summary>
  34 + /// 产品ID
  35 + /// </summary>
  36 + [Required(ErrorMessage = "产品ID不能为空")]
  37 + [StringLength(50, ErrorMessage = "产品ID长度不能超过50个字符")]
  38 + [Display(Name = "产品ID")]
  39 + public string ProductId { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 门店ID
  43 + /// </summary>
  44 + [Required(ErrorMessage = "门店ID不能为空")]
  45 + [StringLength(50, ErrorMessage = "门店ID长度不能超过50个字符")]
  46 + [Display(Name = "门店ID")]
  47 + public string StoreId { get; set; }
  48 +
  49 + /// <summary>
  50 + /// 使用时间
  51 + /// </summary>
  52 + [Required(ErrorMessage = "使用时间不能为空")]
  53 + [Display(Name = "使用时间")]
  54 + public DateTime UsageTime { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 使用数量
  58 + /// </summary>
  59 + [Required(ErrorMessage = "使用数量不能为空")]
  60 + [Range(1, int.MaxValue, ErrorMessage = "使用数量必须大于0")]
  61 + [Display(Name = "使用数量")]
  62 + public int UsageQuantity { get; set; }
  63 +
  64 + /// <summary>
  65 + /// 关联消耗ID(可选)
  66 + /// </summary>
  67 + [StringLength(50, ErrorMessage = "关联消耗ID长度不能超过50个字符")]
  68 + [Display(Name = "关联消耗ID")]
  69 + public string RelatedConsumeId { get; set; }
  70 + }
  71 +}
  72 +
  73 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchCreateOutput.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
  4 +{
  5 + /// <summary>
  6 + /// 库存使用记录批量创建输出
  7 + /// </summary>
  8 + public class LqInventoryUsageBatchCreateOutput
  9 + {
  10 + /// <summary>
  11 + /// 批次ID
  12 + /// </summary>
  13 + public string BatchId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 成功创建的数量
  17 + /// </summary>
  18 + public int SuccessCount { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 失败的数量
  22 + /// </summary>
  23 + public int FailCount { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 创建成功的记录ID列表
  27 + /// </summary>
  28 + public List<string> SuccessIds { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 失败详情列表
  32 + /// </summary>
  33 + public List<BatchCreateFailItem> FailItems { get; set; }
  34 + }
  35 +
  36 + /// <summary>
  37 + /// 批量创建失败项
  38 + /// </summary>
  39 + public class BatchCreateFailItem
  40 + {
  41 + /// <summary>
  42 + /// 失败索引(对应输入列表的索引)
  43 + /// </summary>
  44 + public int Index { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 产品ID
  48 + /// </summary>
  49 + public string ProductId { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 失败原因
  53 + /// </summary>
  54 + public string Reason { get; set; }
  55 + }
  56 +}
  57 +
  58 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchInfoOutput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
  5 +{
  6 + /// <summary>
  7 + /// 库存使用记录批次信息输出
  8 + /// </summary>
  9 + public class LqInventoryUsageBatchInfoOutput
  10 + {
  11 + /// <summary>
  12 + /// 批次ID
  13 + /// </summary>
  14 + public string BatchId { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 创建时间
  18 + /// </summary>
  19 + public DateTime CreateTime { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 创建人ID
  23 + /// </summary>
  24 + public string CreateUser { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 创建人姓名
  28 + /// </summary>
  29 + public string CreateUserName { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 记录总数
  33 + /// </summary>
  34 + public int TotalCount { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 有效记录数
  38 + /// </summary>
  39 + public int EffectiveCount { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 无效记录数
  43 + /// </summary>
  44 + public int IneffectiveCount { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 使用总数量
  48 + /// </summary>
  49 + public int TotalUsageQuantity { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 使用总金额
  53 + /// </summary>
  54 + public decimal TotalUsageAmount { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 使用记录列表
  58 + /// </summary>
  59 + public List<LqInventoryUsageListOutput> UsageRecords { get; set; }
  60 + }
  61 +}
  62 +
  63 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs
... ... @@ -96,5 +96,10 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
96 96 /// 使用总价值
97 97 /// </summary>
98 98 public decimal usageTotalValue { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 使用批次ID(同一批次申请的使用记录使用相同的批次ID)
  102 + /// </summary>
  103 + public string usageBatchId { get; set; }
99 104 }
100 105 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListQueryInput.cs
... ... @@ -51,5 +51,10 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
51 51 /// 是否有效
52 52 /// </summary>
53 53 public int? IsEffective { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 使用批次ID(用于查询同一批次的所有记录)
  57 + /// </summary>
  58 + public string UsageBatchId { get; set; }
54 59 }
55 60 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListQueryInput.cs
... ... @@ -28,6 +28,16 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
28 28 public string EndBillingTime { get; set; }
29 29  
30 30 /// <summary>
  31 + /// 开始时间(兼容参数名)
  32 + /// </summary>
  33 + public string startTime { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 结束时间(兼容参数名)
  37 + /// </summary>
  38 + public string endTime { get; set; }
  39 +
  40 + /// <summary>
31 41 /// 营销活动ID
32 42 /// </summary>
33 43 public string ActivityId { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LaundryStatisticsInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
  4 +{
  5 + /// <summary>
  6 + /// 清洗费用统计输入
  7 + /// </summary>
  8 + public class LaundryStatisticsInput
  9 + {
  10 + /// <summary>
  11 + /// 开始月份(格式:YYYYMM,如202411)
  12 + /// </summary>
  13 + [Display(Name = "开始月份")]
  14 + public string StartMonth { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 结束月份(格式:YYYYMM,如202412)
  18 + /// </summary>
  19 + [Display(Name = "结束月份")]
  20 + public string EndMonth { get; set; }
  21 +
  22 + /// <summary>
  23 + /// 门店ID(可选,门店统计时使用)
  24 + /// </summary>
  25 + [Display(Name = "门店ID")]
  26 + public string StoreId { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 产品类型(可选,产品统计时使用)
  30 + /// </summary>
  31 + [Display(Name = "产品类型")]
  32 + public string ProductType { get; set; }
  33 + }
  34 +}
  35 +
  36 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowDifferenceOutput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
  5 +{
  6 + /// <summary>
  7 + /// 清洗流水差异输出(送出数量 > 送回数量)
  8 + /// </summary>
  9 + public class LqLaundryFlowDifferenceOutput
  10 + {
  11 + /// <summary>
  12 + /// 批次号
  13 + /// </summary>
  14 + [Display(Name = "批次号")]
  15 + public string batchNumber { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 门店ID
  19 + /// </summary>
  20 + [Display(Name = "门店ID")]
  21 + public string storeId { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 门店名称
  25 + /// </summary>
  26 + [Display(Name = "门店名称")]
  27 + public string storeName { get; set; }
  28 +
  29 + /// <summary>
  30 + /// 产品类型
  31 + /// </summary>
  32 + [Display(Name = "产品类型")]
  33 + public string productType { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 送出数量
  37 + /// </summary>
  38 + [Display(Name = "送出数量")]
  39 + public int sendQuantity { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 送回数量
  43 + /// </summary>
  44 + [Display(Name = "送回数量")]
  45 + public int returnQuantity { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 差异数量
  49 + /// </summary>
  50 + [Display(Name = "差异数量")]
  51 + public int differenceQuantity { get; set; }
  52 +
  53 + /// <summary>
  54 + /// 差异说明(备注)
  55 + /// </summary>
  56 + [Display(Name = "差异说明")]
  57 + public string differenceRemark { get; set; }
  58 +
  59 + /// <summary>
  60 + /// 送出时间
  61 + /// </summary>
  62 + [Display(Name = "送出时间")]
  63 + public DateTime? sendTime { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 送回时间
  67 + /// </summary>
  68 + [Display(Name = "送回时间")]
  69 + public DateTime? returnTime { get; set; }
  70 + }
  71 +}
  72 +
  73 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowInfoOutput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
  5 +{
  6 + /// <summary>
  7 + /// 清洗流水详情输出
  8 + /// </summary>
  9 + public class LqLaundryFlowInfoOutput
  10 + {
  11 + /// <summary>
  12 + /// 主键ID
  13 + /// </summary>
  14 + [Display(Name = "ID")]
  15 + public string id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 流水类型(0:送出 1:送回)
  19 + /// </summary>
  20 + [Display(Name = "流水类型")]
  21 + public int flowType { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 流水类型名称
  25 + /// </summary>
  26 + [Display(Name = "流水类型名称")]
  27 + public string flowTypeName { get; set; }
  28 +
  29 + /// <summary>
  30 + /// 批次号
  31 + /// </summary>
  32 + [Display(Name = "批次号")]
  33 + public string batchNumber { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 门店ID
  37 + /// </summary>
  38 + [Display(Name = "门店ID")]
  39 + public string storeId { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 门店名称
  43 + /// </summary>
  44 + [Display(Name = "门店名称")]
  45 + public string storeName { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 产品类型
  49 + /// </summary>
  50 + [Display(Name = "产品类型")]
  51 + public string productType { get; set; }
  52 +
  53 + /// <summary>
  54 + /// 清洗商ID
  55 + /// </summary>
  56 + [Display(Name = "清洗商ID")]
  57 + public string laundrySupplierId { get; set; }
  58 +
  59 + /// <summary>
  60 + /// 清洗商名称
  61 + /// </summary>
  62 + [Display(Name = "清洗商名称")]
  63 + public string laundrySupplierName { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 数量
  67 + /// </summary>
  68 + [Display(Name = "数量")]
  69 + public int quantity { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 清洗单价
  73 + /// </summary>
  74 + [Display(Name = "清洗单价")]
  75 + public decimal laundryPrice { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 总费用
  79 + /// </summary>
  80 + [Display(Name = "总费用")]
  81 + public decimal totalPrice { get; set; }
  82 +
  83 + /// <summary>
  84 + /// 备注
  85 + /// </summary>
  86 + [Display(Name = "备注")]
  87 + public string remark { get; set; }
  88 +
  89 + /// <summary>
  90 + /// 是否有效
  91 + /// </summary>
  92 + [Display(Name = "是否有效")]
  93 + public int isEffective { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 创建人ID
  97 + /// </summary>
  98 + [Display(Name = "创建人ID")]
  99 + public string createUser { get; set; }
  100 +
  101 + /// <summary>
  102 + /// 创建人姓名
  103 + /// </summary>
  104 + [Display(Name = "创建人姓名")]
  105 + public string createUserName { get; set; }
  106 +
  107 + /// <summary>
  108 + /// 创建时间
  109 + /// </summary>
  110 + [Display(Name = "创建时间")]
  111 + public DateTime createTime { get; set; }
  112 + }
  113 +}
  114 +
  115 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowListOutput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
  5 +{
  6 + /// <summary>
  7 + /// 清洗流水列表输出
  8 + /// </summary>
  9 + public class LqLaundryFlowListOutput
  10 + {
  11 + /// <summary>
  12 + /// 主键ID
  13 + /// </summary>
  14 + [Display(Name = "ID")]
  15 + public string id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 流水类型(0:送出 1:送回)
  19 + /// </summary>
  20 + [Display(Name = "流水类型")]
  21 + public int flowType { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 流水类型名称
  25 + /// </summary>
  26 + [Display(Name = "流水类型名称")]
  27 + public string flowTypeName { get; set; }
  28 +
  29 + /// <summary>
  30 + /// 批次号
  31 + /// </summary>
  32 + [Display(Name = "批次号")]
  33 + public string batchNumber { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 门店ID
  37 + /// </summary>
  38 + [Display(Name = "门店ID")]
  39 + public string storeId { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 门店名称
  43 + /// </summary>
  44 + [Display(Name = "门店名称")]
  45 + public string storeName { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 产品类型
  49 + /// </summary>
  50 + [Display(Name = "产品类型")]
  51 + public string productType { get; set; }
  52 +
  53 + /// <summary>
  54 + /// 清洗商ID
  55 + /// </summary>
  56 + [Display(Name = "清洗商ID")]
  57 + public string laundrySupplierId { get; set; }
  58 +
  59 + /// <summary>
  60 + /// 清洗商名称
  61 + /// </summary>
  62 + [Display(Name = "清洗商名称")]
  63 + public string laundrySupplierName { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 数量
  67 + /// </summary>
  68 + [Display(Name = "数量")]
  69 + public int quantity { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 清洗单价
  73 + /// </summary>
  74 + [Display(Name = "清洗单价")]
  75 + public decimal laundryPrice { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 总费用
  79 + /// </summary>
  80 + [Display(Name = "总费用")]
  81 + public decimal totalPrice { get; set; }
  82 +
  83 + /// <summary>
  84 + /// 备注
  85 + /// </summary>
  86 + [Display(Name = "备注")]
  87 + public string remark { get; set; }
  88 +
  89 + /// <summary>
  90 + /// 是否有效
  91 + /// </summary>
  92 + [Display(Name = "是否有效")]
  93 + public int isEffective { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 创建人ID
  97 + /// </summary>
  98 + [Display(Name = "创建人ID")]
  99 + public string createUser { get; set; }
  100 +
  101 + /// <summary>
  102 + /// 创建人姓名
  103 + /// </summary>
  104 + [Display(Name = "创建人姓名")]
  105 + public string createUserName { get; set; }
  106 +
  107 + /// <summary>
  108 + /// 创建时间
  109 + /// </summary>
  110 + [Display(Name = "创建时间")]
  111 + public DateTime createTime { get; set; }
  112 + }
  113 +}
  114 +
  115 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +using System.ComponentModel.DataAnnotations;
  4 +
  5 +namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
  6 +{
  7 + /// <summary>
  8 + /// 清洗流水列表查询输入
  9 + /// </summary>
  10 + public class LqLaundryFlowListQueryInput : PageInputBase
  11 + {
  12 + /// <summary>
  13 + /// 流水类型(0:送出 1:送回)
  14 + /// </summary>
  15 + [Display(Name = "流水类型", Description = "根据流水类型筛选")]
  16 + public int? FlowType { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 批次号
  20 + /// </summary>
  21 + [Display(Name = "批次号", Description = "根据批次号筛选")]
  22 + public string BatchNumber { get; set; }
  23 +
  24 + /// <summary>
  25 + /// 门店ID
  26 + /// </summary>
  27 + [Display(Name = "门店ID", Description = "根据门店ID筛选")]
  28 + public string StoreId { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 产品类型
  32 + /// </summary>
  33 + [Display(Name = "产品类型", Description = "根据产品类型筛选")]
  34 + public string ProductType { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 清洗商ID
  38 + /// </summary>
  39 + [Display(Name = "清洗商ID", Description = "根据清洗商ID筛选")]
  40 + public string LaundrySupplierId { get; set; }
  41 +
  42 + /// <summary>
  43 + /// 开始时间
  44 + /// </summary>
  45 + [Display(Name = "开始时间", Description = "根据创建时间范围筛选")]
  46 + public DateTime? StartTime { get; set; }
  47 +
  48 + /// <summary>
  49 + /// 结束时间
  50 + /// </summary>
  51 + [Display(Name = "结束时间", Description = "根据创建时间范围筛选")]
  52 + public DateTime? EndTime { get; set; }
  53 +
  54 + /// <summary>
  55 + /// 是否有效
  56 + /// </summary>
  57 + [Display(Name = "是否有效", Description = "根据是否有效筛选")]
  58 + public int? IsEffective { get; set; }
  59 + }
  60 +}
  61 +
  62 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowReturnInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
  4 +{
  5 + /// <summary>
  6 + /// 清洗流水送回输入
  7 + /// </summary>
  8 + public class LqLaundryFlowReturnInput
  9 + {
  10 + /// <summary>
  11 + /// 批次号(对应的送出记录的ID)
  12 + /// </summary>
  13 + [Required(ErrorMessage = "批次号不能为空")]
  14 + [StringLength(50, ErrorMessage = "批次号长度不能超过50个字符")]
  15 + [Display(Name = "批次号")]
  16 + public string BatchNumber { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 清洗商ID(可能和送出的清洗商不同)
  20 + /// </summary>
  21 + [Required(ErrorMessage = "清洗商ID不能为空")]
  22 + [StringLength(50, ErrorMessage = "清洗商ID长度不能超过50个字符")]
  23 + [Display(Name = "清洗商ID")]
  24 + public string LaundrySupplierId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 送回数量
  28 + /// </summary>
  29 + [Required(ErrorMessage = "送回数量不能为空")]
  30 + [Range(0, int.MaxValue, ErrorMessage = "送回数量不能小于0")]
  31 + [Display(Name = "送回数量")]
  32 + public int Quantity { get; set; }
  33 +
  34 + /// <summary>
  35 + /// 备注(可说明差异原因,如损坏、丢失等)
  36 + /// </summary>
  37 + [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
  38 + [Display(Name = "备注")]
  39 + public string Remark { get; set; }
  40 + }
  41 +}
  42 +
  43 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryFlowSendInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
  4 +{
  5 + /// <summary>
  6 + /// 清洗流水送出输入
  7 + /// </summary>
  8 + public class LqLaundryFlowSendInput
  9 + {
  10 + /// <summary>
  11 + /// 门店ID
  12 + /// </summary>
  13 + [Required(ErrorMessage = "门店ID不能为空")]
  14 + [StringLength(50, ErrorMessage = "门店ID长度不能超过50个字符")]
  15 + [Display(Name = "门店ID")]
  16 + public string StoreId { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 产品类型(枚举:毛巾、垫子等)
  20 + /// </summary>
  21 + [Required(ErrorMessage = "产品类型不能为空")]
  22 + [StringLength(50, ErrorMessage = "产品类型长度不能超过50个字符")]
  23 + [Display(Name = "产品类型")]
  24 + public string ProductType { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 清洗商ID
  28 + /// </summary>
  29 + [Required(ErrorMessage = "清洗商ID不能为空")]
  30 + [StringLength(50, ErrorMessage = "清洗商ID长度不能超过50个字符")]
  31 + [Display(Name = "清洗商ID")]
  32 + public string LaundrySupplierId { get; set; }
  33 +
  34 + /// <summary>
  35 + /// 送出数量
  36 + /// </summary>
  37 + [Required(ErrorMessage = "送出数量不能为空")]
  38 + [Range(1, int.MaxValue, ErrorMessage = "送出数量必须大于0")]
  39 + [Display(Name = "送出数量")]
  40 + public int Quantity { get; set; }
  41 +
  42 + /// <summary>
  43 + /// 备注
  44 + /// </summary>
  45 + [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
  46 + [Display(Name = "备注")]
  47 + public string Remark { get; set; }
  48 + }
  49 +}
  50 +
  51 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundryFlow/LqLaundryStatisticsOutput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqLaundryFlow
  5 +{
  6 + /// <summary>
  7 + /// 清洗费用统计输出
  8 + /// </summary>
  9 + public class LqLaundryStatisticsOutput
  10 + {
  11 + /// <summary>
  12 + /// 门店ID(门店统计时使用)
  13 + /// </summary>
  14 + [Display(Name = "门店ID")]
  15 + public string storeId { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 门店名称(门店统计时使用)
  19 + /// </summary>
  20 + [Display(Name = "门店名称")]
  21 + public string storeName { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 产品类型(产品统计时使用)
  25 + /// </summary>
  26 + [Display(Name = "产品类型")]
  27 + public string productType { get; set; }
  28 +
  29 + /// <summary>
  30 + /// 统计月份(格式:YYYYMM)
  31 + /// </summary>
  32 + [Display(Name = "统计月份")]
  33 + public string statisticsMonth { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 总费用
  37 + /// </summary>
  38 + [Display(Name = "总费用")]
  39 + public decimal totalPrice { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 清洗次数
  43 + /// </summary>
  44 + [Display(Name = "清洗次数")]
  45 + public int count { get; set; }
  46 + }
  47 +}
  48 +
  49 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundrySupplier/LqLaundrySupplierCrInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqLaundrySupplier
  4 +{
  5 + /// <summary>
  6 + /// 清洗商创建输入
  7 + /// </summary>
  8 + public class LqLaundrySupplierCrInput
  9 + {
  10 + /// <summary>
  11 + /// 清洗商名称
  12 + /// </summary>
  13 + [Required(ErrorMessage = "清洗商名称不能为空")]
  14 + [StringLength(200, ErrorMessage = "清洗商名称长度不能超过200个字符")]
  15 + [Display(Name = "清洗商名称")]
  16 + public string SupplierName { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 产品类型(枚举:毛巾、垫子等)
  20 + /// </summary>
  21 + [Required(ErrorMessage = "产品类型不能为空")]
  22 + [StringLength(50, ErrorMessage = "产品类型长度不能超过50个字符")]
  23 + [Display(Name = "产品类型")]
  24 + public string ProductType { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 清洗价格
  28 + /// </summary>
  29 + [Required(ErrorMessage = "清洗价格不能为空")]
  30 + [Range(0, double.MaxValue, ErrorMessage = "清洗价格不能小于0")]
  31 + [Display(Name = "清洗价格")]
  32 + public decimal LaundryPrice { get; set; }
  33 +
  34 + /// <summary>
  35 + /// 备注
  36 + /// </summary>
  37 + [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
  38 + [Display(Name = "备注")]
  39 + public string Remark { get; set; }
  40 + }
  41 +}
  42 +
  43 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundrySupplier/LqLaundrySupplierInfoOutput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqLaundrySupplier
  5 +{
  6 + /// <summary>
  7 + /// 清洗商详情输出
  8 + /// </summary>
  9 + public class LqLaundrySupplierInfoOutput
  10 + {
  11 + /// <summary>
  12 + /// 主键ID
  13 + /// </summary>
  14 + [Display(Name = "ID")]
  15 + public string id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 清洗商名称
  19 + /// </summary>
  20 + [Display(Name = "清洗商名称")]
  21 + public string supplierName { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 产品类型
  25 + /// </summary>
  26 + [Display(Name = "产品类型")]
  27 + public string productType { get; set; }
  28 +
  29 + /// <summary>
  30 + /// 清洗价格
  31 + /// </summary>
  32 + [Display(Name = "清洗价格")]
  33 + public decimal laundryPrice { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 备注
  37 + /// </summary>
  38 + [Display(Name = "备注")]
  39 + public string remark { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 是否有效
  43 + /// </summary>
  44 + [Display(Name = "是否有效")]
  45 + public int isEffective { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 创建人ID
  49 + /// </summary>
  50 + [Display(Name = "创建人ID")]
  51 + public string createUser { get; set; }
  52 +
  53 + /// <summary>
  54 + /// 创建人姓名
  55 + /// </summary>
  56 + [Display(Name = "创建人姓名")]
  57 + public string createUserName { get; set; }
  58 +
  59 + /// <summary>
  60 + /// 创建时间
  61 + /// </summary>
  62 + [Display(Name = "创建时间")]
  63 + public DateTime createTime { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 更新人ID
  67 + /// </summary>
  68 + [Display(Name = "更新人ID")]
  69 + public string updateUser { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 更新人姓名
  73 + /// </summary>
  74 + [Display(Name = "更新人姓名")]
  75 + public string updateUserName { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 更新时间
  79 + /// </summary>
  80 + [Display(Name = "更新时间")]
  81 + public DateTime? updateTime { get; set; }
  82 + }
  83 +}
  84 +
  85 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundrySupplier/LqLaundrySupplierListOutput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqLaundrySupplier
  5 +{
  6 + /// <summary>
  7 + /// 清洗商列表输出
  8 + /// </summary>
  9 + public class LqLaundrySupplierListOutput
  10 + {
  11 + /// <summary>
  12 + /// 主键ID
  13 + /// </summary>
  14 + [Display(Name = "ID")]
  15 + public string id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 清洗商名称
  19 + /// </summary>
  20 + [Display(Name = "清洗商名称")]
  21 + public string supplierName { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 产品类型
  25 + /// </summary>
  26 + [Display(Name = "产品类型")]
  27 + public string productType { get; set; }
  28 +
  29 + /// <summary>
  30 + /// 清洗价格
  31 + /// </summary>
  32 + [Display(Name = "清洗价格")]
  33 + public decimal laundryPrice { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 备注
  37 + /// </summary>
  38 + [Display(Name = "备注")]
  39 + public string remark { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 是否有效
  43 + /// </summary>
  44 + [Display(Name = "是否有效")]
  45 + public int isEffective { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 创建人ID
  49 + /// </summary>
  50 + [Display(Name = "创建人ID")]
  51 + public string createUser { get; set; }
  52 +
  53 + /// <summary>
  54 + /// 创建人姓名
  55 + /// </summary>
  56 + [Display(Name = "创建人姓名")]
  57 + public string createUserName { get; set; }
  58 +
  59 + /// <summary>
  60 + /// 创建时间
  61 + /// </summary>
  62 + [Display(Name = "创建时间")]
  63 + public DateTime createTime { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 更新人ID
  67 + /// </summary>
  68 + [Display(Name = "更新人ID")]
  69 + public string updateUser { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 更新人姓名
  73 + /// </summary>
  74 + [Display(Name = "更新人姓名")]
  75 + public string updateUserName { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 更新时间
  79 + /// </summary>
  80 + [Display(Name = "更新时间")]
  81 + public DateTime? updateTime { get; set; }
  82 + }
  83 +}
  84 +
  85 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundrySupplier/LqLaundrySupplierListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqLaundrySupplier
  5 +{
  6 + /// <summary>
  7 + /// 清洗商列表查询输入
  8 + /// </summary>
  9 + public class LqLaundrySupplierListQueryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 清洗商名称(模糊查询)
  13 + /// </summary>
  14 + [Display(Name = "清洗商名称", Description = "根据清洗商名称筛选")]
  15 + public string SupplierName { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 产品类型
  19 + /// </summary>
  20 + [Display(Name = "产品类型", Description = "根据产品类型筛选")]
  21 + public string ProductType { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 是否有效
  25 + /// </summary>
  26 + [Display(Name = "是否有效", Description = "根据是否有效筛选")]
  27 + public int? IsEffective { get; set; }
  28 + }
  29 +}
  30 +
  31 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqLaundrySupplier/LqLaundrySupplierUpInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqLaundrySupplier
  4 +{
  5 + /// <summary>
  6 + /// 清洗商更新输入
  7 + /// </summary>
  8 + public class LqLaundrySupplierUpInput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + [Required(ErrorMessage = "ID不能为空")]
  14 + [Display(Name = "ID")]
  15 + public string Id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 清洗商名称
  19 + /// </summary>
  20 + [Required(ErrorMessage = "清洗商名称不能为空")]
  21 + [StringLength(200, ErrorMessage = "清洗商名称长度不能超过200个字符")]
  22 + [Display(Name = "清洗商名称")]
  23 + public string SupplierName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 产品类型(枚举:毛巾、垫子等)
  27 + /// </summary>
  28 + [Required(ErrorMessage = "产品类型不能为空")]
  29 + [StringLength(50, ErrorMessage = "产品类型长度不能超过50个字符")]
  30 + [Display(Name = "产品类型")]
  31 + public string ProductType { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 清洗价格
  35 + /// </summary>
  36 + [Required(ErrorMessage = "清洗价格不能为空")]
  37 + [Range(0, double.MaxValue, ErrorMessage = "清洗价格不能小于0")]
  38 + [Display(Name = "清洗价格")]
  39 + public decimal LaundryPrice { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 备注
  43 + /// </summary>
  44 + [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
  45 + [Display(Name = "备注")]
  46 + public string Remark { get; set; }
  47 + }
  48 +}
  49 +
  50 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/GoddessCardMemberListQueryInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqStatistics
  2 +{
  3 + /// <summary>
  4 + /// 只买了女神卡的会员查询输入
  5 + /// </summary>
  6 + public class GoddessCardMemberListQueryInput
  7 + {
  8 + /// <summary>
  9 + /// 当前页码(从1开始)
  10 + /// </summary>
  11 + public int PageIndex { get; set; } = 1;
  12 +
  13 + /// <summary>
  14 + /// 每页数量
  15 + /// </summary>
  16 + public int PageSize { get; set; } = 20;
  17 + }
  18 +}
  19 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/GoddessCardMemberOutput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqStatistics
  5 +{
  6 + /// <summary>
  7 + /// 只买了女神卡的会员输出
  8 + /// </summary>
  9 + public class GoddessCardMemberOutput
  10 + {
  11 + /// <summary>
  12 + /// 会员ID
  13 + /// </summary>
  14 + public string memberId { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 会员名称
  18 + /// </summary>
  19 + public string memberName { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 手机号
  23 + /// </summary>
  24 + public string phone { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 归属门店ID
  28 + /// </summary>
  29 + public string storeId { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 归属门店名称
  33 + /// </summary>
  34 + public string storeName { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 开单记录列表
  38 + /// </summary>
  39 + public List<GoddessCardBillingInfo> billingList { get; set; }
  40 + }
  41 +
  42 + /// <summary>
  43 + /// 女神卡开单信息
  44 + /// </summary>
  45 + public class GoddessCardBillingInfo
  46 + {
  47 + /// <summary>
  48 + /// 开单编号
  49 + /// </summary>
  50 + public string billingId { get; set; }
  51 +
  52 + /// <summary>
  53 + /// 开单日期
  54 + /// </summary>
  55 + public DateTime? billingDate { get; set; }
  56 +
  57 + /// <summary>
  58 + /// 实付业绩
  59 + /// </summary>
  60 + public decimal actualPerformance { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 门店ID
  64 + /// </summary>
  65 + public string storeId { get; set; }
  66 +
  67 + /// <summary>
  68 + /// 门店名称
  69 + /// </summary>
  70 + public string storeName { get; set; }
  71 +
  72 + /// <summary>
  73 + /// 品项列表
  74 + /// </summary>
  75 + public List<GoddessCardBillingItemInfo> itemList { get; set; }
  76 + }
  77 +
  78 + /// <summary>
  79 + /// 女神卡开单品项信息
  80 + /// </summary>
  81 + public class GoddessCardBillingItemInfo
  82 + {
  83 + /// <summary>
  84 + /// 品项明细ID
  85 + /// </summary>
  86 + public string itemId { get; set; }
  87 +
  88 + /// <summary>
  89 + /// 品项编号
  90 + /// </summary>
  91 + public string itemCode { get; set; }
  92 +
  93 + /// <summary>
  94 + /// 品项名称
  95 + /// </summary>
  96 + public string itemName { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 品项价格
  100 + /// </summary>
  101 + public decimal itemPrice { get; set; }
  102 +
  103 + /// <summary>
  104 + /// 来源类型
  105 + /// </summary>
  106 + public string sourceType { get; set; }
  107 +
  108 + /// <summary>
  109 + /// 项目次数
  110 + /// </summary>
  111 + public decimal projectNumber { get; set; }
  112 +
  113 + /// <summary>
  114 + /// 总价
  115 + /// </summary>
  116 + public decimal totalPrice { get; set; }
  117 +
  118 + /// <summary>
  119 + /// 实付金额
  120 + /// </summary>
  121 + public decimal actualPrice { get; set; }
  122 +
  123 + /// <summary>
  124 + /// 业绩时间
  125 + /// </summary>
  126 + public DateTime? performanceTime { get; set; }
  127 + }
  128 +}
  129 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/TechTeacherSimpleStatisticsOutput.cs
... ... @@ -33,11 +33,6 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics
33 33 public decimal OrderAchievement { get; set; }
34 34  
35 35 /// <summary>
36   - /// 开卡品项次数
37   - /// </summary>
38   - public int OrderItemCount { get; set; }
39   -
40   - /// <summary>
41 36 /// 耗卡品项次数
42 37 /// </summary>
43 38 public int ConsumeItemCount { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreConsumableInventory/LqStoreConsumableInventoryCrInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqStoreConsumableInventory
  4 +{
  5 + /// <summary>
  6 + /// 门店消耗品库存创建输入
  7 + /// </summary>
  8 + public class LqStoreConsumableInventoryCrInput
  9 + {
  10 + /// <summary>
  11 + /// 门店ID
  12 + /// </summary>
  13 + [Required(ErrorMessage = "门店ID不能为空")]
  14 + [StringLength(50, ErrorMessage = "门店ID长度不能超过50个字符")]
  15 + [Display(Name = "门店ID")]
  16 + public string StoreId { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 产品类型(枚举:毛巾、垫子等)
  20 + /// </summary>
  21 + [Required(ErrorMessage = "产品类型不能为空")]
  22 + [StringLength(50, ErrorMessage = "产品类型长度不能超过50个字符")]
  23 + [Display(Name = "产品类型")]
  24 + public string ProductType { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 库存数量
  28 + /// </summary>
  29 + [Required(ErrorMessage = "库存数量不能为空")]
  30 + [Range(0, int.MaxValue, ErrorMessage = "库存数量不能小于0")]
  31 + [Display(Name = "库存数量")]
  32 + public int Quantity { get; set; }
  33 + }
  34 +}
  35 +
  36 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreConsumableInventory/LqStoreConsumableInventoryInfoOutput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqStoreConsumableInventory
  5 +{
  6 + /// <summary>
  7 + /// 门店消耗品库存详情输出
  8 + /// </summary>
  9 + public class LqStoreConsumableInventoryInfoOutput
  10 + {
  11 + /// <summary>
  12 + /// 主键ID
  13 + /// </summary>
  14 + [Display(Name = "ID")]
  15 + public string id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 门店ID
  19 + /// </summary>
  20 + [Display(Name = "门店ID")]
  21 + public string storeId { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 门店名称
  25 + /// </summary>
  26 + [Display(Name = "门店名称")]
  27 + public string storeName { get; set; }
  28 +
  29 + /// <summary>
  30 + /// 产品类型
  31 + /// </summary>
  32 + [Display(Name = "产品类型")]
  33 + public string productType { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 库存数量
  37 + /// </summary>
  38 + [Display(Name = "库存数量")]
  39 + public int quantity { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 是否有效
  43 + /// </summary>
  44 + [Display(Name = "是否有效")]
  45 + public int isEffective { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 创建人ID
  49 + /// </summary>
  50 + [Display(Name = "创建人ID")]
  51 + public string createUser { get; set; }
  52 +
  53 + /// <summary>
  54 + /// 创建人姓名
  55 + /// </summary>
  56 + [Display(Name = "创建人姓名")]
  57 + public string createUserName { get; set; }
  58 +
  59 + /// <summary>
  60 + /// 创建时间
  61 + /// </summary>
  62 + [Display(Name = "创建时间")]
  63 + public DateTime createTime { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 更新人ID
  67 + /// </summary>
  68 + [Display(Name = "更新人ID")]
  69 + public string updateUser { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 更新人姓名
  73 + /// </summary>
  74 + [Display(Name = "更新人姓名")]
  75 + public string updateUserName { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 更新时间
  79 + /// </summary>
  80 + [Display(Name = "更新时间")]
  81 + public DateTime? updateTime { get; set; }
  82 + }
  83 +}
  84 +
  85 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreConsumableInventory/LqStoreConsumableInventoryListOutput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqStoreConsumableInventory
  5 +{
  6 + /// <summary>
  7 + /// 门店消耗品库存列表输出
  8 + /// </summary>
  9 + public class LqStoreConsumableInventoryListOutput
  10 + {
  11 + /// <summary>
  12 + /// 主键ID
  13 + /// </summary>
  14 + [Display(Name = "ID")]
  15 + public string id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 门店ID
  19 + /// </summary>
  20 + [Display(Name = "门店ID")]
  21 + public string storeId { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 门店名称
  25 + /// </summary>
  26 + [Display(Name = "门店名称")]
  27 + public string storeName { get; set; }
  28 +
  29 + /// <summary>
  30 + /// 产品类型
  31 + /// </summary>
  32 + [Display(Name = "产品类型")]
  33 + public string productType { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 库存数量
  37 + /// </summary>
  38 + [Display(Name = "库存数量")]
  39 + public int quantity { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 是否有效
  43 + /// </summary>
  44 + [Display(Name = "是否有效")]
  45 + public int isEffective { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 创建人ID
  49 + /// </summary>
  50 + [Display(Name = "创建人ID")]
  51 + public string createUser { get; set; }
  52 +
  53 + /// <summary>
  54 + /// 创建人姓名
  55 + /// </summary>
  56 + [Display(Name = "创建人姓名")]
  57 + public string createUserName { get; set; }
  58 +
  59 + /// <summary>
  60 + /// 创建时间
  61 + /// </summary>
  62 + [Display(Name = "创建时间")]
  63 + public DateTime createTime { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 更新人ID
  67 + /// </summary>
  68 + [Display(Name = "更新人ID")]
  69 + public string updateUser { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 更新人姓名
  73 + /// </summary>
  74 + [Display(Name = "更新人姓名")]
  75 + public string updateUserName { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 更新时间
  79 + /// </summary>
  80 + [Display(Name = "更新时间")]
  81 + public DateTime? updateTime { get; set; }
  82 + }
  83 +}
  84 +
  85 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreConsumableInventory/LqStoreConsumableInventoryListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqStoreConsumableInventory
  5 +{
  6 + /// <summary>
  7 + /// 门店消耗品库存列表查询输入
  8 + /// </summary>
  9 + public class LqStoreConsumableInventoryListQueryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 门店ID
  13 + /// </summary>
  14 + [Display(Name = "门店ID", Description = "根据门店ID筛选")]
  15 + public string StoreId { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 产品类型
  19 + /// </summary>
  20 + [Display(Name = "产品类型", Description = "根据产品类型筛选")]
  21 + public string ProductType { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 是否有效
  25 + /// </summary>
  26 + [Display(Name = "是否有效", Description = "根据是否有效筛选")]
  27 + public int? IsEffective { get; set; }
  28 + }
  29 +}
  30 +
  31 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreConsumableInventory/LqStoreConsumableInventoryUpInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqStoreConsumableInventory
  4 +{
  5 + /// <summary>
  6 + /// 门店消耗品库存更新输入
  7 + /// </summary>
  8 + public class LqStoreConsumableInventoryUpInput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + [Required(ErrorMessage = "ID不能为空")]
  14 + [Display(Name = "ID")]
  15 + public string Id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 门店ID
  19 + /// </summary>
  20 + [Required(ErrorMessage = "门店ID不能为空")]
  21 + [StringLength(50, ErrorMessage = "门店ID长度不能超过50个字符")]
  22 + [Display(Name = "门店ID")]
  23 + public string StoreId { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 产品类型(枚举:毛巾、垫子等)
  27 + /// </summary>
  28 + [Required(ErrorMessage = "产品类型不能为空")]
  29 + [StringLength(50, ErrorMessage = "产品类型长度不能超过50个字符")]
  30 + [Display(Name = "产品类型")]
  31 + public string ProductType { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 库存数量
  35 + /// </summary>
  36 + [Required(ErrorMessage = "库存数量不能为空")]
  37 + [Range(0, int.MaxValue, ErrorMessage = "库存数量不能小于0")]
  38 + [Display(Name = "库存数量")]
  39 + public int Quantity { get; set; }
  40 + }
  41 +}
  42 +
  43 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage/LqInventoryUsageEntity.cs
... ... @@ -48,6 +48,12 @@ namespace NCC.Extend.Entitys.lq_inventory_usage
48 48 public string RelatedConsumeId { get; set; }
49 49  
50 50 /// <summary>
  51 + /// 使用批次ID(同一批次申请的使用记录使用相同的批次ID)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_UsageBatchId")]
  54 + public string UsageBatchId { get; set; }
  55 +
  56 + /// <summary>
51 57 /// 创建人ID
52 58 /// </summary>
53 59 [SugarColumn(ColumnName = "F_CreateUser")]
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_laundry_flow/LqLaundryFlowEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_laundry_flow
  6 +{
  7 + /// <summary>
  8 + /// 清洗流水表
  9 + /// </summary>
  10 + [SugarTable("lq_laundry_flow")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqLaundryFlowEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID(送出时作为批次号)
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 流水类型(0:送出 1:送回)
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_FlowType")]
  24 + public int FlowType { get; set; } = 0;
  25 +
  26 + /// <summary>
  27 + /// 批次号(送出时=F_Id,送回时=对应的送出记录的F_Id)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_BatchNumber")]
  30 + public string BatchNumber { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 门店ID(关联lq_mdxx.F_Id)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_StoreId")]
  36 + public string StoreId { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 产品类型(枚举:毛巾、垫子等)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_ProductType")]
  42 + public string ProductType { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 清洗商ID(关联lq_laundry_supplier.F_Id)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_LaundrySupplierId")]
  48 + public string LaundrySupplierId { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 数量
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_Quantity")]
  54 + public int Quantity { get; set; } = 0;
  55 +
  56 + /// <summary>
  57 + /// 清洗单价(记录历史价格)
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_LaundryPrice")]
  60 + public decimal LaundryPrice { get; set; } = 0;
  61 +
  62 + /// <summary>
  63 + /// 总费用(数量 × 单价)
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_TotalPrice")]
  66 + public decimal TotalPrice { get; set; } = 0;
  67 +
  68 + /// <summary>
  69 + /// 备注(可说明差异原因,如损坏、丢失等)
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_Remark")]
  72 + public string Remark { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 是否有效(1:有效 0:无效)
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_IsEffective")]
  78 + public int IsEffective { get; set; } = 1;
  79 +
  80 + /// <summary>
  81 + /// 创建人ID
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_CreateUser")]
  84 + public string CreateUser { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 创建时间
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_CreateTime")]
  90 + public DateTime CreateTime { get; set; }
  91 + }
  92 +}
  93 +
  94 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_laundry_supplier/LqLaundrySupplierEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_laundry_supplier
  6 +{
  7 + /// <summary>
  8 + /// 清洗商表
  9 + /// </summary>
  10 + [SugarTable("lq_laundry_supplier")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqLaundrySupplierEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 清洗商名称
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_SupplierName")]
  24 + public string SupplierName { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 产品类型(枚举:毛巾、垫子等)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_ProductType")]
  30 + public string ProductType { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 清洗价格(当前价格)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_LaundryPrice")]
  36 + public decimal LaundryPrice { get; set; } = 0;
  37 +
  38 + /// <summary>
  39 + /// 备注
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_Remark")]
  42 + public string Remark { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 是否有效(1:有效 0:无效)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_IsEffective")]
  48 + public int IsEffective { get; set; } = 1;
  49 +
  50 + /// <summary>
  51 + /// 创建人ID
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_CreateUser")]
  54 + public string CreateUser { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 创建时间
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_CreateTime")]
  60 + public DateTime CreateTime { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 更新人ID
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_UpdateUser")]
  66 + public string UpdateUser { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 更新时间
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_UpdateTime")]
  72 + public DateTime? UpdateTime { get; set; }
  73 + }
  74 +}
  75 +
  76 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_store_consumable_inventory/LqStoreConsumableInventoryEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_store_consumable_inventory
  6 +{
  7 + /// <summary>
  8 + /// 门店消耗品库存表
  9 + /// </summary>
  10 + [SugarTable("lq_store_consumable_inventory")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqStoreConsumableInventoryEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键ID
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID(关联lq_mdxx.F_Id)
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_StoreId")]
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 产品类型(枚举:毛巾、垫子等)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_ProductType")]
  30 + public string ProductType { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 当前库存数量
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_Quantity")]
  36 + public int Quantity { get; set; } = 0;
  37 +
  38 + /// <summary>
  39 + /// 是否有效(1:有效 0:无效)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_IsEffective")]
  42 + public int IsEffective { get; set; } = 1;
  43 +
  44 + /// <summary>
  45 + /// 创建人ID
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_CreateUser")]
  48 + public string CreateUser { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 创建时间
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_CreateTime")]
  54 + public DateTime CreateTime { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 更新人ID
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_UpdateUser")]
  60 + public string UpdateUser { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 更新时间
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_UpdateTime")]
  66 + public DateTime? UpdateTime { get; set; }
  67 + }
  68 +}
  69 +
  70 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/ConsumableProductTypeEnum.cs 0 → 100644
  1 +using System.ComponentModel;
  2 +
  3 +namespace NCC.Extend.Entitys.Enum
  4 +{
  5 + /// <summary>
  6 + /// 消耗品产品类型枚举
  7 + /// </summary>
  8 + public enum ConsumableProductTypeEnum
  9 + {
  10 + /// <summary>
  11 + /// 毛巾
  12 + /// </summary>
  13 + [Description("毛巾")]
  14 + 毛巾 = 1,
  15 +
  16 + /// <summary>
  17 + /// 垫子
  18 + /// </summary>
  19 + [Description("垫子")]
  20 + 垫子 = 2,
  21 +
  22 + /// <summary>
  23 + /// 浴巾
  24 + /// </summary>
  25 + [Description("浴巾")]
  26 + 浴巾 = 3,
  27 +
  28 + /// <summary>
  29 + /// 床单
  30 + /// </summary>
  31 + [Description("床单")]
  32 + 床单 = 4,
  33 +
  34 + /// <summary>
  35 + /// 枕套
  36 + /// </summary>
  37 + [Description("枕套")]
  38 + 枕套 = 5,
  39 +
  40 + /// <summary>
  41 + /// 其他
  42 + /// </summary>
  43 + [Description("其他")]
  44 + 其他 = 99
  45 + }
  46 +}
  47 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqLaundryFlow/ILqLaundryFlowService.cs 0 → 100644
  1 +namespace NCC.Extend.Interfaces.LqLaundryFlow
  2 +{
  3 + /// <summary>
  4 + /// 清洗流水服务接口
  5 + /// </summary>
  6 + public interface ILqLaundryFlowService
  7 + {
  8 + }
  9 +}
  10 +
  11 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqLaundrySupplier/ILqLaundrySupplierService.cs 0 → 100644
  1 +namespace NCC.Extend.Interfaces.LqLaundrySupplier
  2 +{
  3 + /// <summary>
  4 + /// 清洗商服务接口
  5 + /// </summary>
  6 + public interface ILqLaundrySupplierService
  7 + {
  8 + }
  9 +}
  10 +
  11 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqStoreConsumableInventory/ILqStoreConsumableInventoryService.cs 0 → 100644
  1 +namespace NCC.Extend.Interfaces.LqStoreConsumableInventory
  2 +{
  3 + /// <summary>
  4 + /// 门店消耗品库存服务接口
  5 + /// </summary>
  6 + public interface ILqStoreConsumableInventoryService
  7 + {
  8 + }
  9 +}
  10 +
  11 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
... ... @@ -265,8 +265,8 @@ namespace NCC.Extend
265 265 FROM lq_kd_kdjlb billing
266 266 WHERE billing.djmd = store.F_Id
267 267 AND billing.F_IsEffective = 1
268   - AND DATE(billing.kdrq) >= '{startDate:yyyy-MM-dd}'
269   - AND DATE(billing.kdrq) <= '{endDate:yyyy-MM-dd}'
  268 + AND DATE(billing.kdrq) >= '{startDate:yyyy-MM-dd 00:00:00}'
  269 + AND DATE(billing.kdrq) <= '{endDate:yyyy-MM-dd 23:59:59}'
270 270 ), 0) as BillingPerformance,
271 271 -- 退款业绩总和(退卡业绩)
272 272 COALESCE((
... ... @@ -274,8 +274,8 @@ namespace NCC.Extend
274 274 FROM lq_hytk_hytk refund
275 275 WHERE refund.md = store.F_Id
276 276 AND refund.F_IsEffective = 1
277   - AND DATE(refund.tksj) >= '{startDate:yyyy-MM-dd}'
278   - AND DATE(refund.tksj) <= '{endDate:yyyy-MM-dd}'
  277 + AND DATE(refund.tksj) >= '{startDate:yyyy-MM-dd 00:00:00}'
  278 + AND DATE(refund.tksj) <= '{endDate:yyyy-MM-dd 23:59:59}'
279 279 ), 0) as RefundPerformance
280 280 FROM lq_mdxx store
281 281 LEFT JOIN lq_md_target target ON target.F_StoreId = store.F_Id AND target.F_Month = '{month}'
... ... @@ -1762,8 +1762,6 @@ namespace NCC.Extend
1762 1762 }
1763 1763 #endregion
1764 1764  
1765   -
1766   -
1767 1765 #region 获取储值扣减金额统计
1768 1766 /// <summary>
1769 1767 /// 获取储值扣减金额统计
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
1 1 using System;
  2 +using System.Collections.Generic;
2 3 using System.Linq;
3 4 using System.Threading.Tasks;
4 5 using Microsoft.AspNetCore.Mvc;
... ... @@ -117,6 +118,173 @@ namespace NCC.Extend
117 118 }
118 119 #endregion
119 120  
  121 + #region 批量添加库存使用记录
  122 + /// <summary>
  123 + /// 批量添加库存使用记录
  124 + /// </summary>
  125 + /// <remarks>
  126 + /// 一次性添加多条库存使用记录,同一批次的所有记录使用相同的批次ID
  127 + ///
  128 + /// 示例请求:
  129 + /// ```json
  130 + /// {
  131 + /// "batchId": "可选,不传则自动生成",
  132 + /// "usageItems": [
  133 + /// {
  134 + /// "productId": "产品ID",
  135 + /// "storeId": "门店ID",
  136 + /// "usageTime": "2024-01-01T10:00:00",
  137 + /// "usageQuantity": 10,
  138 + /// "relatedConsumeId": "关联消耗ID(可选)"
  139 + /// }
  140 + /// ]
  141 + /// }
  142 + /// ```
  143 + ///
  144 + /// 参数说明:
  145 + /// - batchId: 批次ID,可选。如果不传,系统会自动生成一个唯一的批次ID
  146 + /// - usageItems: 使用记录列表,至少需要一条记录
  147 + /// - productId: 产品ID(必填)
  148 + /// - storeId: 门店ID(必填)
  149 + /// - usageTime: 使用时间(必填)
  150 + /// - usageQuantity: 使用数量(必填,必须大于0)
  151 + /// - relatedConsumeId: 关联消耗ID(可选)
  152 + /// </remarks>
  153 + /// <param name="input">批量创建输入</param>
  154 + /// <returns>批量创建结果,包含批次ID和成功/失败信息</returns>
  155 + /// <response code="200">批量创建成功,返回批次ID和创建结果</response>
  156 + /// <response code="400">输入参数错误或库存不足</response>
  157 + /// <response code="500">服务器错误</response>
  158 + [HttpPost("BatchCreate")]
  159 + public async Task<LqInventoryUsageBatchCreateOutput> BatchCreateAsync([FromBody] LqInventoryUsageBatchCreateInput input)
  160 + {
  161 + try
  162 + {
  163 + if (input == null || input.UsageItems == null || !input.UsageItems.Any())
  164 + {
  165 + throw NCCException.Oh("使用记录列表不能为空");
  166 + }
  167 +
  168 + // 生成批次ID(如果未提供)
  169 + var batchId = string.IsNullOrWhiteSpace(input.BatchId)
  170 + ? YitIdHelper.NextId().ToString()
  171 + : input.BatchId;
  172 +
  173 + var successIds = new List<string>();
  174 + var failItems = new List<BatchCreateFailItem>();
  175 +
  176 + _db.Ado.BeginTran();
  177 +
  178 + try
  179 + {
  180 + // 按产品ID分组,批量验证库存
  181 + var productGroups = input.UsageItems
  182 + .Select((item, index) => new { Item = item, Index = index })
  183 + .GroupBy(x => x.Item.ProductId)
  184 + .ToList();
  185 +
  186 + // 计算每个产品的总需求并检查库存
  187 + foreach (var productGroup in productGroups)
  188 + {
  189 + var productId = productGroup.Key;
  190 + var totalRequired = productGroup.Sum(x => x.Item.UsageQuantity);
  191 +
  192 + // 计算该产品的总库存数量
  193 + var totalInventory = await _db.Queryable<LqInventoryEntity>()
  194 + .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode())
  195 + .SumAsync(x => (int?)x.Quantity) ?? 0;
  196 +
  197 + // 计算该产品的已使用数量
  198 + var totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
  199 + .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode())
  200 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  201 +
  202 + // 计算可用库存
  203 + var availableInventory = totalInventory - totalUsage;
  204 +
  205 + // 检查库存是否足够
  206 + if (availableInventory < totalRequired)
  207 + {
  208 + var failIndices = productGroup.Select(x => x.Index).ToList();
  209 +
  210 + foreach (var index in failIndices)
  211 + {
  212 + failItems.Add(new BatchCreateFailItem
  213 + {
  214 + Index = index,
  215 + ProductId = productId,
  216 + Reason = $"库存不足,当前可用库存:{availableInventory},需要数量:{totalRequired}"
  217 + });
  218 + }
  219 + }
  220 + }
  221 +
  222 + // 创建成功的使用记录
  223 + var entitiesToInsert = new List<LqInventoryUsageEntity>();
  224 + for (int i = 0; i < input.UsageItems.Count; i++)
  225 + {
  226 + var item = input.UsageItems[i];
  227 +
  228 + // 跳过失败项
  229 + if (failItems.Any(x => x.Index == i))
  230 + {
  231 + continue;
  232 + }
  233 +
  234 + var usageEntity = new LqInventoryUsageEntity
  235 + {
  236 + Id = YitIdHelper.NextId().ToString(),
  237 + ProductId = item.ProductId,
  238 + StoreId = item.StoreId,
  239 + UsageTime = item.UsageTime,
  240 + UsageQuantity = item.UsageQuantity,
  241 + RelatedConsumeId = item.RelatedConsumeId,
  242 + UsageBatchId = batchId,
  243 + CreateUser = _userManager.UserId,
  244 + CreateTime = DateTime.Now,
  245 + IsEffective = StatusEnum.有效.GetHashCode()
  246 + };
  247 +
  248 + entitiesToInsert.Add(usageEntity);
  249 + successIds.Add(usageEntity.Id);
  250 + }
  251 +
  252 + // 批量插入
  253 + if (entitiesToInsert.Any())
  254 + {
  255 + var insertCount = await _db.Insertable(entitiesToInsert).ExecuteCommandAsync();
  256 + if (insertCount != entitiesToInsert.Count)
  257 + {
  258 + throw NCCException.Oh($"批量插入失败,预期插入{entitiesToInsert.Count}条,实际插入{insertCount}条");
  259 + }
  260 + }
  261 +
  262 + _db.Ado.CommitTran();
  263 +
  264 + return new LqInventoryUsageBatchCreateOutput
  265 + {
  266 + BatchId = batchId,
  267 + SuccessCount = successIds.Count,
  268 + FailCount = failItems.Count,
  269 + SuccessIds = successIds,
  270 + FailItems = failItems
  271 + };
  272 + }
  273 + catch
  274 + {
  275 + _db.Ado.RollbackTran();
  276 + throw;
  277 + }
  278 + }
  279 + catch (Exception ex)
  280 + {
  281 + _db.Ado.RollbackTran();
  282 + _logger.LogError(ex, "批量添加库存使用记录失败");
  283 + throw NCCException.Oh($"批量添加失败:{ex.Message}");
  284 + }
  285 + }
  286 + #endregion
  287 +
120 288 #region 作废库存使用记录
121 289 /// <summary>
122 290 /// 作废库存使用记录
... ... @@ -185,6 +353,7 @@ namespace NCC.Extend
185 353 .WhereIF(input.UsageStartTime.HasValue, (usage, product) => usage.UsageTime >= input.UsageStartTime.Value)
186 354 .WhereIF(input.UsageEndTime.HasValue, (usage, product) => usage.UsageTime <= input.UsageEndTime.Value)
187 355 .WhereIF(!string.IsNullOrWhiteSpace(input.RelatedConsumeId), (usage, product) => usage.RelatedConsumeId == input.RelatedConsumeId)
  356 + .WhereIF(!string.IsNullOrWhiteSpace(input.UsageBatchId), (usage, product) => usage.UsageBatchId == input.UsageBatchId)
188 357 .WhereIF(input.IsEffective.HasValue, (usage, product) => usage.IsEffective == input.IsEffective.Value)
189 358 .Select((usage, product) => new LqInventoryUsageListOutput
190 359 {
... ... @@ -198,6 +367,7 @@ namespace NCC.Extend
198 367 usageTime = usage.UsageTime,
199 368 usageQuantity = usage.UsageQuantity,
200 369 relatedConsumeId = usage.RelatedConsumeId,
  370 + usageBatchId = usage.UsageBatchId,
201 371 createUser = usage.CreateUser,
202 372 createUserName = "",
203 373 createTime = usage.CreateTime,
... ... @@ -258,6 +428,129 @@ namespace NCC.Extend
258 428 }
259 429 #endregion
260 430  
  431 + #region 根据批次号获取批次信息
  432 + /// <summary>
  433 + /// 根据批次号获取批次信息
  434 + /// </summary>
  435 + /// <remarks>
  436 + /// 根据批次ID查询该批次的所有使用记录信息,包括批次基本信息和详细的使用记录列表
  437 + ///
  438 + /// 返回数据结构:
  439 + /// - 批次基本信息:批次ID、创建时间、创建人、统计信息等
  440 + /// - 使用记录列表:该批次的所有使用记录详情
  441 + ///
  442 + /// 参数说明:
  443 + /// - batchId: 批次ID(必填)
  444 + /// </remarks>
  445 + /// <param name="batchId">批次ID</param>
  446 + /// <returns>批次信息,包含该批次的所有使用记录</returns>
  447 + /// <response code="200">查询成功,返回批次信息和使用记录列表</response>
  448 + /// <response code="400">批次ID不能为空</response>
  449 + /// <response code="404">批次不存在</response>
  450 + /// <response code="500">服务器错误</response>
  451 + [HttpGet("GetBatchInfo")]
  452 + public async Task<LqInventoryUsageBatchInfoOutput> GetBatchInfoAsync([FromQuery] string batchId)
  453 + {
  454 + try
  455 + {
  456 + if (string.IsNullOrWhiteSpace(batchId))
  457 + {
  458 + throw NCCException.Oh("批次ID不能为空");
  459 + }
  460 +
  461 + // 查询该批次的所有使用记录
  462 + var usageRecords = await _db.Queryable<LqInventoryUsageEntity, LqProductEntity>(
  463 + (usage, product) => usage.ProductId == product.Id)
  464 + .LeftJoin<LqMdxxEntity>((usage, product, store) => usage.StoreId == store.Id)
  465 + .Where((usage, product, store) => usage.UsageBatchId == batchId)
  466 + .Select((usage, product, store) => new LqInventoryUsageListOutput
  467 + {
  468 + id = usage.Id,
  469 + productId = usage.ProductId,
  470 + productName = product.ProductName,
  471 + productCategory = product.ProductCategory,
  472 + productPrice = product.Price,
  473 + storeId = usage.StoreId,
  474 + storeName = store.Dm,
  475 + usageTime = usage.UsageTime,
  476 + usageQuantity = usage.UsageQuantity,
  477 + relatedConsumeId = usage.RelatedConsumeId,
  478 + usageBatchId = usage.UsageBatchId,
  479 + createUser = usage.CreateUser,
  480 + createUserName = "",
  481 + createTime = usage.CreateTime,
  482 + updateUser = usage.UpdateUser,
  483 + updateUserName = "",
  484 + updateTime = usage.UpdateTime,
  485 + isEffective = usage.IsEffective
  486 + })
  487 + .MergeTable()
  488 + .OrderBy("createTime")
  489 + .ToListAsync();
  490 +
  491 + if (!usageRecords.Any())
  492 + {
  493 + throw NCCException.Oh("批次不存在或该批次下没有使用记录");
  494 + }
  495 +
  496 + // 补充用户信息
  497 + var userIds = usageRecords.SelectMany(x => new[] { x.createUser, x.updateUser })
  498 + .Where(x => !string.IsNullOrEmpty(x))
  499 + .Distinct()
  500 + .ToList();
  501 +
  502 + if (userIds.Any())
  503 + {
  504 + var userList = await _db.Queryable<UserEntity>()
  505 + .Where(x => userIds.Contains(x.Id))
  506 + .Select(x => new { x.Id, x.RealName })
  507 + .ToListAsync();
  508 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  509 +
  510 + foreach (var item in usageRecords)
  511 + {
  512 + if (!string.IsNullOrEmpty(item.createUser) && userDict.ContainsKey(item.createUser))
  513 + item.createUserName = userDict[item.createUser];
  514 + if (!string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser))
  515 + item.updateUserName = userDict[item.updateUser];
  516 + }
  517 + }
  518 +
  519 + // 计算使用总金额
  520 + foreach (var record in usageRecords)
  521 + {
  522 + record.usageTotalValue = record.usageQuantity * record.productPrice;
  523 + }
  524 +
  525 + // 获取批次基本信息(使用第一条记录的创建信息)
  526 + var firstRecord = usageRecords.OrderBy(x => x.createTime).First();
  527 + var effectiveRecords = usageRecords.Where(x => x.isEffective == StatusEnum.有效.GetHashCode()).ToList();
  528 + var ineffectiveRecords = usageRecords.Where(x => x.isEffective == StatusEnum.无效.GetHashCode()).ToList();
  529 +
  530 + var batchInfo = new LqInventoryUsageBatchInfoOutput
  531 + {
  532 + BatchId = batchId,
  533 + CreateTime = firstRecord.createTime,
  534 + CreateUser = firstRecord.createUser,
  535 + CreateUserName = firstRecord.createUserName,
  536 + TotalCount = usageRecords.Count,
  537 + EffectiveCount = effectiveRecords.Count,
  538 + IneffectiveCount = ineffectiveRecords.Count,
  539 + TotalUsageQuantity = effectiveRecords.Sum(x => x.usageQuantity),
  540 + TotalUsageAmount = effectiveRecords.Sum(x => x.usageTotalValue),
  541 + UsageRecords = usageRecords
  542 + };
  543 +
  544 + return batchInfo;
  545 + }
  546 + catch (Exception ex)
  547 + {
  548 + _logger.LogError(ex, "获取批次信息失败");
  549 + throw NCCException.Oh($"获取批次信息失败:{ex.Message}");
  550 + }
  551 + }
  552 + #endregion
  553 +
261 554 #region 统计时间周期内每个产品的使用数量
262 555 /// <summary>
263 556 /// 统计时间周期内每个产品的使用数量
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -3099,22 +3099,22 @@ namespace NCC.Extend.LqKdKdjlb
3099 3099 // 查询购买数量
3100 3100 var purchasedCount = await _db.Queryable<LqKdPxmxEntity>()
3101 3101 .Where(x => x.Id == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode())
3102   - .SumAsync(x => x.ProjectNumber);
  3102 + .SumAsync(x => (decimal?)x.ProjectNumber) ?? 0m;
3103 3103  
3104 3104 // 查询消费数量
3105 3105 var consumedCount = await _db.Queryable<LqXhPxmxEntity>()
3106 3106 .Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode())
3107   - .SumAsync(x => x.OriginalProjectNumber);
  3107 + .SumAsync(x => x.OriginalProjectNumber) ?? 0m;
3108 3108  
3109 3109 // 查询退卡数量
3110 3110 var refundedCount = await _db.Queryable<LqHytkMxEntity>()
3111 3111 .Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode())
3112   - .SumAsync(x => x.ProjectNumber);
  3112 + .SumAsync(x => (decimal?)x.ProjectNumber) ?? 0m;
3113 3113  
3114 3114 // 查询储扣数量
3115 3115 var deductCount = await _db.Queryable<LqKdDeductinfoEntity>()
3116 3116 .Where(x => x.DeductId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode())
3117   - .SumAsync(x => x.ProjectNumber) ?? 0;
  3117 + .SumAsync(x => (decimal?)x.ProjectNumber) ?? 0m;
3118 3118  
3119 3119 // 计算剩余数量
3120 3120 var remainingCount = (int)(purchasedCount - consumedCount - refundedCount - deductCount);
... ... @@ -3460,15 +3460,34 @@ namespace NCC.Extend.LqKdKdjlb
3460 3460 var sidx = input.sidx == null ? "yjsj" : input.sidx;
3461 3461 var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
3462 3462  
3463   - // 处理开单时间范围
3464   - List<string> queryBillingTime = null;
  3463 + // 处理开单时间范围(兼容 StartBillingTime/EndBillingTime 和 startTime/endTime 两种参数名)
3465 3464 DateTime? startBillingTime = null;
3466 3465 DateTime? endBillingTime = null;
3467   - if (!string.IsNullOrEmpty(input.StartBillingTime) && !string.IsNullOrEmpty(input.EndBillingTime))
  3466 +
  3467 + // 优先使用 StartBillingTime/EndBillingTime,如果没有则使用 startTime/endTime
  3468 + string startTimeStr = !string.IsNullOrEmpty(input.StartBillingTime) ? input.StartBillingTime : input.startTime;
  3469 + string endTimeStr = !string.IsNullOrEmpty(input.EndBillingTime) ? input.EndBillingTime : input.endTime;
  3470 +
  3471 + if (!string.IsNullOrEmpty(startTimeStr) && !string.IsNullOrEmpty(endTimeStr))
3468 3472 {
3469   - queryBillingTime = new List<string> { input.StartBillingTime, input.EndBillingTime };
3470   - startBillingTime = Ext.GetDateTime(queryBillingTime.First());
3471   - endBillingTime = Ext.GetDateTime(queryBillingTime.Last());
  3473 + // 尝试解析日期字符串(支持多种格式)
  3474 + if (DateTime.TryParse(startTimeStr, out DateTime startDate))
  3475 + {
  3476 + startBillingTime = startDate;
  3477 + }
  3478 + else
  3479 + {
  3480 + throw NCCException.Oh($"开始时间格式错误:{startTimeStr}");
  3481 + }
  3482 +
  3483 + if (DateTime.TryParse(endTimeStr, out DateTime endDate))
  3484 + {
  3485 + endBillingTime = endDate;
  3486 + }
  3487 + else
  3488 + {
  3489 + throw NCCException.Oh($"结束时间格式错误:{endTimeStr}");
  3490 + }
3472 3491 }
3473 3492  
3474 3493 // 优化查询:先分页查询主表,再批量查询关联数据,避免子查询性能问题
... ... @@ -3477,18 +3496,18 @@ namespace NCC.Extend.LqKdKdjlb
3477 3496 .Where(pxmx => pxmx.IsEffective == StatusEnum.有效.GetHashCode())
3478 3497 .WhereIF(!string.IsNullOrEmpty(input.Id), pxmx => pxmx.Id == input.Id)
3479 3498 .WhereIF(!string.IsNullOrEmpty(input.BillingId), pxmx => pxmx.Glkdbh == input.BillingId)
3480   - .WhereIF(queryBillingTime != null && startBillingTime.HasValue, pxmx => pxmx.Yjsj >= new DateTime(startBillingTime.Value.Year, startBillingTime.Value.Month, startBillingTime.Value.Day, 0, 0, 0))
3481   - .WhereIF(queryBillingTime != null && endBillingTime.HasValue, pxmx => pxmx.Yjsj <= new DateTime(endBillingTime.Value.Year, endBillingTime.Value.Month, endBillingTime.Value.Day, 23, 59, 59))
  3499 + .WhereIF(startBillingTime.HasValue, pxmx => pxmx.Yjsj >= new DateTime(startBillingTime.Value.Year, startBillingTime.Value.Month, startBillingTime.Value.Day, 0, 0, 0))
  3500 + .WhereIF(endBillingTime.HasValue, pxmx => pxmx.Yjsj <= new DateTime(endBillingTime.Value.Year, endBillingTime.Value.Month, endBillingTime.Value.Day, 23, 59, 59))
3482 3501 .WhereIF(!string.IsNullOrEmpty(input.ActivityId), pxmx => pxmx.ActivityId == input.ActivityId)
3483 3502 .WhereIF(!string.IsNullOrEmpty(input.MemberId), pxmx => pxmx.MemberId == input.MemberId)
3484 3503 .WhereIF(!string.IsNullOrEmpty(input.ItemId), pxmx => pxmx.Px == input.ItemId)
3485 3504 .WhereIF(!string.IsNullOrEmpty(input.ItemName), pxmx => pxmx.Pxmc != null && pxmx.Pxmc.Contains(input.ItemName))
3486   - .WhereIF(!string.IsNullOrEmpty(input.SourceType), pxmx => pxmx.SourceType == input.SourceType);
  3505 + .WhereIF(!string.IsNullOrEmpty(input.SourceType), pxmx => pxmx.SourceType == input.SourceType)
  3506 + .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType);
3487 3507  
3488 3508 // 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确)
3489 3509 baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any())
3490 3510 .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any())
3491   - .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => SqlFunc.Subqueryable<LqXmzlEntity>().Where(x => x.Id == pxmx.Px && x.Fl4 == input.ItemType).Any())
3492 3511 .WhereIF(!string.IsNullOrEmpty(input.StoreId), pxmx => SqlFunc.Subqueryable<LqKdKdjlbEntity>().Where(x => x.Id == pxmx.Glkdbh && x.Djmd == input.StoreId).Any());
3493 3512  
3494 3513 // 3. 先分页查询主表数据(查询实体类,提高性能)
... ... @@ -3498,7 +3517,6 @@ namespace NCC.Extend.LqKdKdjlb
3498 3517 var itemIds = pagedData.list.Select(x => x.Id).ToList();
3499 3518 var memberIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.MemberId)).Select(x => x.MemberId).Distinct().ToList();
3500 3519 var activityIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.ActivityId)).Select(x => x.ActivityId).Distinct().ToList();
3501   - var projectIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.Px)).Select(x => x.Px).Distinct().ToList();
3502 3520 var billingIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.Glkdbh)).Select(x => x.Glkdbh).Distinct().ToList();
3503 3521  
3504 3522 // 批量查询会员信息
... ... @@ -3517,14 +3535,6 @@ namespace NCC.Extend.LqKdKdjlb
3517 3535 activityDict = activities.ToDictionary(x => x.Id, x => x.ActivityName ?? "");
3518 3536 }
3519 3537  
3520   - // 批量查询项目资料
3521   - var projectDict = new Dictionary<string, string>();
3522   - if (projectIds.Any())
3523   - {
3524   - var projects = await _db.Queryable<LqXmzlEntity>().Where(x => projectIds.Contains(x.Id)).Select(x => new { x.Id, x.Qt2 }).ToListAsync();
3525   - projectDict = projects.ToDictionary(x => x.Id, x => x.Qt2 ?? "");
3526   - }
3527   -
3528 3538 // 批量查询开单记录,获取门店ID
3529 3539 var billingStoreDict = new Dictionary<string, string>();
3530 3540 if (billingIds.Any())
... ... @@ -3552,7 +3562,7 @@ namespace NCC.Extend.LqKdKdjlb
3552 3562 memberName = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Name : "",
3553 3563 memberPhone = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Phone : "",
3554 3564 itemName = pxmx.Pxmc,
3555   - itemType = pxmx.Px != null && projectDict.ContainsKey(pxmx.Px) ? projectDict[pxmx.Px] : "",
  3565 + itemType = pxmx.ItemCategory,
3556 3566 actualPrice = pxmx.ActualPrice,
3557 3567 projectNumber = pxmx.ProjectNumber,
3558 3568 sourceType = pxmx.SourceType,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqLaundryFlowService.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +using System.Linq;
  4 +using System.Threading.Tasks;
  5 +using Microsoft.AspNetCore.Mvc;
  6 +using Microsoft.Extensions.Logging;
  7 +using NCC.Common.Core.Manager;
  8 +using NCC.Common.Enum;
  9 +using NCC.Common.Filter;
  10 +using NCC.Dependency;
  11 +using NCC.DynamicApiController;
  12 +using NCC.Extend.Entitys.Dto.LqLaundryFlow;
  13 +using NCC.Extend.Entitys.Enum;
  14 +using NCC.Extend.Entitys.lq_laundry_flow;
  15 +using NCC.Extend.Entitys.lq_laundry_supplier;
  16 +using NCC.Extend.Entitys.lq_mdxx;
  17 +using NCC.Extend.Interfaces.LqLaundryFlow;
  18 +using NCC.FriendlyException;
  19 +using NCC.System.Entitys.Permission;
  20 +using SqlSugar;
  21 +using Yitter.IdGenerator;
  22 +
  23 +namespace NCC.Extend
  24 +{
  25 + /// <summary>
  26 + /// 清洗流水服务
  27 + /// </summary>
  28 + [ApiDescriptionSettings(Tag = "绿纤清洗流水管理", Name = "LqLaundryFlow", Order = 200)]
  29 + [Route("api/Extend/LqLaundryFlow")]
  30 + public class LqLaundryFlowService : IDynamicApiController, ITransient, ILqLaundryFlowService
  31 + {
  32 + private readonly IUserManager _userManager;
  33 + private readonly ILogger<LqLaundryFlowService> _logger;
  34 + private readonly ISqlSugarClient _db;
  35 +
  36 + /// <summary>
  37 + /// 构造函数
  38 + /// </summary>
  39 + public LqLaundryFlowService(IUserManager userManager, ILogger<LqLaundryFlowService> logger, ISqlSugarClient db)
  40 + {
  41 + _userManager = userManager;
  42 + _logger = logger;
  43 + _db = db;
  44 + }
  45 +
  46 + #region 创建送出记录
  47 + /// <summary>
  48 + /// 创建送出记录
  49 + /// </summary>
  50 + /// <remarks>
  51 + /// 门店选择清洗商和产品,填写送出数量,创建送出记录
  52 + ///
  53 + /// 示例请求:
  54 + /// ```json
  55 + /// {
  56 + /// "storeId": "门店ID",
  57 + /// "productType": "毛巾",
  58 + /// "laundrySupplierId": "清洗商ID",
  59 + /// "quantity": 100,
  60 + /// "remark": "备注"
  61 + /// }
  62 + /// ```
  63 + /// </remarks>
  64 + /// <param name="input">送出输入</param>
  65 + /// <returns>创建结果(包含批次号)</returns>
  66 + /// <response code="200">创建成功</response>
  67 + /// <response code="400">参数错误或清洗商不存在</response>
  68 + /// <response code="500">服务器错误</response>
  69 + [HttpPost("Send")]
  70 + public async Task<dynamic> SendAsync([FromBody] LqLaundryFlowSendInput input)
  71 + {
  72 + try
  73 + {
  74 + // 验证门店是否存在
  75 + var store = await _db.Queryable<LqMdxxEntity>()
  76 + .Where(x => x.Id == input.StoreId)
  77 + .FirstAsync();
  78 +
  79 + if (store == null)
  80 + {
  81 + throw NCCException.Oh("门店不存在");
  82 + }
  83 +
  84 + // 验证清洗商是否存在
  85 + var supplier = await _db.Queryable<LqLaundrySupplierEntity>()
  86 + .Where(x => x.Id == input.LaundrySupplierId && x.IsEffective == StatusEnum.有效.GetHashCode())
  87 + .FirstAsync();
  88 +
  89 + if (supplier == null)
  90 + {
  91 + throw NCCException.Oh("清洗商不存在或已失效");
  92 + }
  93 +
  94 + // 验证产品类型是否匹配
  95 + if (supplier.ProductType != input.ProductType)
  96 + {
  97 + throw NCCException.Oh($"清洗商【{supplier.SupplierName}】不支持清洗产品类型【{input.ProductType}】");
  98 + }
  99 +
  100 + // 生成批次号(使用ID)
  101 + var batchId = YitIdHelper.NextId().ToString();
  102 +
  103 + // 创建送出记录
  104 + var entity = new LqLaundryFlowEntity
  105 + {
  106 + Id = batchId,
  107 + FlowType = 0, // 送出
  108 + BatchNumber = batchId, // 批次号等于ID
  109 + StoreId = input.StoreId,
  110 + ProductType = input.ProductType,
  111 + LaundrySupplierId = input.LaundrySupplierId,
  112 + Quantity = input.Quantity,
  113 + LaundryPrice = supplier.LaundryPrice, // 记录历史价格
  114 + TotalPrice = 0, // 送出时总费用为0
  115 + Remark = input.Remark,
  116 + IsEffective = StatusEnum.有效.GetHashCode(),
  117 + CreateUser = _userManager.UserId,
  118 + CreateTime = DateTime.Now
  119 + };
  120 +
  121 + var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
  122 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  123 +
  124 + return new { batchNumber = batchId, message = "送出记录创建成功" };
  125 + }
  126 + catch (Exception ex)
  127 + {
  128 + _logger.LogError(ex, "创建送出记录失败");
  129 + throw NCCException.Oh($"创建失败:{ex.Message}");
  130 + }
  131 + }
  132 + #endregion
  133 +
  134 + #region 创建送回记录
  135 + /// <summary>
  136 + /// 创建送回记录
  137 + /// </summary>
  138 + /// <remarks>
  139 + /// 清洗完毕后,填写送回清洗商、送回数量,创建送回记录
  140 + ///
  141 + /// 示例请求:
  142 + /// ```json
  143 + /// {
  144 + /// "batchNumber": "批次号(对应的送出记录的ID)",
  145 + /// "laundrySupplierId": "清洗商ID",
  146 + /// "quantity": 95,
  147 + /// "remark": "5条损坏"
  148 + /// }
  149 + /// ```
  150 + /// </remarks>
  151 + /// <param name="input">送回输入</param>
  152 + /// <returns>创建结果</returns>
  153 + /// <response code="200">创建成功</response>
  154 + /// <response code="400">批次号不存在或参数错误</response>
  155 + /// <response code="500">服务器错误</response>
  156 + [HttpPost("Return")]
  157 + public async Task<dynamic> ReturnAsync([FromBody] LqLaundryFlowReturnInput input)
  158 + {
  159 + try
  160 + {
  161 + // 验证对应的送出记录是否存在
  162 + var sendRecord = await _db.Queryable<LqLaundryFlowEntity>()
  163 + .Where(x => x.BatchNumber == input.BatchNumber && x.FlowType == 0 && x.IsEffective == StatusEnum.有效.GetHashCode())
  164 + .FirstAsync();
  165 +
  166 + if (sendRecord == null)
  167 + {
  168 + throw NCCException.Oh("对应的送出记录不存在或已失效");
  169 + }
  170 +
  171 + // 检查是否已经存在送回记录
  172 + var existingReturn = await _db.Queryable<LqLaundryFlowEntity>()
  173 + .Where(x => x.BatchNumber == input.BatchNumber && x.FlowType == 1 && x.IsEffective == StatusEnum.有效.GetHashCode())
  174 + .FirstAsync();
  175 +
  176 + if (existingReturn != null)
  177 + {
  178 + throw NCCException.Oh("该批次已存在送回记录");
  179 + }
  180 +
  181 + // 验证清洗商是否存在
  182 + var supplier = await _db.Queryable<LqLaundrySupplierEntity>()
  183 + .Where(x => x.Id == input.LaundrySupplierId && x.IsEffective == StatusEnum.有效.GetHashCode())
  184 + .FirstAsync();
  185 +
  186 + if (supplier == null)
  187 + {
  188 + throw NCCException.Oh("清洗商不存在或已失效");
  189 + }
  190 +
  191 + // 验证产品类型是否匹配
  192 + if (supplier.ProductType != sendRecord.ProductType)
  193 + {
  194 + throw NCCException.Oh($"清洗商【{supplier.SupplierName}】不支持清洗产品类型【{sendRecord.ProductType}】");
  195 + }
  196 +
  197 + // 计算总费用(送回数量 × 清洗单价)
  198 + var totalPrice = input.Quantity * supplier.LaundryPrice;
  199 +
  200 + // 创建送回记录
  201 + var entity = new LqLaundryFlowEntity
  202 + {
  203 + Id = YitIdHelper.NextId().ToString(),
  204 + FlowType = 1, // 送回
  205 + BatchNumber = input.BatchNumber, // 使用送出记录的批次号
  206 + StoreId = sendRecord.StoreId,
  207 + ProductType = sendRecord.ProductType,
  208 + LaundrySupplierId = input.LaundrySupplierId,
  209 + Quantity = input.Quantity,
  210 + LaundryPrice = supplier.LaundryPrice, // 记录历史价格(可能已变化)
  211 + TotalPrice = totalPrice,
  212 + Remark = input.Remark,
  213 + IsEffective = StatusEnum.有效.GetHashCode(),
  214 + CreateUser = _userManager.UserId,
  215 + CreateTime = DateTime.Now
  216 + };
  217 +
  218 + var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
  219 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  220 +
  221 + return new { message = "送回记录创建成功", totalPrice = totalPrice };
  222 + }
  223 + catch (Exception ex)
  224 + {
  225 + _logger.LogError(ex, "创建送回记录失败");
  226 + throw NCCException.Oh($"创建失败:{ex.Message}");
  227 + }
  228 + }
  229 + #endregion
  230 +
  231 + #region 获取清洗流水列表
  232 + /// <summary>
  233 + /// 获取清洗流水列表
  234 + /// </summary>
  235 + /// <remarks>
  236 + /// 分页查询清洗流水列表,支持按流水类型、批次号、门店、产品类型、清洗商、时间范围筛选
  237 + /// </remarks>
  238 + /// <param name="input">查询输入</param>
  239 + /// <returns>清洗流水列表</returns>
  240 + /// <response code="200">查询成功</response>
  241 + /// <response code="500">服务器错误</response>
  242 + [HttpGet("GetList")]
  243 + public async Task<dynamic> GetListAsync([FromQuery] LqLaundryFlowListQueryInput input)
  244 + {
  245 + try
  246 + {
  247 + var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx;
  248 + var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
  249 +
  250 + var data = await _db.Queryable<LqLaundryFlowEntity, LqMdxxEntity, LqLaundrySupplierEntity>(
  251 + (flow, store, supplier) => flow.StoreId == store.Id && flow.LaundrySupplierId == supplier.Id)
  252 + .WhereIF(input.FlowType.HasValue, (flow, store, supplier) => flow.FlowType == input.FlowType.Value)
  253 + .WhereIF(!string.IsNullOrWhiteSpace(input.BatchNumber), (flow, store, supplier) => flow.BatchNumber == input.BatchNumber)
  254 + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (flow, store, supplier) => flow.StoreId == input.StoreId)
  255 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductType), (flow, store, supplier) => flow.ProductType == input.ProductType)
  256 + .WhereIF(!string.IsNullOrWhiteSpace(input.LaundrySupplierId), (flow, store, supplier) => flow.LaundrySupplierId == input.LaundrySupplierId)
  257 + .WhereIF(input.StartTime.HasValue, (flow, store, supplier) => flow.CreateTime >= input.StartTime.Value)
  258 + .WhereIF(input.EndTime.HasValue, (flow, store, supplier) => flow.CreateTime <= input.EndTime.Value)
  259 + .WhereIF(input.IsEffective.HasValue, (flow, store, supplier) => flow.IsEffective == input.IsEffective.Value)
  260 + .Select((flow, store, supplier) => new LqLaundryFlowListOutput
  261 + {
  262 + id = flow.Id,
  263 + flowType = flow.FlowType,
  264 + flowTypeName = flow.FlowType == 0 ? "送出" : "送回",
  265 + batchNumber = flow.BatchNumber,
  266 + storeId = flow.StoreId,
  267 + storeName = store.Dm ?? "",
  268 + productType = flow.ProductType,
  269 + laundrySupplierId = flow.LaundrySupplierId,
  270 + laundrySupplierName = supplier.SupplierName ?? "",
  271 + quantity = flow.Quantity,
  272 + laundryPrice = flow.LaundryPrice,
  273 + totalPrice = flow.TotalPrice,
  274 + remark = flow.Remark,
  275 + isEffective = flow.IsEffective,
  276 + createUser = flow.CreateUser,
  277 + createUserName = "",
  278 + createTime = flow.CreateTime
  279 + })
  280 + .MergeTable()
  281 + .OrderBy(sidx + " " + sort)
  282 + .ToPagedListAsync(input.currentPage, input.pageSize);
  283 +
  284 + // 补充用户名称信息
  285 + var userIds = data.list.Select(x => x.createUser)
  286 + .Where(x => !string.IsNullOrEmpty(x))
  287 + .Distinct()
  288 + .ToList();
  289 +
  290 + if (userIds.Any())
  291 + {
  292 + var userList = await _db.Queryable<UserEntity>()
  293 + .Where(x => userIds.Contains(x.Id))
  294 + .Select(x => new { x.Id, x.RealName })
  295 + .ToListAsync();
  296 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  297 +
  298 + foreach (var item in data.list)
  299 + {
  300 + item.createUserName = userDict.ContainsKey(item.createUser) ? userDict[item.createUser] : "";
  301 + }
  302 + }
  303 +
  304 + return PageResult<LqLaundryFlowListOutput>.SqlSugarPageResult(data);
  305 + }
  306 + catch (Exception ex)
  307 + {
  308 + _logger.LogError(ex, "获取清洗流水列表失败");
  309 + throw NCCException.Oh($"查询失败:{ex.Message}");
  310 + }
  311 + }
  312 + #endregion
  313 +
  314 + #region 获取清洗流水详情
  315 + /// <summary>
  316 + /// 获取清洗流水详情
  317 + /// </summary>
  318 + /// <remarks>
  319 + /// 根据ID获取清洗流水的详细信息
  320 + /// </remarks>
  321 + /// <param name="id">流水ID</param>
  322 + /// <returns>流水详情</returns>
  323 + /// <response code="200">查询成功</response>
  324 + /// <response code="400">记录不存在</response>
  325 + /// <response code="500">服务器错误</response>
  326 + [HttpGet("{id}")]
  327 + public async Task<LqLaundryFlowInfoOutput> GetInfoAsync(string id)
  328 + {
  329 + try
  330 + {
  331 + var entity = await _db.Queryable<LqLaundryFlowEntity, LqMdxxEntity, LqLaundrySupplierEntity>(
  332 + (flow, store, supplier) => flow.StoreId == store.Id && flow.LaundrySupplierId == supplier.Id)
  333 + .Where((flow, store, supplier) => flow.Id == id)
  334 + .Select((flow, store, supplier) => new LqLaundryFlowInfoOutput
  335 + {
  336 + id = flow.Id,
  337 + flowType = flow.FlowType,
  338 + flowTypeName = flow.FlowType == 0 ? "送出" : "送回",
  339 + batchNumber = flow.BatchNumber,
  340 + storeId = flow.StoreId,
  341 + storeName = store.Dm ?? "",
  342 + productType = flow.ProductType,
  343 + laundrySupplierId = flow.LaundrySupplierId,
  344 + laundrySupplierName = supplier.SupplierName ?? "",
  345 + quantity = flow.Quantity,
  346 + laundryPrice = flow.LaundryPrice,
  347 + totalPrice = flow.TotalPrice,
  348 + remark = flow.Remark,
  349 + isEffective = flow.IsEffective,
  350 + createUser = flow.CreateUser,
  351 + createUserName = "",
  352 + createTime = flow.CreateTime
  353 + })
  354 + .FirstAsync();
  355 +
  356 + if (entity == null)
  357 + {
  358 + throw NCCException.Oh("流水记录不存在");
  359 + }
  360 +
  361 + // 补充用户名称
  362 + if (!string.IsNullOrEmpty(entity.createUser))
  363 + {
  364 + var createUser = await _db.Queryable<UserEntity>()
  365 + .Where(x => x.Id == entity.createUser)
  366 + .Select(x => x.RealName)
  367 + .FirstAsync();
  368 + entity.createUserName = createUser ?? "";
  369 + }
  370 +
  371 + return entity;
  372 + }
  373 + catch (Exception ex)
  374 + {
  375 + _logger.LogError(ex, "获取清洗流水详情失败");
  376 + throw NCCException.Oh($"查询失败:{ex.Message}");
  377 + }
  378 + }
  379 + #endregion
  380 +
  381 + #region 查询差异记录
  382 + /// <summary>
  383 + /// 查询差异记录(送出数量 > 送回数量)
  384 + /// </summary>
  385 + /// <remarks>
  386 + /// 查询所有送出数量大于送回数量的记录,用于追踪差异来源
  387 + /// </remarks>
  388 + /// <param name="input">查询输入(支持分页)</param>
  389 + /// <returns>差异记录列表</returns>
  390 + /// <response code="200">查询成功</response>
  391 + /// <response code="500">服务器错误</response>
  392 + [HttpPost("GetDifferenceList")]
  393 + public async Task<dynamic> GetDifferenceListAsync([FromBody] LqLaundryFlowListQueryInput input)
  394 + {
  395 + try
  396 + {
  397 + // 查询所有送出记录
  398 + var sendRecords = await _db.Queryable<LqLaundryFlowEntity, LqMdxxEntity>(
  399 + (flow, store) => flow.StoreId == store.Id)
  400 + .Where((flow, store) => flow.FlowType == 0 && flow.IsEffective == StatusEnum.有效.GetHashCode())
  401 + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (flow, store) => flow.StoreId == input.StoreId)
  402 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductType), (flow, store) => flow.ProductType == input.ProductType)
  403 + .WhereIF(input.StartTime.HasValue, (flow, store) => flow.CreateTime >= input.StartTime.Value)
  404 + .WhereIF(input.EndTime.HasValue, (flow, store) => flow.CreateTime <= input.EndTime.Value)
  405 + .Select((flow, store) => new
  406 + {
  407 + flow.BatchNumber,
  408 + flow.StoreId,
  409 + StoreName = store.Dm ?? "",
  410 + flow.ProductType,
  411 + SendQuantity = flow.Quantity,
  412 + SendTime = flow.CreateTime
  413 + })
  414 + .ToListAsync();
  415 +
  416 + if (!sendRecords.Any())
  417 + {
  418 + return new
  419 + {
  420 + list = new List<LqLaundryFlowDifferenceOutput>(),
  421 + pagination = new
  422 + {
  423 + page = input.currentPage,
  424 + pageSize = input.pageSize,
  425 + total = 0
  426 + }
  427 + };
  428 + }
  429 +
  430 + // 查询所有送回记录
  431 + var returnRecords = await _db.Queryable<LqLaundryFlowEntity>()
  432 + .Where(x => x.FlowType == 1 && x.IsEffective == StatusEnum.有效.GetHashCode())
  433 + .Select(x => new
  434 + {
  435 + x.BatchNumber,
  436 + ReturnQuantity = x.Quantity,
  437 + ReturnTime = x.CreateTime,
  438 + x.Remark
  439 + })
  440 + .ToListAsync();
  441 +
  442 + var returnDict = returnRecords
  443 + .GroupBy(x => x.BatchNumber)
  444 + .ToDictionary(g => g.Key, g => g.First());
  445 +
  446 + // 计算差异
  447 + var differenceList = sendRecords
  448 + .Select(send => new LqLaundryFlowDifferenceOutput
  449 + {
  450 + batchNumber = send.BatchNumber,
  451 + storeId = send.StoreId,
  452 + storeName = send.StoreName,
  453 + productType = send.ProductType,
  454 + sendQuantity = send.SendQuantity,
  455 + returnQuantity = returnDict.ContainsKey(send.BatchNumber) ? returnDict[send.BatchNumber].ReturnQuantity : 0,
  456 + differenceQuantity = send.SendQuantity - (returnDict.ContainsKey(send.BatchNumber) ? returnDict[send.BatchNumber].ReturnQuantity : 0),
  457 + differenceRemark = returnDict.ContainsKey(send.BatchNumber) ? returnDict[send.BatchNumber].Remark : "",
  458 + sendTime = send.SendTime,
  459 + returnTime = returnDict.ContainsKey(send.BatchNumber) ? returnDict[send.BatchNumber].ReturnTime : (DateTime?)null
  460 + })
  461 + .Where(x => x.differenceQuantity > 0)
  462 + .ToList();
  463 +
  464 + // 手动分页
  465 + var totalCount = differenceList.Count;
  466 + var pagedList = differenceList
  467 + .Skip((input.currentPage - 1) * input.pageSize)
  468 + .Take(input.pageSize)
  469 + .ToList();
  470 +
  471 + return new
  472 + {
  473 + list = pagedList,
  474 + pagination = new
  475 + {
  476 + page = input.currentPage,
  477 + pageSize = input.pageSize,
  478 + total = totalCount
  479 + }
  480 + };
  481 + }
  482 + catch (Exception ex)
  483 + {
  484 + _logger.LogError(ex, "查询差异记录失败");
  485 + throw NCCException.Oh($"查询失败:{ex.Message}");
  486 + }
  487 + }
  488 + #endregion
  489 +
  490 + #region 门店每月清洗费用统计
  491 + /// <summary>
  492 + /// 门店每月清洗费用统计
  493 + /// </summary>
  494 + /// <remarks>
  495 + /// 统计每个门店每月的清洗费用(只统计送回记录)
  496 + ///
  497 + /// 示例请求:
  498 + /// ```json
  499 + /// {
  500 + /// "startMonth": "202411",
  501 + /// "endMonth": "202412",
  502 + /// "storeId": "门店ID(可选)"
  503 + /// }
  504 + /// ```
  505 + /// </remarks>
  506 + /// <param name="input">统计输入</param>
  507 + /// <returns>统计结果</returns>
  508 + /// <response code="200">统计成功</response>
  509 + /// <response code="500">服务器错误</response>
  510 + [HttpPost("GetStoreMonthlyStatistics")]
  511 + public async Task<List<LqLaundryStatisticsOutput>> GetStoreMonthlyStatisticsAsync([FromBody] LaundryStatisticsInput input)
  512 + {
  513 + try
  514 + {
  515 + // 构建月份过滤条件
  516 + var startDate = (DateTime?)null;
  517 + var endDate = (DateTime?)null;
  518 +
  519 + if (!string.IsNullOrWhiteSpace(input.StartMonth) && input.StartMonth.Length == 6)
  520 + {
  521 + var year = int.Parse(input.StartMonth.Substring(0, 4));
  522 + var month = int.Parse(input.StartMonth.Substring(4, 2));
  523 + startDate = new DateTime(year, month, 1);
  524 + }
  525 +
  526 + if (!string.IsNullOrWhiteSpace(input.EndMonth) && input.EndMonth.Length == 6)
  527 + {
  528 + var year = int.Parse(input.EndMonth.Substring(0, 4));
  529 + var month = int.Parse(input.EndMonth.Substring(4, 2));
  530 + endDate = new DateTime(year, month, DateTime.DaysInMonth(year, month), 23, 59, 59);
  531 + }
  532 +
  533 + var query = _db.Queryable<LqLaundryFlowEntity, LqMdxxEntity>(
  534 + (flow, store) => flow.StoreId == store.Id)
  535 + .Where((flow, store) => flow.FlowType == 1 && flow.IsEffective == StatusEnum.有效.GetHashCode())
  536 + .WhereIF(startDate.HasValue, (flow, store) => flow.CreateTime >= startDate.Value)
  537 + .WhereIF(endDate.HasValue, (flow, store) => flow.CreateTime <= endDate.Value)
  538 + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (flow, store) => flow.StoreId == input.StoreId);
  539 +
  540 + var allRecords = await query
  541 + .Select((flow, store) => new
  542 + {
  543 + flow.StoreId,
  544 + StoreName = store.Dm ?? "",
  545 + flow.CreateTime,
  546 + flow.TotalPrice,
  547 + flow.Id
  548 + })
  549 + .ToListAsync();
  550 +
  551 + // 在内存中分组统计
  552 + var result = allRecords
  553 + .GroupBy(x => new { x.StoreId, x.StoreName, Month = x.CreateTime.ToString("yyyyMM") })
  554 + .Select(g => new LqLaundryStatisticsOutput
  555 + {
  556 + storeId = g.Key.StoreId,
  557 + storeName = g.Key.StoreName,
  558 + statisticsMonth = g.Key.Month,
  559 + totalPrice = g.Sum(x => x.TotalPrice),
  560 + count = g.Count()
  561 + })
  562 + .OrderBy(x => x.storeId)
  563 + .ThenBy(x => x.statisticsMonth)
  564 + .ToList();
  565 +
  566 + return result;
  567 + }
  568 + catch (Exception ex)
  569 + {
  570 + _logger.LogError(ex, "门店每月清洗费用统计失败");
  571 + throw NCCException.Oh($"统计失败:{ex.Message}");
  572 + }
  573 + }
  574 + #endregion
  575 +
  576 + #region 产品每月清洗费用统计
  577 + /// <summary>
  578 + /// 产品每月清洗费用统计
  579 + /// </summary>
  580 + /// <remarks>
  581 + /// 统计每个产品每月的清洗费用(只统计送回记录)
  582 + ///
  583 + /// 示例请求:
  584 + /// ```json
  585 + /// {
  586 + /// "startMonth": "202411",
  587 + /// "endMonth": "202412",
  588 + /// "productType": "毛巾(可选)"
  589 + /// }
  590 + /// ```
  591 + /// </remarks>
  592 + /// <param name="input">统计输入</param>
  593 + /// <returns>统计结果</returns>
  594 + /// <response code="200">统计成功</response>
  595 + /// <response code="500">服务器错误</response>
  596 + [HttpPost("GetProductMonthlyStatistics")]
  597 + public async Task<List<LqLaundryStatisticsOutput>> GetProductMonthlyStatisticsAsync([FromBody] LaundryStatisticsInput input)
  598 + {
  599 + try
  600 + {
  601 + // 构建月份过滤条件
  602 + var startDate = (DateTime?)null;
  603 + var endDate = (DateTime?)null;
  604 +
  605 + if (!string.IsNullOrWhiteSpace(input.StartMonth) && input.StartMonth.Length == 6)
  606 + {
  607 + var year = int.Parse(input.StartMonth.Substring(0, 4));
  608 + var month = int.Parse(input.StartMonth.Substring(4, 2));
  609 + startDate = new DateTime(year, month, 1);
  610 + }
  611 +
  612 + if (!string.IsNullOrWhiteSpace(input.EndMonth) && input.EndMonth.Length == 6)
  613 + {
  614 + var year = int.Parse(input.EndMonth.Substring(0, 4));
  615 + var month = int.Parse(input.EndMonth.Substring(4, 2));
  616 + endDate = new DateTime(year, month, DateTime.DaysInMonth(year, month), 23, 59, 59);
  617 + }
  618 +
  619 + var query = _db.Queryable<LqLaundryFlowEntity>()
  620 + .Where(x => x.FlowType == 1 && x.IsEffective == StatusEnum.有效.GetHashCode())
  621 + .WhereIF(startDate.HasValue, x => x.CreateTime >= startDate.Value)
  622 + .WhereIF(endDate.HasValue, x => x.CreateTime <= endDate.Value)
  623 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductType), x => x.ProductType == input.ProductType);
  624 +
  625 + var allRecords = await query
  626 + .Select(x => new
  627 + {
  628 + x.ProductType,
  629 + x.CreateTime,
  630 + x.TotalPrice,
  631 + x.Id
  632 + })
  633 + .ToListAsync();
  634 +
  635 + // 在内存中分组统计
  636 + var result = allRecords
  637 + .GroupBy(x => new { x.ProductType, Month = x.CreateTime.ToString("yyyyMM") })
  638 + .Select(g => new LqLaundryStatisticsOutput
  639 + {
  640 + productType = g.Key.ProductType,
  641 + statisticsMonth = g.Key.Month,
  642 + totalPrice = g.Sum(x => x.TotalPrice),
  643 + count = g.Count()
  644 + })
  645 + .OrderBy(x => x.productType)
  646 + .ThenBy(x => x.statisticsMonth)
  647 + .ToList();
  648 +
  649 + return result;
  650 + }
  651 + catch (Exception ex)
  652 + {
  653 + _logger.LogError(ex, "产品每月清洗费用统计失败");
  654 + throw NCCException.Oh($"统计失败:{ex.Message}");
  655 + }
  656 + }
  657 + #endregion
  658 + }
  659 +}
  660 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqLaundrySupplierService.cs 0 → 100644
  1 +using System;
  2 +using System.Linq;
  3 +using System.Threading.Tasks;
  4 +using Microsoft.AspNetCore.Mvc;
  5 +using Microsoft.Extensions.Logging;
  6 +using NCC.Common.Core.Manager;
  7 +using NCC.Common.Enum;
  8 +using NCC.Common.Filter;
  9 +using NCC.Dependency;
  10 +using NCC.DynamicApiController;
  11 +using NCC.Extend.Entitys.Dto.LqLaundrySupplier;
  12 +using NCC.Extend.Entitys.Enum;
  13 +using NCC.Extend.Entitys.lq_laundry_supplier;
  14 +using NCC.Extend.Interfaces.LqLaundrySupplier;
  15 +using NCC.FriendlyException;
  16 +using NCC.System.Entitys.Permission;
  17 +using SqlSugar;
  18 +using Yitter.IdGenerator;
  19 +
  20 +namespace NCC.Extend
  21 +{
  22 + /// <summary>
  23 + /// 清洗商服务
  24 + /// </summary>
  25 + [ApiDescriptionSettings(Tag = "绿纤清洗商管理", Name = "LqLaundrySupplier", Order = 200)]
  26 + [Route("api/Extend/LqLaundrySupplier")]
  27 + public class LqLaundrySupplierService : IDynamicApiController, ITransient, ILqLaundrySupplierService
  28 + {
  29 + private readonly IUserManager _userManager;
  30 + private readonly ILogger<LqLaundrySupplierService> _logger;
  31 + private readonly ISqlSugarClient _db;
  32 +
  33 + /// <summary>
  34 + /// 构造函数
  35 + /// </summary>
  36 + public LqLaundrySupplierService(IUserManager userManager, ILogger<LqLaundrySupplierService> logger, ISqlSugarClient db)
  37 + {
  38 + _userManager = userManager;
  39 + _logger = logger;
  40 + _db = db;
  41 + }
  42 +
  43 + #region 创建清洗商
  44 + /// <summary>
  45 + /// 创建清洗商
  46 + /// </summary>
  47 + /// <remarks>
  48 + /// 创建清洗商记录,一个清洗商对应一个产品类型一条记录
  49 + ///
  50 + /// 示例请求:
  51 + /// ```json
  52 + /// {
  53 + /// "supplierName": "清洗商A",
  54 + /// "productType": "毛巾",
  55 + /// "laundryPrice": 5.00,
  56 + /// "remark": "备注信息"
  57 + /// }
  58 + /// ```
  59 + /// </remarks>
  60 + /// <param name="input">创建输入</param>
  61 + /// <returns>创建结果</returns>
  62 + /// <response code="200">创建成功</response>
  63 + /// <response code="400">清洗商已存在或参数错误</response>
  64 + /// <response code="500">服务器错误</response>
  65 + [HttpPost("Create")]
  66 + public async Task CreateAsync([FromBody] LqLaundrySupplierCrInput input)
  67 + {
  68 + try
  69 + {
  70 + // 检查是否已存在相同清洗商和产品类型的记录
  71 + var existing = await _db.Queryable<LqLaundrySupplierEntity>()
  72 + .Where(x => x.SupplierName == input.SupplierName
  73 + && x.ProductType == input.ProductType
  74 + && x.IsEffective == StatusEnum.有效.GetHashCode())
  75 + .FirstAsync();
  76 +
  77 + if (existing != null)
  78 + {
  79 + throw NCCException.Oh($"清洗商【{input.SupplierName}】已存在{input.ProductType}的记录");
  80 + }
  81 +
  82 + // 创建清洗商记录
  83 + var entity = new LqLaundrySupplierEntity
  84 + {
  85 + Id = YitIdHelper.NextId().ToString(),
  86 + SupplierName = input.SupplierName,
  87 + ProductType = input.ProductType,
  88 + LaundryPrice = input.LaundryPrice,
  89 + Remark = input.Remark,
  90 + IsEffective = StatusEnum.有效.GetHashCode(),
  91 + CreateUser = _userManager.UserId,
  92 + CreateTime = DateTime.Now
  93 + };
  94 +
  95 + var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
  96 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  97 + }
  98 + catch (Exception ex)
  99 + {
  100 + _logger.LogError(ex, "创建清洗商失败");
  101 + throw NCCException.Oh($"创建失败:{ex.Message}");
  102 + }
  103 + }
  104 + #endregion
  105 +
  106 + #region 更新清洗商
  107 + /// <summary>
  108 + /// 更新清洗商
  109 + /// </summary>
  110 + /// <remarks>
  111 + /// 更新清洗商信息,包括价格(更新价格不影响历史流水记录的价格)
  112 + /// </remarks>
  113 + /// <param name="input">更新输入</param>
  114 + /// <returns>更新结果</returns>
  115 + /// <response code="200">更新成功</response>
  116 + /// <response code="400">记录不存在或参数错误</response>
  117 + /// <response code="500">服务器错误</response>
  118 + [HttpPut("Update")]
  119 + public async Task UpdateAsync([FromBody] LqLaundrySupplierUpInput input)
  120 + {
  121 + try
  122 + {
  123 + var existing = await _db.Queryable<LqLaundrySupplierEntity>()
  124 + .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
  125 + .FirstAsync();
  126 +
  127 + if (existing == null)
  128 + {
  129 + throw NCCException.Oh("清洗商记录不存在或已失效");
  130 + }
  131 +
  132 + // 如果清洗商名称或产品类型改变,检查是否冲突
  133 + if (existing.SupplierName != input.SupplierName || existing.ProductType != input.ProductType)
  134 + {
  135 + var conflict = await _db.Queryable<LqLaundrySupplierEntity>()
  136 + .Where(x => x.Id != input.Id
  137 + && x.SupplierName == input.SupplierName
  138 + && x.ProductType == input.ProductType
  139 + && x.IsEffective == StatusEnum.有效.GetHashCode())
  140 + .FirstAsync();
  141 +
  142 + if (conflict != null)
  143 + {
  144 + throw NCCException.Oh($"清洗商【{input.SupplierName}】已存在{input.ProductType}的记录");
  145 + }
  146 + }
  147 +
  148 + // 更新记录
  149 + existing.SupplierName = input.SupplierName;
  150 + existing.ProductType = input.ProductType;
  151 + existing.LaundryPrice = input.LaundryPrice;
  152 + existing.Remark = input.Remark;
  153 + existing.UpdateUser = _userManager.UserId;
  154 + existing.UpdateTime = DateTime.Now;
  155 +
  156 + var isOk = await _db.Updateable(existing).ExecuteCommandAsync();
  157 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  158 + }
  159 + catch (Exception ex)
  160 + {
  161 + _logger.LogError(ex, "更新清洗商失败");
  162 + throw NCCException.Oh($"更新失败:{ex.Message}");
  163 + }
  164 + }
  165 + #endregion
  166 +
  167 + #region 获取清洗商列表
  168 + /// <summary>
  169 + /// 获取清洗商列表
  170 + /// </summary>
  171 + /// <remarks>
  172 + /// 分页查询清洗商列表,支持按清洗商名称、产品类型筛选
  173 + /// </remarks>
  174 + /// <param name="input">查询输入</param>
  175 + /// <returns>清洗商列表</returns>
  176 + /// <response code="200">查询成功</response>
  177 + /// <response code="500">服务器错误</response>
  178 + [HttpGet("GetList")]
  179 + public async Task<dynamic> GetListAsync([FromQuery] LqLaundrySupplierListQueryInput input)
  180 + {
  181 + try
  182 + {
  183 + var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx;
  184 + var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
  185 +
  186 + var data = await _db.Queryable<LqLaundrySupplierEntity>()
  187 + .WhereIF(!string.IsNullOrWhiteSpace(input.SupplierName), x => x.SupplierName.Contains(input.SupplierName))
  188 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductType), x => x.ProductType == input.ProductType)
  189 + .WhereIF(input.IsEffective.HasValue, x => x.IsEffective == input.IsEffective.Value)
  190 + .Select(x => new LqLaundrySupplierListOutput
  191 + {
  192 + id = x.Id,
  193 + supplierName = x.SupplierName,
  194 + productType = x.ProductType,
  195 + laundryPrice = x.LaundryPrice,
  196 + remark = x.Remark,
  197 + isEffective = x.IsEffective,
  198 + createUser = x.CreateUser,
  199 + createUserName = "",
  200 + createTime = x.CreateTime,
  201 + updateUser = x.UpdateUser,
  202 + updateUserName = "",
  203 + updateTime = x.UpdateTime
  204 + })
  205 + .MergeTable()
  206 + .OrderBy(sidx + " " + sort)
  207 + .ToPagedListAsync(input.currentPage, input.pageSize);
  208 +
  209 + // 补充用户名称信息
  210 + var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser })
  211 + .Where(x => !string.IsNullOrEmpty(x))
  212 + .Distinct()
  213 + .ToList();
  214 +
  215 + if (userIds.Any())
  216 + {
  217 + var userList = await _db.Queryable<UserEntity>()
  218 + .Where(x => userIds.Contains(x.Id))
  219 + .Select(x => new { x.Id, x.RealName })
  220 + .ToListAsync();
  221 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  222 +
  223 + foreach (var item in data.list)
  224 + {
  225 + item.createUserName = userDict.ContainsKey(item.createUser) ? userDict[item.createUser] : "";
  226 + item.updateUserName = !string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser) ? userDict[item.updateUser] : "";
  227 + }
  228 + }
  229 +
  230 + return PageResult<LqLaundrySupplierListOutput>.SqlSugarPageResult(data);
  231 + }
  232 + catch (Exception ex)
  233 + {
  234 + _logger.LogError(ex, "获取清洗商列表失败");
  235 + throw NCCException.Oh($"查询失败:{ex.Message}");
  236 + }
  237 + }
  238 + #endregion
  239 +
  240 + #region 获取清洗商详情
  241 + /// <summary>
  242 + /// 获取清洗商详情
  243 + /// </summary>
  244 + /// <remarks>
  245 + /// 根据ID获取清洗商的详细信息
  246 + /// </remarks>
  247 + /// <param name="id">清洗商ID</param>
  248 + /// <returns>清洗商详情</returns>
  249 + /// <response code="200">查询成功</response>
  250 + /// <response code="400">记录不存在</response>
  251 + /// <response code="500">服务器错误</response>
  252 + [HttpGet("{id}")]
  253 + public async Task<LqLaundrySupplierInfoOutput> GetInfoAsync(string id)
  254 + {
  255 + try
  256 + {
  257 + var entity = await _db.Queryable<LqLaundrySupplierEntity>()
  258 + .Where(x => x.Id == id)
  259 + .Select(x => new LqLaundrySupplierInfoOutput
  260 + {
  261 + id = x.Id,
  262 + supplierName = x.SupplierName,
  263 + productType = x.ProductType,
  264 + laundryPrice = x.LaundryPrice,
  265 + remark = x.Remark,
  266 + isEffective = x.IsEffective,
  267 + createUser = x.CreateUser,
  268 + createUserName = "",
  269 + createTime = x.CreateTime,
  270 + updateUser = x.UpdateUser,
  271 + updateUserName = "",
  272 + updateTime = x.UpdateTime
  273 + })
  274 + .FirstAsync();
  275 +
  276 + if (entity == null)
  277 + {
  278 + throw NCCException.Oh("清洗商记录不存在");
  279 + }
  280 +
  281 + // 补充用户名称
  282 + if (!string.IsNullOrEmpty(entity.createUser))
  283 + {
  284 + var createUser = await _db.Queryable<UserEntity>()
  285 + .Where(x => x.Id == entity.createUser)
  286 + .Select(x => x.RealName)
  287 + .FirstAsync();
  288 + entity.createUserName = createUser ?? "";
  289 + }
  290 +
  291 + if (!string.IsNullOrEmpty(entity.updateUser))
  292 + {
  293 + var updateUser = await _db.Queryable<UserEntity>()
  294 + .Where(x => x.Id == entity.updateUser)
  295 + .Select(x => x.RealName)
  296 + .FirstAsync();
  297 + entity.updateUserName = updateUser ?? "";
  298 + }
  299 +
  300 + return entity;
  301 + }
  302 + catch (Exception ex)
  303 + {
  304 + _logger.LogError(ex, "获取清洗商详情失败");
  305 + throw NCCException.Oh($"查询失败:{ex.Message}");
  306 + }
  307 + }
  308 + #endregion
  309 +
  310 + #region 删除/作废清洗商
  311 + /// <summary>
  312 + /// 删除/作废清洗商
  313 + /// </summary>
  314 + /// <remarks>
  315 + /// 将清洗商记录标记为无效
  316 + /// </remarks>
  317 + /// <param name="id">清洗商ID</param>
  318 + /// <returns>操作结果</returns>
  319 + /// <response code="200">操作成功</response>
  320 + /// <response code="400">记录不存在</response>
  321 + /// <response code="500">服务器错误</response>
  322 + [HttpDelete("{id}")]
  323 + public async Task DeleteAsync(string id)
  324 + {
  325 + try
  326 + {
  327 + var entity = await _db.Queryable<LqLaundrySupplierEntity>()
  328 + .Where(x => x.Id == id)
  329 + .FirstAsync();
  330 +
  331 + if (entity == null)
  332 + {
  333 + throw NCCException.Oh("清洗商记录不存在");
  334 + }
  335 +
  336 + entity.IsEffective = StatusEnum.无效.GetHashCode();
  337 + entity.UpdateUser = _userManager.UserId;
  338 + entity.UpdateTime = DateTime.Now;
  339 +
  340 + var isOk = await _db.Updateable(entity).ExecuteCommandAsync();
  341 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  342 + }
  343 + catch (Exception ex)
  344 + {
  345 + _logger.LogError(ex, "删除清洗商失败");
  346 + throw NCCException.Oh($"删除失败:{ex.Message}");
  347 + }
  348 + }
  349 + #endregion
  350 + }
  351 +}
  352 +
  353 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
... ... @@ -26,6 +26,8 @@ using NCC.Extend.Entitys.lq_kd_jksyj;
26 26 using NCC.Extend.Entitys.lq_kd_kdjlb;
27 27 using NCC.Extend.Entitys.lq_kd_kjbsyj;
28 28 using NCC.Extend.Entitys.lq_mdxx;
  29 +using NCC.Extend.Entitys.lq_md_target;
  30 +using NCC.Extend.Entitys.lq_hytk_hytk;
29 31 using NCC.Extend.Entitys.lq_md_xdbhsj;
30 32 using NCC.Extend.Entitys.lq_xh_kjbsyj;
31 33 using NCC.Extend.Entitys.lq_xh_hyhk;
... ... @@ -50,6 +52,7 @@ using NCC.System.Entitys.Permission;
50 52 using SqlSugar;
51 53 using Yitter.IdGenerator;
52 54 using NCC.Extend.Entitys.lq_kd_pxmx;
  55 +using NCC.Extend.Entitys.lq_khxx;
53 56  
54 57 namespace NCC.Extend.LqStatistics
55 58 {
... ... @@ -886,52 +889,18 @@ namespace NCC.Extend.LqStatistics
886 889 var now = DateTime.Now;
887 890 var startDate = new DateTime(now.Year, now.Month, 1);
888 891 var endDate = startDate.AddMonths(1).AddDays(-1);
889   -
890 892 // 获取当前月份(YYYYMM格式)
891 893 var currentMonth = now.ToString("yyyyMM");
892   -
893 894 // 1. 获取本月的门店目标业绩总和(从门店目标表获取 F_StoreTarget 字段,按月份过滤)
894   - var targetPerformanceSql = @"
895   - SELECT
896   - COALESCE(SUM(F_StoreTarget), 0) AS TotalTargetPerformance
897   - FROM lq_md_target
898   - WHERE F_Month = @month";
899   -
900   - var targetParameters = new Dictionary<string, object>
901   - {
902   - { "@month", currentMonth }
903   - };
904   -
905   - var targetResult = await _db.Ado.SqlQueryAsync<dynamic>(targetPerformanceSql, targetParameters);
906   - var targetPerformance = 0m;
907   - if (targetResult != null && targetResult.Any())
908   - {
909   - targetPerformance = Convert.ToDecimal(targetResult.FirstOrDefault()?.TotalTargetPerformance ?? 0);
910   - }
911   -
  895 + var targetPerformance = await _db.Queryable<LqMdTargetEntity>().Where(x => x.Month == currentMonth).SumAsync(x => (decimal?)x.StoreTarget) ?? 0m;
912 896 // 2. 从开单记录表获取本月实付金额的总和(sfyj 字段)
913   - var actualPerformanceSql = @"
914   - SELECT
915   - COALESCE(SUM(sfyj), 0) AS TotalActualPerformance
916   - FROM lq_kd_kdjlb
917   - WHERE F_IsEffective = 1
918   - AND kdrq >= @startDate
919   - AND kdrq <= @endDate";
920   -
921   - var parameters = new Dictionary<string, object>
922   - {
923   - { "@startDate", startDate.ToString("yyyy-MM-dd 00:00:00") },
924   - { "@endDate", endDate.ToString("yyyy-MM-dd 23:59:59") }
925   - };
926   -
927   - var actualResult = await _db.Ado.SqlQueryAsync<dynamic>(actualPerformanceSql, parameters);
928   - var actualPerformance = 0m;
929   - if (actualResult != null && actualResult.Any())
930   - {
931   - actualPerformance = Convert.ToDecimal(actualResult.FirstOrDefault()?.TotalActualPerformance ?? 0);
932   - }
933   -
934   - // 3. 计算完成率
  897 + var endDateTime = endDate.Date.AddDays(1).AddSeconds(-1); // 结束日期的23:59:59
  898 + var billingPerformance = await _db.Queryable<LqKdKdjlbEntity>().Where(x => x.IsEffective == 1).Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime).SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
  899 + // 3. 从退卡记录表获取本月退卡金额的总和(F_ActualRefundAmount 字段)
  900 + var refundPerformance = await _db.Queryable<LqHytkHytkEntity>().Where(x => x.IsEffective == 1).Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date).SumAsync(x => x.ActualRefundAmount) ?? 0m;
  901 + // 4. 计算实际开单业绩(开单业绩 - 退卡金额)
  902 + var actualPerformance = billingPerformance - refundPerformance;
  903 + // 5. 计算完成率(使用实际开单业绩)
935 904 var completionRate = 0m;
936 905 if (targetPerformance > 0)
937 906 {
... ... @@ -941,6 +910,8 @@ namespace NCC.Extend.LqStatistics
941 910 return new
942 911 {
943 912 TargetPerformance = targetPerformance,
  913 + BillingPerformance = billingPerformance,
  914 + RefundPerformance = refundPerformance,
944 915 ActualPerformance = actualPerformance,
945 916 CompletionRate = decimal.Round(completionRate, 2),
946 917 Month = now.ToString("yyyy年MM月"),
... ... @@ -952,6 +923,8 @@ namespace NCC.Extend.LqStatistics
952 923 return new
953 924 {
954 925 TargetPerformance = 0m,
  926 + BillingPerformance = 0m,
  927 + RefundPerformance = 0m,
955 928 ActualPerformance = 0m,
956 929 CompletionRate = 0m,
957 930 Month = DateTime.Now.ToString("yyyy年MM月"),
... ... @@ -3679,7 +3652,6 @@ namespace NCC.Extend.LqStatistics
3679 3652 /// - ConsumeProjectCount: 消耗项目数
3680 3653 /// - ConsumeAchievement: 消耗业绩
3681 3654 /// - OrderAchievement: 开单业绩(开卡业绩)
3682   - /// - OrderItemCount: 开单品项次数
3683 3655 /// - ConsumeItemCount: 耗卡品项次数
3684 3656 /// - ConsumeLaborCost: 消耗手工费(耗卡手工费)
3685 3657 /// </remarks>
... ... @@ -3689,92 +3661,45 @@ namespace NCC.Extend.LqStatistics
3689 3661 /// <response code="400">参数错误</response>
3690 3662 /// <response code="500">服务器内部错误</response>
3691 3663 [HttpPost("GetTechTeacherStatistics")]
3692   - [AllowAnonymous]
3693 3664 public async Task<List<TechTeacherSimpleStatisticsOutput>> GetTechTeacherStatistics(TechTeacherStatisticsInput input)
3694 3665 {
  3666 + TechTeacherSimpleStatisticsOutput result = new TechTeacherSimpleStatisticsOutput
  3667 + {
  3668 + DepartmentName = "科技部",
  3669 + TeacherName = "",
  3670 + OrderAchievement = 0m,
  3671 + ConsumeAchievement = 0m,
  3672 + ConsumeItemCount = 0,
  3673 + ConsumeProjectCount = 0,
  3674 + ConsumeLaborCost = 0m,
  3675 + RefundAchievement = 0m,
  3676 + };
3695 3677 try
3696 3678 {
3697 3679 // 1. 验证必须传入科技老师ID
3698 3680 if (string.IsNullOrEmpty(input.TeacherId))
3699 3681 {
3700   - return new List<TechTeacherSimpleStatisticsOutput>
3701   - {
3702   - new TechTeacherSimpleStatisticsOutput
3703   - {
3704   - DepartmentName = "科技部",
3705   - TeacherName = "",
3706   - OrderAchievement = 0m,
3707   - OrderItemCount = 0,
3708   - ConsumeAchievement = 0m,
3709   - ConsumeItemCount = 0,
3710   - ConsumeProjectCount = 0,
3711   - ConsumeLaborCost = 0m,
3712   - RefundAchievement = 0m,
3713   - }
3714   - };
  3682 + return new List<TechTeacherSimpleStatisticsOutput> { result };
3715 3683 }
3716 3684  
3717 3685 // 2. 获取科技老师信息
3718   - var teacher = await _db.Queryable<UserEntity>()
3719   - .Where(x => x.Gw == "科技老师" && x.Id == input.TeacherId)
3720   - .Select(x => new
3721   - {
3722   - TeacherId = x.Id,
3723   - TeacherName = x.RealName,
3724   - TeacherAccount = x.Account,
3725   - })
3726   - .FirstAsync();
3727   -
3728   - if (teacher == null)
  3686 + var teacher = await _db.Queryable<UserEntity>().Where(x => x.Gw == "科技老师" && x.Id == input.TeacherId)
  3687 + .Select(x => new
3729 3688 {
3730   - return new List<TechTeacherSimpleStatisticsOutput>
3731   - {
3732   - new TechTeacherSimpleStatisticsOutput
3733   - {
3734   - DepartmentName = "科技部",
3735   - TeacherName = "",
3736   - OrderAchievement = 0m,
3737   - OrderItemCount = 0,
3738   - ConsumeAchievement = 0m,
3739   - ConsumeItemCount = 0,
3740   - ConsumeProjectCount = 0,
3741   - ConsumeLaborCost = 0m,
3742   - RefundAchievement = 0m,
3743   - }
3744   - };
3745   - }
3746   -
3747   - // 3. 查询开单业绩(从 lq_kd_kjbsyj 关联 lq_kd_kdjlb 和 lq_kd_pxmx)
3748   - var orderQuery = _db.Queryable<LqKdKjbsyjEntity, LqKdKdjlbEntity, LqKdPxmxEntity>(
3749   - (kjbsyj, kdjlb, pxmx) => kjbsyj.Glkdbh == kdjlb.Id && kjbsyj.Kdpxid == pxmx.Id)
3750   - .Where((kjbsyj, kdjlb, pxmx) => kjbsyj.IsEffective == StatusEnum.有效.GetHashCode())
3751   - .Where((kjbsyj, kdjlb, pxmx) => kdjlb.IsEffective == StatusEnum.有效.GetHashCode())
3752   - .Where((kjbsyj, kdjlb, pxmx) => kjbsyj.Kjblszh == teacher.TeacherAccount);
3753   -
3754   - // 日期过滤
3755   - if (input.StartDate.HasValue)
3756   - {
3757   - orderQuery = orderQuery.Where((kjbsyj, kdjlb, pxmx) => kjbsyj.Yjsj >= input.StartDate.Value);
3758   - }
  3689 + TeacherId = x.Id,
  3690 + TeacherName = x.RealName,
  3691 + TeacherAccount = x.Account,
  3692 + }).FirstAsync();
3759 3693  
3760   - if (input.EndDate.HasValue)
  3694 + if (teacher == null)
3761 3695 {
3762   - orderQuery = orderQuery.Where((kjbsyj, kdjlb, pxmx) => kjbsyj.Yjsj <= input.EndDate.Value);
  3696 + return new List<TechTeacherSimpleStatisticsOutput> { result };
3763 3697 }
3764   -
3765   - var orderStats = await orderQuery
3766   - .Select((kjbsyj, kdjlb, pxmx) => new
3767   - {
3768   - OrderAchievement = SqlFunc.ToDecimal(kjbsyj.Kjblsyj),
3769   - OrderItemCount = SqlFunc.ToInt32(pxmx.ProjectNumber),
3770   - })
3771   - .ToListAsync();
  3698 + // 3. 查询开单业绩(从 lq_kd_kjbsyj 表)
  3699 + var OrderAchievement = await _db.Queryable<LqKdKjbsyjEntity>().Where(x => x.Kjbls == teacher.TeacherId && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Yjsj >= input.StartDate.Value && x.Yjsj <= input.EndDate.Value).SumAsync(x => x.Kjblsyj);
3772 3700  
3773 3701 // 4. 查询耗卡业绩(从 lq_xh_kjbsyj 关联 lq_xh_hyhk 和 lq_xh_pxmx)
3774   - // 注意:lq_xh_kjbsyj.kjbls 字段存储的是健康师id,不是科技部老师id
3775   - // 科技部老师信息在 kjblszh(账号)和 kjblsxm(姓名)字段
3776   - var consumeQuery = _db.Queryable<LqXhKjbsyjEntity, LqXhHyhkEntity, LqXhPxmxEntity>(
3777   - (kjbsyj, hyhk, pxmx) => kjbsyj.Glkdbh == hyhk.Id && kjbsyj.Hkpxid == pxmx.Id)
  3702 + var consumeQuery = _db.Queryable<LqXhKjbsyjEntity, LqXhHyhkEntity, LqXhPxmxEntity>((kjbsyj, hyhk, pxmx) => kjbsyj.Glkdbh == hyhk.Id && kjbsyj.Hkpxid == pxmx.Id)
3778 3703 .Where((kjbsyj, hyhk, pxmx) => kjbsyj.IsEffective == StatusEnum.有效.GetHashCode())
3779 3704 .Where((kjbsyj, hyhk, pxmx) => hyhk.IsEffective == StatusEnum.有效.GetHashCode())
3780 3705 .Where((kjbsyj, hyhk, pxmx) => kjbsyj.Kjblszh == teacher.TeacherAccount);
... ... @@ -3799,69 +3724,230 @@ namespace NCC.Extend.LqStatistics
3799 3724 ConsumeLaborCost = kjbsyj.LaborCost,
3800 3725 })
3801 3726 .ToListAsync();
3802   -
3803 3727 // 5. 查询退卡业绩(从 lq_hytk_kjbsyj 表)
3804   - var refundQuery = _db.Queryable<LqHytkKjbsyjEntity>()
3805   - .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
3806   - .Where(x => x.Kjblszh == teacher.TeacherAccount);
  3728 + var RefundAchievement = await _db.Queryable<LqHytkKjbsyjEntity>().Where(x => x.Kjbls == teacher.TeacherId && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Tksj >= input.StartDate.Value && x.Tksj <= input.EndDate.Value).SumAsync(x => x.Kjblsyj);
  3729 + // 6. 统计并返回结果
  3730 + result.TeacherName = teacher.TeacherName;
  3731 + result.OrderAchievement = OrderAchievement.ToDecimal();
  3732 + result.ConsumeAchievement = consumeStats.Sum(x => x.ConsumeAchievement ?? 0m);
  3733 + result.ConsumeItemCount = consumeStats.Sum(x => x.ConsumeItemCount);
  3734 + result.ConsumeProjectCount = consumeStats.Sum(x => x.ConsumeProjectCount);
  3735 + result.ConsumeLaborCost = consumeStats.Sum(x => x.ConsumeLaborCost ?? 0m);
  3736 + result.RefundAchievement = RefundAchievement.ToDecimal();
  3737 + return new List<TechTeacherSimpleStatisticsOutput> { result };
  3738 + }
  3739 + catch (Exception)
  3740 + {
  3741 + return new List<TechTeacherSimpleStatisticsOutput> { result };
  3742 + }
  3743 + }
3807 3744  
3808   - // 日期过滤
3809   - if (input.StartDate.HasValue)
3810   - {
3811   - refundQuery = refundQuery.Where(x => x.Tksj >= input.StartDate.Value);
3812   - }
  3745 + #endregion
3813 3746  
3814   - if (input.EndDate.HasValue)
  3747 + #region 只买了女神卡的会员统计
  3748 + /// <summary>
  3749 + /// 获取只买了女神卡的会员及其开单数据(分页)
  3750 + /// </summary>
  3751 + /// <remarks>
  3752 + /// 统计所有只购买了女神卡(品项编号为61)的会员,并返回这些会员的开单记录
  3753 + ///
  3754 + /// 判断逻辑:
  3755 + /// 1. 会员必须有购买女神卡的记录
  3756 + /// 2. 该会员的所有有效开单记录中,所有品项都必须是女神卡(px = "61")
  3757 + ///
  3758 + /// 返回说明:
  3759 + /// - list: 会员列表
  3760 + /// - memberId: 会员ID
  3761 + /// - memberName: 会员名称
  3762 + /// - phone: 手机号
  3763 + /// - storeId: 归属门店ID
  3764 + /// - storeName: 归属门店名称
  3765 + /// - billingList: 开单记录列表
  3766 + /// - billingId: 开单编号
  3767 + /// - billingDate: 开单日期
  3768 + /// - actualPerformance: 实付业绩
  3769 + /// - storeId: 门店ID
  3770 + /// - storeName: 门店名称
  3771 + /// - itemList: 品项列表
  3772 + /// - itemId: 品项明细ID
  3773 + /// - itemCode: 品项编号(如61)
  3774 + /// - itemName: 品项名称(如女神卡)
  3775 + /// - itemPrice: 品项价格
  3776 + /// - sourceType: 来源类型
  3777 + /// - projectNumber: 项目次数
  3778 + /// - totalPrice: 总价
  3779 + /// - actualPrice: 实付金额
  3780 + /// - performanceTime: 业绩时间
  3781 + /// - pagination: 分页信息
  3782 + /// - pageIndex: 当前页码
  3783 + /// - pageSize: 每页数量
  3784 + /// - total: 总记录数
  3785 + /// </remarks>
  3786 + /// <param name="input">查询参数</param>
  3787 + /// <returns>只买了女神卡的会员列表(分页)</returns>
  3788 + /// <response code="200">成功返回会员列表</response>
  3789 + /// <response code="500">服务器内部错误</response>
  3790 + [HttpPost("GetGoddessCardMembers")]
  3791 + public async Task<dynamic> GetGoddessCardMembers([FromBody] GoddessCardMemberListQueryInput input)
  3792 + {
  3793 + try
  3794 + {
  3795 + // 使用SQL一次性查询出符合条件的会员ID
  3796 + // 逻辑:会员有购买女神卡的记录,且该会员的所有开单记录的所有品项都是女神卡
  3797 + var sql = @"
  3798 + SELECT DISTINCT kd1.Kdhy AS MemberId
  3799 + FROM lq_kd_pxmx pxmx1
  3800 + INNER JOIN lq_kd_kdjlb kd1 ON pxmx1.glkdbh = kd1.F_Id
  3801 + WHERE pxmx1.px = '61'
  3802 + AND pxmx1.F_IsEffective = 1
  3803 + AND kd1.F_IsEffective = 1
  3804 + AND kd1.Kdhy IS NOT NULL
  3805 + AND kd1.Kdhy != ''
  3806 + AND NOT EXISTS (
  3807 + -- 排除那些有非女神卡品项的会员
  3808 + SELECT 1
  3809 + FROM lq_kd_pxmx pxmx2
  3810 + INNER JOIN lq_kd_kdjlb kd2 ON pxmx2.glkdbh = kd2.F_Id
  3811 + WHERE kd2.Kdhy = kd1.Kdhy
  3812 + AND pxmx2.F_IsEffective = 1
  3813 + AND kd2.F_IsEffective = 1
  3814 + AND pxmx2.px != '61'
  3815 + )";
  3816 +
  3817 + var validMemberIds = await _db.Ado.SqlQueryAsync<string>(sql);
  3818 +
  3819 + if (!validMemberIds.Any())
3815 3820 {
3816   - refundQuery = refundQuery.Where(x => x.Tksj <= input.EndDate.Value);
  3821 + return new
  3822 + {
  3823 + list = new List<GoddessCardMemberOutput>(),
  3824 + pagination = new
  3825 + {
  3826 + pageIndex = input.PageIndex,
  3827 + pageSize = input.PageSize,
  3828 + total = 0
  3829 + }
  3830 + };
3817 3831 }
3818 3832  
3819   - var refundStats = await refundQuery
  3833 + // 分页处理
  3834 + var totalCount = validMemberIds.Count;
  3835 + var pagedMemberIds = validMemberIds
  3836 + .Skip((input.PageIndex - 1) * input.PageSize)
  3837 + .Take(input.PageSize)
  3838 + .ToList();
  3839 +
  3840 + // 获取会员基本信息
  3841 + var members = await _db.Queryable<LqKhxxEntity>()
  3842 + .Where(x => pagedMemberIds.Contains(x.Id))
3820 3843 .Select(x => new
3821 3844 {
3822   - RefundAchievement = x.Kjblsyj,
  3845 + x.Id,
  3846 + x.Khmc,
  3847 + x.Sjh,
  3848 + x.Gsmd
3823 3849 })
3824 3850 .ToListAsync();
3825 3851  
3826   - // 6. 统计并返回结果
3827   - var result = new TechTeacherSimpleStatisticsOutput
3828   - {
3829   - DepartmentName = "科技部",
3830   - TeacherName = teacher.TeacherName,
3831   - OrderAchievement = orderStats.Sum(x => x.OrderAchievement != null && decimal.TryParse(x.OrderAchievement.ToString(), out var val) ? val : 0m),
3832   - OrderItemCount = orderStats.Sum(x => x.OrderItemCount),
3833   - ConsumeAchievement = consumeStats.Sum(x => x.ConsumeAchievement ?? 0m),
3834   - ConsumeItemCount = consumeStats.Sum(x => x.ConsumeItemCount),
3835   - ConsumeProjectCount = consumeStats.Sum(x => x.ConsumeProjectCount),
3836   - ConsumeLaborCost = consumeStats.Sum(x => x.ConsumeLaborCost ?? 0m),
3837   - RefundAchievement = refundStats.Sum(x => x.RefundAchievement ?? 0m),
3838   - };
  3852 + // 获取门店信息
  3853 + var storeIds = members.Where(x => !string.IsNullOrEmpty(x.Gsmd)).Select(x => x.Gsmd).Distinct().ToList();
  3854 + var stores = new Dictionary<string, string>();
  3855 + if (storeIds.Any())
  3856 + {
  3857 + var storeList = await _db.Queryable<LqMdxxEntity>()
  3858 + .Where(x => storeIds.Contains(x.Id))
  3859 + .Select(x => new { x.Id, x.Dm })
  3860 + .ToListAsync();
  3861 + stores = storeList.ToDictionary(x => x.Id, x => x.Dm ?? "");
  3862 + }
3839 3863  
3840   - return new List<TechTeacherSimpleStatisticsOutput> { result };
3841   - }
3842   - catch (Exception ex)
3843   - {
3844   - _logger.LogError(ex, "获取科技部老师业绩统计时发生错误");
3845   - // 发生错误时返回全0的结果,而不是抛出异常
3846   - return new List<TechTeacherSimpleStatisticsOutput>
  3864 + // 获取所有开单记录
  3865 + var billings = await _db.Queryable<LqKdKdjlbEntity>().Where(x => pagedMemberIds.Contains(x.Kdhy) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
  3866 +
  3867 + // 获取开单记录的门店信息
  3868 + var billingStoreIds = billings.Where(x => !string.IsNullOrEmpty(x.Djmd)).Select(x => x.Djmd).Distinct().ToList();
  3869 + var billingStores = new Dictionary<string, string>();
  3870 + if (billingStoreIds.Any())
  3871 + {
  3872 + var billingStoreList = await _db.Queryable<LqMdxxEntity>()
  3873 + .Where(x => billingStoreIds.Contains(x.Id))
  3874 + .Select(x => new { x.Id, x.Dm })
  3875 + .ToListAsync();
  3876 + billingStores = billingStoreList.ToDictionary(x => x.Id, x => x.Dm ?? "");
  3877 + }
  3878 +
  3879 + // 获取所有开单的品项明细
  3880 + var billingIds = billings.Select(x => x.Id).ToList();
  3881 + var billingItems = await _db.Queryable<LqKdPxmxEntity>()
  3882 + .Where(x => billingIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
  3883 + .ToListAsync();
  3884 +
  3885 + // 按开单编号分组品项
  3886 + var itemsByBilling = billingItems
  3887 + .GroupBy(x => x.Glkdbh)
  3888 + .ToDictionary(g => g.Key, g => g.ToList());
  3889 +
  3890 + // 组装返回数据
  3891 + var result = members.Select(member =>
  3892 + {
  3893 + var memberBillings = billings.Where(x => x.Kdhy == member.Id).ToList();
  3894 +
  3895 + return new GoddessCardMemberOutput
  3896 + {
  3897 + memberId = member.Id,
  3898 + memberName = member.Khmc ?? "",
  3899 + phone = member.Sjh ?? "",
  3900 + storeId = member.Gsmd ?? "",
  3901 + storeName = !string.IsNullOrEmpty(member.Gsmd) && stores.ContainsKey(member.Gsmd) ? stores[member.Gsmd] : "",
  3902 + billingList = memberBillings.Select(b =>
  3903 + {
  3904 + var hasItems = itemsByBilling.ContainsKey(b.Id);
  3905 + var items = hasItems ? itemsByBilling[b.Id] : new List<LqKdPxmxEntity>();
  3906 +
  3907 + return new GoddessCardBillingInfo
  3908 + {
  3909 + billingId = b.Id,
  3910 + billingDate = b.Kdrq,
  3911 + actualPerformance = b.Sfyj,
  3912 + storeId = b.Djmd ?? "",
  3913 + storeName = !string.IsNullOrEmpty(b.Djmd) && billingStores.ContainsKey(b.Djmd) ? billingStores[b.Djmd] : "",
  3914 + itemList = items.Select(item => new GoddessCardBillingItemInfo
  3915 + {
  3916 + itemId = item.Id ?? "",
  3917 + itemCode = item.Px ?? "",
  3918 + itemName = item.Pxmc ?? "",
  3919 + itemPrice = item.Pxjg,
  3920 + sourceType = item.SourceType ?? "",
  3921 + projectNumber = item.ProjectNumber,
  3922 + totalPrice = item.TotalPrice,
  3923 + actualPrice = item.ActualPrice,
  3924 + performanceTime = item.Yjsj
  3925 + }).ToList()
  3926 + };
  3927 + }).ToList()
  3928 + };
  3929 + }).ToList();
  3930 +
  3931 + return new
3847 3932 {
3848   - new TechTeacherSimpleStatisticsOutput
  3933 + list = result,
  3934 + pagination = new
3849 3935 {
3850   - DepartmentName = "科技部",
3851   - TeacherName = "",
3852   - OrderAchievement = 0m,
3853   - OrderItemCount = 0,
3854   - ConsumeAchievement = 0m,
3855   - ConsumeItemCount = 0,
3856   - ConsumeProjectCount = 0,
3857   - ConsumeLaborCost = 0m,
3858   - RefundAchievement = 0m,
  3936 + pageIndex = input.PageIndex,
  3937 + pageSize = input.PageSize,
  3938 + total = totalCount
3859 3939 }
3860 3940 };
3861 3941 }
  3942 + catch (Exception ex)
  3943 + {
  3944 + _logger.LogError(ex, "获取只买了女神卡的会员数据时发生错误");
  3945 + throw NCCException.Oh($"获取数据失败:{ex.Message}");
  3946 + }
3862 3947 }
3863   -
3864 3948 #endregion
3865 3949  
  3950 +
  3951 +
3866 3952 }
3867 3953 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStoreConsumableInventoryService.cs 0 → 100644
  1 +using System;
  2 +using System.Linq;
  3 +using System.Threading.Tasks;
  4 +using Microsoft.AspNetCore.Mvc;
  5 +using Microsoft.Extensions.Logging;
  6 +using NCC.Common.Core.Manager;
  7 +using NCC.Common.Enum;
  8 +using NCC.Common.Filter;
  9 +using NCC.Dependency;
  10 +using NCC.DynamicApiController;
  11 +using NCC.Extend.Entitys.Dto.LqStoreConsumableInventory;
  12 +using NCC.Extend.Entitys.Enum;
  13 +using NCC.Extend.Entitys.lq_mdxx;
  14 +using NCC.Extend.Entitys.lq_store_consumable_inventory;
  15 +using NCC.Extend.Interfaces.LqStoreConsumableInventory;
  16 +using NCC.FriendlyException;
  17 +using NCC.System.Entitys.Permission;
  18 +using SqlSugar;
  19 +using Yitter.IdGenerator;
  20 +
  21 +namespace NCC.Extend
  22 +{
  23 + /// <summary>
  24 + /// 门店消耗品库存服务
  25 + /// </summary>
  26 + [ApiDescriptionSettings(Tag = "绿纤门店消耗品库存管理", Name = "LqStoreConsumableInventory", Order = 200)]
  27 + [Route("api/Extend/LqStoreConsumableInventory")]
  28 + public class LqStoreConsumableInventoryService : IDynamicApiController, ITransient, ILqStoreConsumableInventoryService
  29 + {
  30 + private readonly IUserManager _userManager;
  31 + private readonly ILogger<LqStoreConsumableInventoryService> _logger;
  32 + private readonly ISqlSugarClient _db;
  33 +
  34 + /// <summary>
  35 + /// 构造函数
  36 + /// </summary>
  37 + public LqStoreConsumableInventoryService(IUserManager userManager, ILogger<LqStoreConsumableInventoryService> logger, ISqlSugarClient db)
  38 + {
  39 + _userManager = userManager;
  40 + _logger = logger;
  41 + _db = db;
  42 + }
  43 +
  44 + #region 创建门店消耗品库存
  45 + /// <summary>
  46 + /// 创建门店消耗品库存
  47 + /// </summary>
  48 + /// <remarks>
  49 + /// 为指定门店创建消耗品库存记录
  50 + ///
  51 + /// 示例请求:
  52 + /// ```json
  53 + /// {
  54 + /// "storeId": "门店ID",
  55 + /// "productType": "毛巾",
  56 + /// "quantity": 100
  57 + /// }
  58 + /// ```
  59 + /// </remarks>
  60 + /// <param name="input">创建输入</param>
  61 + /// <returns>创建结果</returns>
  62 + /// <response code="200">创建成功</response>
  63 + /// <response code="400">门店不存在或参数错误</response>
  64 + /// <response code="500">服务器错误</response>
  65 + [HttpPost("Create")]
  66 + public async Task CreateAsync([FromBody] LqStoreConsumableInventoryCrInput input)
  67 + {
  68 + try
  69 + {
  70 + // 验证门店是否存在
  71 + var store = await _db.Queryable<LqMdxxEntity>()
  72 + .Where(x => x.Id == input.StoreId)
  73 + .FirstAsync();
  74 +
  75 + if (store == null)
  76 + {
  77 + throw NCCException.Oh("门店不存在");
  78 + }
  79 +
  80 + // 检查是否已存在相同门店和产品类型的记录
  81 + var existing = await _db.Queryable<LqStoreConsumableInventoryEntity>()
  82 + .Where(x => x.StoreId == input.StoreId
  83 + && x.ProductType == input.ProductType
  84 + && x.IsEffective == StatusEnum.有效.GetHashCode())
  85 + .FirstAsync();
  86 +
  87 + if (existing != null)
  88 + {
  89 + throw NCCException.Oh($"该门店已存在{input.ProductType}的库存记录,请使用更新功能");
  90 + }
  91 +
  92 + // 创建库存记录
  93 + var entity = new LqStoreConsumableInventoryEntity
  94 + {
  95 + Id = YitIdHelper.NextId().ToString(),
  96 + StoreId = input.StoreId,
  97 + ProductType = input.ProductType,
  98 + Quantity = input.Quantity,
  99 + IsEffective = StatusEnum.有效.GetHashCode(),
  100 + CreateUser = _userManager.UserId,
  101 + CreateTime = DateTime.Now
  102 + };
  103 +
  104 + var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
  105 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  106 + }
  107 + catch (Exception ex)
  108 + {
  109 + _logger.LogError(ex, "创建门店消耗品库存失败");
  110 + throw NCCException.Oh($"创建失败:{ex.Message}");
  111 + }
  112 + }
  113 + #endregion
  114 +
  115 + #region 更新门店消耗品库存
  116 + /// <summary>
  117 + /// 更新门店消耗品库存
  118 + /// </summary>
  119 + /// <remarks>
  120 + /// 更新指定门店的消耗品库存记录
  121 + /// </remarks>
  122 + /// <param name="input">更新输入</param>
  123 + /// <returns>更新结果</returns>
  124 + /// <response code="200">更新成功</response>
  125 + /// <response code="400">记录不存在或参数错误</response>
  126 + /// <response code="500">服务器错误</response>
  127 + [HttpPut("Update")]
  128 + public async Task UpdateAsync([FromBody] LqStoreConsumableInventoryUpInput input)
  129 + {
  130 + try
  131 + {
  132 + var existing = await _db.Queryable<LqStoreConsumableInventoryEntity>()
  133 + .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
  134 + .FirstAsync();
  135 +
  136 + if (existing == null)
  137 + {
  138 + throw NCCException.Oh("库存记录不存在或已失效");
  139 + }
  140 +
  141 + // 验证门店是否存在
  142 + var store = await _db.Queryable<LqMdxxEntity>()
  143 + .Where(x => x.Id == input.StoreId)
  144 + .FirstAsync();
  145 +
  146 + if (store == null)
  147 + {
  148 + throw NCCException.Oh("门店不存在");
  149 + }
  150 +
  151 + // 如果门店或产品类型改变,检查是否冲突
  152 + if (existing.StoreId != input.StoreId || existing.ProductType != input.ProductType)
  153 + {
  154 + var conflict = await _db.Queryable<LqStoreConsumableInventoryEntity>()
  155 + .Where(x => x.Id != input.Id
  156 + && x.StoreId == input.StoreId
  157 + && x.ProductType == input.ProductType
  158 + && x.IsEffective == StatusEnum.有效.GetHashCode())
  159 + .FirstAsync();
  160 +
  161 + if (conflict != null)
  162 + {
  163 + throw NCCException.Oh($"该门店已存在{input.ProductType}的库存记录");
  164 + }
  165 + }
  166 +
  167 + // 更新记录
  168 + existing.StoreId = input.StoreId;
  169 + existing.ProductType = input.ProductType;
  170 + existing.Quantity = input.Quantity;
  171 + existing.UpdateUser = _userManager.UserId;
  172 + existing.UpdateTime = DateTime.Now;
  173 +
  174 + var isOk = await _db.Updateable(existing).ExecuteCommandAsync();
  175 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  176 + }
  177 + catch (Exception ex)
  178 + {
  179 + _logger.LogError(ex, "更新门店消耗品库存失败");
  180 + throw NCCException.Oh($"更新失败:{ex.Message}");
  181 + }
  182 + }
  183 + #endregion
  184 +
  185 + #region 获取门店消耗品库存列表
  186 + /// <summary>
  187 + /// 获取门店消耗品库存列表
  188 + /// </summary>
  189 + /// <remarks>
  190 + /// 分页查询门店消耗品库存列表,支持按门店、产品类型筛选
  191 + /// </remarks>
  192 + /// <param name="input">查询输入</param>
  193 + /// <returns>库存列表</returns>
  194 + /// <response code="200">查询成功</response>
  195 + /// <response code="500">服务器错误</response>
  196 + [HttpGet("GetList")]
  197 + public async Task<dynamic> GetListAsync([FromQuery] LqStoreConsumableInventoryListQueryInput input)
  198 + {
  199 + try
  200 + {
  201 + var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx;
  202 + var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
  203 +
  204 + var data = await _db.Queryable<LqStoreConsumableInventoryEntity, LqMdxxEntity>(
  205 + (inv, store) => inv.StoreId == store.Id)
  206 + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), (inv, store) => inv.StoreId == input.StoreId)
  207 + .WhereIF(!string.IsNullOrWhiteSpace(input.ProductType), (inv, store) => inv.ProductType == input.ProductType)
  208 + .WhereIF(input.IsEffective.HasValue, (inv, store) => inv.IsEffective == input.IsEffective.Value)
  209 + .Select((inv, store) => new LqStoreConsumableInventoryListOutput
  210 + {
  211 + id = inv.Id,
  212 + storeId = inv.StoreId,
  213 + storeName = store.Dm ?? "",
  214 + productType = inv.ProductType,
  215 + quantity = inv.Quantity,
  216 + isEffective = inv.IsEffective,
  217 + createUser = inv.CreateUser,
  218 + createUserName = "",
  219 + createTime = inv.CreateTime,
  220 + updateUser = inv.UpdateUser,
  221 + updateUserName = "",
  222 + updateTime = inv.UpdateTime
  223 + })
  224 + .MergeTable()
  225 + .OrderBy(sidx + " " + sort)
  226 + .ToPagedListAsync(input.currentPage, input.pageSize);
  227 +
  228 + // 补充用户名称信息
  229 + var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser })
  230 + .Where(x => !string.IsNullOrEmpty(x))
  231 + .Distinct()
  232 + .ToList();
  233 +
  234 + if (userIds.Any())
  235 + {
  236 + var userList = await _db.Queryable<UserEntity>()
  237 + .Where(x => userIds.Contains(x.Id))
  238 + .Select(x => new { x.Id, x.RealName })
  239 + .ToListAsync();
  240 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  241 +
  242 + foreach (var item in data.list)
  243 + {
  244 + item.createUserName = userDict.ContainsKey(item.createUser) ? userDict[item.createUser] : "";
  245 + item.updateUserName = !string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser) ? userDict[item.updateUser] : "";
  246 + }
  247 + }
  248 +
  249 + return PageResult<LqStoreConsumableInventoryListOutput>.SqlSugarPageResult(data);
  250 + }
  251 + catch (Exception ex)
  252 + {
  253 + _logger.LogError(ex, "获取门店消耗品库存列表失败");
  254 + throw NCCException.Oh($"查询失败:{ex.Message}");
  255 + }
  256 + }
  257 + #endregion
  258 +
  259 + #region 获取门店消耗品库存详情
  260 + /// <summary>
  261 + /// 获取门店消耗品库存详情
  262 + /// </summary>
  263 + /// <remarks>
  264 + /// 根据ID获取门店消耗品库存的详细信息
  265 + /// </remarks>
  266 + /// <param name="id">库存ID</param>
  267 + /// <returns>库存详情</returns>
  268 + /// <response code="200">查询成功</response>
  269 + /// <response code="400">记录不存在</response>
  270 + /// <response code="500">服务器错误</response>
  271 + [HttpGet("{id}")]
  272 + public async Task<LqStoreConsumableInventoryInfoOutput> GetInfoAsync(string id)
  273 + {
  274 + try
  275 + {
  276 + var entity = await _db.Queryable<LqStoreConsumableInventoryEntity, LqMdxxEntity>(
  277 + (inv, store) => inv.StoreId == store.Id)
  278 + .Where((inv, store) => inv.Id == id)
  279 + .Select((inv, store) => new LqStoreConsumableInventoryInfoOutput
  280 + {
  281 + id = inv.Id,
  282 + storeId = inv.StoreId,
  283 + storeName = store.Dm ?? "",
  284 + productType = inv.ProductType,
  285 + quantity = inv.Quantity,
  286 + isEffective = inv.IsEffective,
  287 + createUser = inv.CreateUser,
  288 + createUserName = "",
  289 + createTime = inv.CreateTime,
  290 + updateUser = inv.UpdateUser,
  291 + updateUserName = "",
  292 + updateTime = inv.UpdateTime
  293 + })
  294 + .FirstAsync();
  295 +
  296 + if (entity == null)
  297 + {
  298 + throw NCCException.Oh("库存记录不存在");
  299 + }
  300 +
  301 + // 补充用户名称
  302 + if (!string.IsNullOrEmpty(entity.createUser))
  303 + {
  304 + var createUser = await _db.Queryable<UserEntity>()
  305 + .Where(x => x.Id == entity.createUser)
  306 + .Select(x => x.RealName)
  307 + .FirstAsync();
  308 + entity.createUserName = createUser ?? "";
  309 + }
  310 +
  311 + if (!string.IsNullOrEmpty(entity.updateUser))
  312 + {
  313 + var updateUser = await _db.Queryable<UserEntity>()
  314 + .Where(x => x.Id == entity.updateUser)
  315 + .Select(x => x.RealName)
  316 + .FirstAsync();
  317 + entity.updateUserName = updateUser ?? "";
  318 + }
  319 +
  320 + return entity;
  321 + }
  322 + catch (Exception ex)
  323 + {
  324 + _logger.LogError(ex, "获取门店消耗品库存详情失败");
  325 + throw NCCException.Oh($"查询失败:{ex.Message}");
  326 + }
  327 + }
  328 + #endregion
  329 +
  330 + #region 删除/作废门店消耗品库存
  331 + /// <summary>
  332 + /// 删除/作废门店消耗品库存
  333 + /// </summary>
  334 + /// <remarks>
  335 + /// 将库存记录标记为无效
  336 + /// </remarks>
  337 + /// <param name="id">库存ID</param>
  338 + /// <returns>操作结果</returns>
  339 + /// <response code="200">操作成功</response>
  340 + /// <response code="400">记录不存在</response>
  341 + /// <response code="500">服务器错误</response>
  342 + [HttpDelete("{id}")]
  343 + public async Task DeleteAsync(string id)
  344 + {
  345 + try
  346 + {
  347 + var entity = await _db.Queryable<LqStoreConsumableInventoryEntity>()
  348 + .Where(x => x.Id == id)
  349 + .FirstAsync();
  350 +
  351 + if (entity == null)
  352 + {
  353 + throw NCCException.Oh("库存记录不存在");
  354 + }
  355 +
  356 + entity.IsEffective = StatusEnum.无效.GetHashCode();
  357 + entity.UpdateUser = _userManager.UserId;
  358 + entity.UpdateTime = DateTime.Now;
  359 +
  360 + var isOk = await _db.Updateable(entity).ExecuteCommandAsync();
  361 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  362 + }
  363 + catch (Exception ex)
  364 + {
  365 + _logger.LogError(ex, "删除门店消耗品库存失败");
  366 + throw NCCException.Oh($"删除失败:{ex.Message}");
  367 + }
  368 + }
  369 + #endregion
  370 + }
  371 +}
  372 +
  373 +
... ...
sql/创建清洗管理相关表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 创建清洗管理相关表
  3 +-- ============================================
  4 +-- 说明:包含门店消耗品库存表、清洗商表、清洗流水表
  5 +-- ============================================
  6 +
  7 +-- ============================================
  8 +-- 1. 创建门店消耗品库存表(lq_store_consumable_inventory)
  9 +-- ============================================
  10 +CREATE TABLE IF NOT EXISTS `lq_store_consumable_inventory` (
  11 + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID',
  12 + `F_StoreId` VARCHAR(50) NULL COMMENT '门店ID(关联lq_mdxx.F_Id)',
  13 + `F_ProductType` VARCHAR(50) NULL COMMENT '产品类型(枚举:毛巾、垫子等)',
  14 + `F_Quantity` INT NULL DEFAULT 0 COMMENT '当前库存数量',
  15 + `F_IsEffective` INT NULL DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)',
  16 + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID',
  17 + `F_CreateTime` DATETIME NULL COMMENT '创建时间',
  18 + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID',
  19 + `F_UpdateTime` DATETIME NULL COMMENT '更新时间',
  20 + PRIMARY KEY (`F_Id`),
  21 + INDEX `idx_store_id` (`F_StoreId`) COMMENT '门店ID索引',
  22 + INDEX `idx_product_type` (`F_ProductType`) COMMENT '产品类型索引',
  23 + INDEX `idx_store_product` (`F_StoreId`, `F_ProductType`) COMMENT '门店+产品类型联合索引'
  24 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='门店消耗品库存表';
  25 +
  26 +-- ============================================
  27 +-- 2. 创建清洗商表(lq_laundry_supplier)
  28 +-- ============================================
  29 +CREATE TABLE IF NOT EXISTS `lq_laundry_supplier` (
  30 + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID',
  31 + `F_SupplierName` VARCHAR(200) NULL COMMENT '清洗商名称',
  32 + `F_ProductType` VARCHAR(50) NULL COMMENT '产品类型(枚举:毛巾、垫子等)',
  33 + `F_LaundryPrice` DECIMAL(18,2) NULL DEFAULT 0 COMMENT '清洗价格(当前价格)',
  34 + `F_Remark` VARCHAR(1000) NULL COMMENT '备注',
  35 + `F_IsEffective` INT NULL DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)',
  36 + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID',
  37 + `F_CreateTime` DATETIME NULL COMMENT '创建时间',
  38 + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID',
  39 + `F_UpdateTime` DATETIME NULL COMMENT '更新时间',
  40 + PRIMARY KEY (`F_Id`),
  41 + INDEX `idx_supplier_name` (`F_SupplierName`) COMMENT '清洗商名称索引',
  42 + INDEX `idx_product_type` (`F_ProductType`) COMMENT '产品类型索引',
  43 + INDEX `idx_supplier_product` (`F_SupplierName`, `F_ProductType`) COMMENT '清洗商+产品类型联合索引'
  44 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='清洗商表';
  45 +
  46 +-- ============================================
  47 +-- 3. 创建清洗流水表(lq_laundry_flow)
  48 +-- ============================================
  49 +CREATE TABLE IF NOT EXISTS `lq_laundry_flow` (
  50 + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID(送出时作为批次号)',
  51 + `F_FlowType` INT NULL DEFAULT 0 COMMENT '流水类型(0:送出 1:送回)',
  52 + `F_BatchNumber` VARCHAR(50) NULL COMMENT '批次号(送出时=F_Id,送回时=对应的送出记录的F_Id)',
  53 + `F_StoreId` VARCHAR(50) NULL COMMENT '门店ID(关联lq_mdxx.F_Id)',
  54 + `F_ProductType` VARCHAR(50) NULL COMMENT '产品类型(枚举:毛巾、垫子等)',
  55 + `F_LaundrySupplierId` VARCHAR(50) NULL COMMENT '清洗商ID(关联lq_laundry_supplier.F_Id)',
  56 + `F_Quantity` INT NULL DEFAULT 0 COMMENT '数量',
  57 + `F_LaundryPrice` DECIMAL(18,2) NULL DEFAULT 0 COMMENT '清洗单价(记录历史价格)',
  58 + `F_TotalPrice` DECIMAL(18,2) NULL DEFAULT 0 COMMENT '总费用(数量 × 单价)',
  59 + `F_Remark` VARCHAR(1000) NULL COMMENT '备注(可说明差异原因,如损坏、丢失等)',
  60 + `F_IsEffective` INT NULL DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)',
  61 + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID',
  62 + `F_CreateTime` DATETIME NULL COMMENT '创建时间',
  63 + PRIMARY KEY (`F_Id`),
  64 + INDEX `idx_flow_type` (`F_FlowType`) COMMENT '流水类型索引',
  65 + INDEX `idx_batch_number` (`F_BatchNumber`) COMMENT '批次号索引',
  66 + INDEX `idx_store_id` (`F_StoreId`) COMMENT '门店ID索引',
  67 + INDEX `idx_product_type` (`F_ProductType`) COMMENT '产品类型索引',
  68 + INDEX `idx_supplier_id` (`F_LaundrySupplierId`) COMMENT '清洗商ID索引',
  69 + INDEX `idx_create_time` (`F_CreateTime`) COMMENT '创建时间索引',
  70 + INDEX `idx_store_month` (`F_StoreId`, `F_CreateTime`) COMMENT '门店+时间联合索引(用于月度统计)'
  71 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='清洗流水表';
  72 +
... ...