Commit 33860a93e73b8b01645e6613919ea369b3e4f9db

Authored by “wangming”
1 parent bb0be37f

feat: 新增合同管理系统功能

- 新增合同表(lq_contract)和月租明细表(lq_contract_rent_detail)
- 实现合同CRUD功能(创建、更新、删除、查询)
- 实现自动生成月租明细功能
- 实现计算下次应交时间功能(匿名方法)
- 实现标记明细已缴费功能
- 实现统计门店合同费用功能(支持按分类统计)
- 新增合同管理相关DTO类
- 新增合同管理服务类(LqContractService)
- 更新前端调用说明文档
Showing 41 changed files with 5911 additions and 12 deletions
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/ContractExpenseStatisticsInput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqContract
  5 +{
  6 + /// <summary>
  7 + /// 合同费用统计输入
  8 + /// </summary>
  9 + public class ContractExpenseStatisticsInput
  10 + {
  11 + /// <summary>
  12 + /// 门店ID(必填)
  13 + /// </summary>
  14 + [Required(ErrorMessage = "门店ID不能为空")]
  15 + [Display(Name = "门店ID")]
  16 + public string StoreId { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 统计年份(必填)
  20 + /// </summary>
  21 + [Required(ErrorMessage = "年份不能为空")]
  22 + [Range(2020, 2100, ErrorMessage = "年份必须在2020-2100之间")]
  23 + [Display(Name = "年份")]
  24 + public int Year { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 统计月份(必填,1-12)
  28 + /// </summary>
  29 + [Required(ErrorMessage = "月份不能为空")]
  30 + [Range(1, 12, ErrorMessage = "月份必须在1-12之间")]
  31 + [Display(Name = "月份")]
  32 + public int Month { get; set; }
  33 +
  34 + /// <summary>
  35 + /// 分类列表(可选,不传则统计所有分类)
  36 + /// </summary>
  37 + [Display(Name = "分类列表")]
  38 + public string[] Categories { get; set; }
  39 + }
  40 +}
  41 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/ContractExpenseStatisticsOutput.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqContract
  4 +{
  5 + /// <summary>
  6 + /// 合同费用统计输出
  7 + /// </summary>
  8 + public class ContractExpenseStatisticsOutput
  9 + {
  10 + /// <summary>
  11 + /// 门店ID
  12 + /// </summary>
  13 + public string storeId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店名称
  17 + /// </summary>
  18 + public string storeName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 统计年份
  22 + /// </summary>
  23 + public int year { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 统计月份
  27 + /// </summary>
  28 + public int month { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 总费用(所有分类的费用总和)
  32 + /// </summary>
  33 + public decimal totalAmount { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 按分类统计的费用明细
  37 + /// </summary>
  38 + public List<CategoryExpenseDetail> categoryDetails { get; set; }
  39 + }
  40 +
  41 + /// <summary>
  42 + /// 分类费用明细
  43 + /// </summary>
  44 + public class CategoryExpenseDetail
  45 + {
  46 + /// <summary>
  47 + /// 分类名称
  48 + /// </summary>
  49 + public string category { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 该分类的费用总额
  53 + /// </summary>
  54 + public decimal amount { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 该分类的明细数量
  58 + /// </summary>
  59 + public int detailCount { get; set; }
  60 +
  61 + /// <summary>
  62 + /// 明细列表
  63 + /// </summary>
  64 + public List<ExpenseDetailItem> details { get; set; }
  65 + }
  66 +
  67 + /// <summary>
  68 + /// 费用明细项
  69 + /// </summary>
  70 + public class ExpenseDetailItem
  71 + {
  72 + /// <summary>
  73 + /// 明细ID
  74 + /// </summary>
  75 + public string detailId { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 合同ID
  79 + /// </summary>
  80 + public string contractId { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 合同标题
  84 + /// </summary>
  85 + public string contractTitle { get; set; }
  86 +
  87 + /// <summary>
  88 + /// 分类
  89 + /// </summary>
  90 + public string category { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 应缴金额
  94 + /// </summary>
  95 + public decimal dueAmount { get; set; }
  96 +
  97 + /// <summary>
  98 + /// 是否已缴(0-未缴,1-已缴)
  99 + /// </summary>
  100 + public int isPaid { get; set; }
  101 +
  102 + /// <summary>
  103 + /// 实际缴费金额(如果已缴费)
  104 + /// </summary>
  105 + public decimal? actualPaymentAmount { get; set; }
  106 + }
  107 +}
  108 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/LqContractCrInput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqContract
  5 +{
  6 + /// <summary>
  7 + /// 合同创建输入
  8 + /// </summary>
  9 + public class LqContractCrInput
  10 + {
  11 + /// <summary>
  12 + /// 门店ID(关联lq_mdxx.F_Id)
  13 + /// </summary>
  14 + [Required(ErrorMessage = "门店ID不能为空")]
  15 + [StringLength(50, ErrorMessage = "门店ID长度不能超过50个字符")]
  16 + [Display(Name = "门店ID")]
  17 + public string StoreId { get; set; }
  18 +
  19 + /// <summary>
  20 + /// 标题
  21 + /// </summary>
  22 + [Required(ErrorMessage = "标题不能为空")]
  23 + [StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]
  24 + [Display(Name = "标题")]
  25 + public string Title { get; set; }
  26 +
  27 + /// <summary>
  28 + /// 分类(string类型,自己填写)
  29 + /// </summary>
  30 + [StringLength(100, ErrorMessage = "分类长度不能超过100个字符")]
  31 + [Display(Name = "分类")]
  32 + public string Category { get; set; }
  33 +
  34 + /// <summary>
  35 + /// 户名
  36 + /// </summary>
  37 + [StringLength(200, ErrorMessage = "户名长度不能超过200个字符")]
  38 + [Display(Name = "户名")]
  39 + public string TenantName { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 合同起始日期
  43 + /// </summary>
  44 + [Required(ErrorMessage = "合同起始日期不能为空")]
  45 + [Display(Name = "合同起始日期")]
  46 + public DateTime ContractStartDate { get; set; }
  47 +
  48 + /// <summary>
  49 + /// 合同结束日期
  50 + /// </summary>
  51 + [Required(ErrorMessage = "合同结束日期不能为空")]
  52 + [Display(Name = "合同结束日期")]
  53 + public DateTime ContractEndDate { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 提前多少天提醒
  57 + /// </summary>
  58 + [Range(0, 365, ErrorMessage = "提前提醒天数必须在0-365之间")]
  59 + [Display(Name = "提前提醒天数")]
  60 + public int ReminderDays { get; set; } = 0;
  61 +
  62 + /// <summary>
  63 + /// 押金
  64 + /// </summary>
  65 + [Range(0, double.MaxValue, ErrorMessage = "押金不能小于0")]
  66 + [Display(Name = "押金")]
  67 + public decimal Deposit { get; set; } = 0;
  68 +
  69 + /// <summary>
  70 + /// 月租
  71 + /// </summary>
  72 + [Required(ErrorMessage = "月租不能为空")]
  73 + [Range(0, double.MaxValue, ErrorMessage = "月租不能小于0")]
  74 + [Display(Name = "月租")]
  75 + public decimal MonthlyRent { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 缴租金额(每次交租的金额,通常=月租×交租周期)
  79 + /// </summary>
  80 + [Required(ErrorMessage = "缴租金额不能为空")]
  81 + [Range(0, double.MaxValue, ErrorMessage = "缴租金额不能小于0")]
  82 + [Display(Name = "缴租金额")]
  83 + public decimal PaymentAmount { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 交租周期(数字,表示几个月,如1、3、6等)
  87 + /// </summary>
  88 + [Required(ErrorMessage = "交租周期不能为空")]
  89 + [Range(1, 12, ErrorMessage = "交租周期必须在1-12个月之间")]
  90 + [Display(Name = "交租周期")]
  91 + public int PaymentCycle { get; set; }
  92 +
  93 + /// <summary>
  94 + /// 备注
  95 + /// </summary>
  96 + [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
  97 + [Display(Name = "备注")]
  98 + public string Remarks { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 附件(存储附件路径或JSON)
  102 + /// </summary>
  103 + [StringLength(500, ErrorMessage = "附件长度不能超过500个字符")]
  104 + [Display(Name = "附件")]
  105 + public string Attachment { get; set; }
  106 + }
  107 +}
  108 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/LqContractInfoOutput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqContract
  5 +{
  6 + /// <summary>
  7 + /// 合同详情输出
  8 + /// </summary>
  9 + public class LqContractInfoOutput
  10 + {
  11 + /// <summary>
  12 + /// 合同ID
  13 + /// </summary>
  14 + public string id { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 门店ID
  18 + /// </summary>
  19 + public string storeId { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 店名
  23 + /// </summary>
  24 + public string storeName { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 标题
  28 + /// </summary>
  29 + public string title { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 分类
  33 + /// </summary>
  34 + public string category { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 户名
  38 + /// </summary>
  39 + public string tenantName { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 合同起始日期
  43 + /// </summary>
  44 + public DateTime contractStartDate { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 合同结束日期
  48 + /// </summary>
  49 + public DateTime contractEndDate { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 提前多少天提醒
  53 + /// </summary>
  54 + public int reminderDays { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 押金
  58 + /// </summary>
  59 + public decimal deposit { get; set; }
  60 +
  61 + /// <summary>
  62 + /// 下次应交时间
  63 + /// </summary>
  64 + public DateTime? nextPaymentDate { get; set; }
  65 +
  66 + /// <summary>
  67 + /// 月租
  68 + /// </summary>
  69 + public decimal monthlyRent { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 缴租金额
  73 + /// </summary>
  74 + public decimal paymentAmount { get; set; }
  75 +
  76 + /// <summary>
  77 + /// 交租周期
  78 + /// </summary>
  79 + public int paymentCycle { get; set; }
  80 +
  81 + /// <summary>
  82 + /// 备注
  83 + /// </summary>
  84 + public string remarks { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 附件
  88 + /// </summary>
  89 + public string attachment { get; set; }
  90 +
  91 + /// <summary>
  92 + /// 创建人ID
  93 + /// </summary>
  94 + public string createUser { get; set; }
  95 +
  96 + /// <summary>
  97 + /// 创建人姓名
  98 + /// </summary>
  99 + public string createUserName { get; set; }
  100 +
  101 + /// <summary>
  102 + /// 创建时间
  103 + /// </summary>
  104 + public DateTime createTime { get; set; }
  105 +
  106 + /// <summary>
  107 + /// 更新人ID
  108 + /// </summary>
  109 + public string updateUser { get; set; }
  110 +
  111 + /// <summary>
  112 + /// 更新人姓名
  113 + /// </summary>
  114 + public string updateUserName { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 更新时间
  118 + /// </summary>
  119 + public DateTime? updateTime { get; set; }
  120 +
  121 + /// <summary>
  122 + /// 是否有效
  123 + /// </summary>
  124 + public int isEffective { get; set; }
  125 +
  126 + /// <summary>
  127 + /// 月租明细列表
  128 + /// </summary>
  129 + public List<LqContractRentDetailListOutput> rentDetails { get; set; }
  130 + }
  131 +}
  132 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/LqContractListOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqContract
  4 +{
  5 + /// <summary>
  6 + /// 合同列表输出
  7 + /// </summary>
  8 + public class LqContractListOutput
  9 + {
  10 + /// <summary>
  11 + /// 合同ID
  12 + /// </summary>
  13 + public string id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店ID
  17 + /// </summary>
  18 + public string storeId { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 店名
  22 + /// </summary>
  23 + public string storeName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 标题
  27 + /// </summary>
  28 + public string title { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 分类
  32 + /// </summary>
  33 + public string category { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 户名
  37 + /// </summary>
  38 + public string tenantName { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 合同起始日期
  42 + /// </summary>
  43 + public DateTime contractStartDate { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 合同结束日期
  47 + /// </summary>
  48 + public DateTime contractEndDate { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 提前多少天提醒
  52 + /// </summary>
  53 + public int reminderDays { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 押金
  57 + /// </summary>
  58 + public decimal deposit { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 下次应交时间
  62 + /// </summary>
  63 + public DateTime? nextPaymentDate { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 月租
  67 + /// </summary>
  68 + public decimal monthlyRent { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 缴租金额
  72 + /// </summary>
  73 + public decimal paymentAmount { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 交租周期
  77 + /// </summary>
  78 + public int paymentCycle { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 备注
  82 + /// </summary>
  83 + public string remarks { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 附件
  87 + /// </summary>
  88 + public string attachment { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 创建人ID
  92 + /// </summary>
  93 + public string createUser { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 创建人姓名
  97 + /// </summary>
  98 + public string createUserName { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 创建时间
  102 + /// </summary>
  103 + public DateTime createTime { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 更新人ID
  107 + /// </summary>
  108 + public string updateUser { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 更新人姓名
  112 + /// </summary>
  113 + public string updateUserName { get; set; }
  114 +
  115 + /// <summary>
  116 + /// 更新时间
  117 + /// </summary>
  118 + public DateTime? updateTime { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 是否有效
  122 + /// </summary>
  123 + public int isEffective { get; set; }
  124 + }
  125 +}
  126 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/LqContractListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +using System.ComponentModel.DataAnnotations;
  4 +
  5 +namespace NCC.Extend.Entitys.Dto.LqContract
  6 +{
  7 + /// <summary>
  8 + /// 合同列表查询输入
  9 + /// </summary>
  10 + public class LqContractListQueryInput : PageInputBase
  11 + {
  12 + /// <summary>
  13 + /// 门店ID
  14 + /// </summary>
  15 + [Display(Name = "门店ID", Description = "根据门店ID筛选")]
  16 + public string StoreId { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 门店名称(模糊查询)
  20 + /// </summary>
  21 + [Display(Name = "门店名称", Description = "根据门店名称筛选")]
  22 + public string StoreName { get; set; }
  23 +
  24 + /// <summary>
  25 + /// 分类
  26 + /// </summary>
  27 + [Display(Name = "分类", Description = "根据分类筛选")]
  28 + public string Category { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 标题(模糊查询)
  32 + /// </summary>
  33 + [Display(Name = "标题", Description = "根据标题筛选")]
  34 + public string Title { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 合同起始日期(开始)
  38 + /// </summary>
  39 + [Display(Name = "合同起始日期(开始)", Description = "根据合同起始日期范围筛选")]
  40 + public DateTime? ContractStartDateBegin { get; set; }
  41 +
  42 + /// <summary>
  43 + /// 合同起始日期(结束)
  44 + /// </summary>
  45 + [Display(Name = "合同起始日期(结束)", Description = "根据合同起始日期范围筛选")]
  46 + public DateTime? ContractStartDateEnd { get; set; }
  47 +
  48 + /// <summary>
  49 + /// 合同结束日期(开始)
  50 + /// </summary>
  51 + [Display(Name = "合同结束日期(开始)", Description = "根据合同结束日期范围筛选")]
  52 + public DateTime? ContractEndDateBegin { get; set; }
  53 +
  54 + /// <summary>
  55 + /// 合同结束日期(结束)
  56 + /// </summary>
  57 + [Display(Name = "合同结束日期(结束)", Description = "根据合同结束日期范围筛选")]
  58 + public DateTime? ContractEndDateEnd { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 是否有效
  62 + /// </summary>
  63 + [Display(Name = "是否有效", Description = "根据是否有效筛选")]
  64 + public int? IsEffective { get; set; }
  65 + }
  66 +}
  67 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/LqContractRentDetailListOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqContract
  4 +{
  5 + /// <summary>
  6 + /// 月租明细列表输出
  7 + /// </summary>
  8 + public class LqContractRentDetailListOutput
  9 + {
  10 + /// <summary>
  11 + /// 明细ID
  12 + /// </summary>
  13 + public string id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 合同ID
  17 + /// </summary>
  18 + public string contractId { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 应缴月份(表示哪个月份,格式:YYYY-MM-01)
  22 + /// </summary>
  23 + public DateTime paymentMonth { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 应缴日期(具体应缴日期)
  27 + /// </summary>
  28 + public DateTime dueDate { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 应缴金额
  32 + /// </summary>
  33 + public decimal dueAmount { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 是否已缴(0-未缴,1-已缴)
  37 + /// </summary>
  38 + public int isPaid { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 实际缴费时间
  42 + /// </summary>
  43 + public DateTime? actualPaymentDate { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 实际缴费金额
  47 + /// </summary>
  48 + public decimal? actualPaymentAmount { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 备注
  52 + /// </summary>
  53 + public string remarks { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 创建人ID
  57 + /// </summary>
  58 + public string createUser { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 创建人姓名
  62 + /// </summary>
  63 + public string createUserName { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 创建时间
  67 + /// </summary>
  68 + public DateTime createTime { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 更新人ID
  72 + /// </summary>
  73 + public string updateUser { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 更新人姓名
  77 + /// </summary>
  78 + public string updateUserName { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 更新时间
  82 + /// </summary>
  83 + public DateTime? updateTime { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 是否有效
  87 + /// </summary>
  88 + public int isEffective { get; set; }
  89 + }
  90 +}
  91 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/LqContractRentDetailMarkPaidInput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqContract
  5 +{
  6 + /// <summary>
  7 + /// 月租明细标记已缴费输入
  8 + /// </summary>
  9 + public class LqContractRentDetailMarkPaidInput
  10 + {
  11 + /// <summary>
  12 + /// 明细ID
  13 + /// </summary>
  14 + [Required(ErrorMessage = "明细ID不能为空")]
  15 + [StringLength(50, ErrorMessage = "明细ID长度不能超过50个字符")]
  16 + [Display(Name = "明细ID")]
  17 + public string Id { get; set; }
  18 +
  19 + /// <summary>
  20 + /// 实际缴费时间
  21 + /// </summary>
  22 + [Required(ErrorMessage = "实际缴费时间不能为空")]
  23 + [Display(Name = "实际缴费时间")]
  24 + public DateTime ActualPaymentDate { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 实际缴费金额
  28 + /// </summary>
  29 + [Required(ErrorMessage = "实际缴费金额不能为空")]
  30 + [Range(0, double.MaxValue, ErrorMessage = "实际缴费金额不能小于0")]
  31 + [Display(Name = "实际缴费金额")]
  32 + public decimal ActualPaymentAmount { get; set; }
  33 +
  34 + /// <summary>
  35 + /// 备注
  36 + /// </summary>
  37 + [StringLength(500, ErrorMessage = "备注长度不能超过500个字符")]
  38 + [Display(Name = "备注")]
  39 + public string Remarks { get; set; }
  40 + }
  41 +}
  42 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqContract/LqContractUpInput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqContract
  5 +{
  6 + /// <summary>
  7 + /// 合同更新输入
  8 + /// </summary>
  9 + public class LqContractUpInput
  10 + {
  11 + /// <summary>
  12 + /// 合同ID
  13 + /// </summary>
  14 + [Required(ErrorMessage = "合同ID不能为空")]
  15 + [StringLength(50, ErrorMessage = "合同ID长度不能超过50个字符")]
  16 + [Display(Name = "合同ID")]
  17 + public string Id { get; set; }
  18 +
  19 + /// <summary>
  20 + /// 门店ID(关联lq_mdxx.F_Id)
  21 + /// </summary>
  22 + [Required(ErrorMessage = "门店ID不能为空")]
  23 + [StringLength(50, ErrorMessage = "门店ID长度不能超过50个字符")]
  24 + [Display(Name = "门店ID")]
  25 + public string StoreId { get; set; }
  26 +
  27 + /// <summary>
  28 + /// 标题
  29 + /// </summary>
  30 + [Required(ErrorMessage = "标题不能为空")]
  31 + [StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]
  32 + [Display(Name = "标题")]
  33 + public string Title { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 分类(string类型,自己填写)
  37 + /// </summary>
  38 + [StringLength(100, ErrorMessage = "分类长度不能超过100个字符")]
  39 + [Display(Name = "分类")]
  40 + public string Category { get; set; }
  41 +
  42 + /// <summary>
  43 + /// 户名
  44 + /// </summary>
  45 + [StringLength(200, ErrorMessage = "户名长度不能超过200个字符")]
  46 + [Display(Name = "户名")]
  47 + public string TenantName { get; set; }
  48 +
  49 + /// <summary>
  50 + /// 合同起始日期
  51 + /// </summary>
  52 + [Required(ErrorMessage = "合同起始日期不能为空")]
  53 + [Display(Name = "合同起始日期")]
  54 + public DateTime ContractStartDate { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 合同结束日期
  58 + /// </summary>
  59 + [Required(ErrorMessage = "合同结束日期不能为空")]
  60 + [Display(Name = "合同结束日期")]
  61 + public DateTime ContractEndDate { get; set; }
  62 +
  63 + /// <summary>
  64 + /// 提前多少天提醒
  65 + /// </summary>
  66 + [Range(0, 365, ErrorMessage = "提前提醒天数必须在0-365之间")]
  67 + [Display(Name = "提前提醒天数")]
  68 + public int ReminderDays { get; set; } = 0;
  69 +
  70 + /// <summary>
  71 + /// 押金
  72 + /// </summary>
  73 + [Range(0, double.MaxValue, ErrorMessage = "押金不能小于0")]
  74 + [Display(Name = "押金")]
  75 + public decimal Deposit { get; set; } = 0;
  76 +
  77 + /// <summary>
  78 + /// 月租
  79 + /// </summary>
  80 + [Required(ErrorMessage = "月租不能为空")]
  81 + [Range(0, double.MaxValue, ErrorMessage = "月租不能小于0")]
  82 + [Display(Name = "月租")]
  83 + public decimal MonthlyRent { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 缴租金额(每次交租的金额,通常=月租×交租周期)
  87 + /// </summary>
  88 + [Required(ErrorMessage = "缴租金额不能为空")]
  89 + [Range(0, double.MaxValue, ErrorMessage = "缴租金额不能小于0")]
  90 + [Display(Name = "缴租金额")]
  91 + public decimal PaymentAmount { get; set; }
  92 +
  93 + /// <summary>
  94 + /// 交租周期(数字,表示几个月,如1、3、6等)
  95 + /// </summary>
  96 + [Required(ErrorMessage = "交租周期不能为空")]
  97 + [Range(1, 12, ErrorMessage = "交租周期必须在1-12个月之间")]
  98 + [Display(Name = "交租周期")]
  99 + public int PaymentCycle { get; set; }
  100 +
  101 + /// <summary>
  102 + /// 备注
  103 + /// </summary>
  104 + [StringLength(1000, ErrorMessage = "备注长度不能超过1000个字符")]
  105 + [Display(Name = "备注")]
  106 + public string Remarks { get; set; }
  107 +
  108 + /// <summary>
  109 + /// 附件(存储附件路径或JSON)
  110 + /// </summary>
  111 + [StringLength(500, ErrorMessage = "附件长度不能超过500个字符")]
  112 + [Display(Name = "附件")]
  113 + public string Attachment { get; set; }
  114 + }
  115 +}
  116 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchCreateInput.cs
... ... @@ -23,6 +23,29 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
23 23 [StringLength(50, ErrorMessage = "批次ID长度不能超过50个字符")]
24 24 [Display(Name = "使用批次ID")]
25 25 public string BatchId { get; set; }
  26 +
  27 + /// <summary>
  28 + /// 审批人ID(财务老师或库管,必填,创建申请记录并提交审批)
  29 + /// </summary>
  30 + [Required(ErrorMessage = "审批人ID不能为空")]
  31 + [StringLength(50, ErrorMessage = "审批人ID长度不能超过50个字符")]
  32 + [Display(Name = "审批人ID")]
  33 + public string ApproverId { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 申请门店ID(必填)
  37 + /// </summary>
  38 + [Required(ErrorMessage = "申请门店ID不能为空")]
  39 + [StringLength(50, ErrorMessage = "申请门店ID长度不能超过50个字符")]
  40 + [Display(Name = "申请门店ID")]
  41 + public string ApplicationStoreId { get; set; }
  42 +
  43 + /// <summary>
  44 + /// 备注(可选)
  45 + /// </summary>
  46 + [StringLength(500, ErrorMessage = "备注长度不能超过500个字符")]
  47 + [Display(Name = "备注")]
  48 + public string Remarks { get; set; }
26 49 }
27 50  
28 51 /// <summary>
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchInfoOutput.cs
... ... @@ -57,6 +57,67 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
57 57 /// 使用记录列表
58 58 /// </summary>
59 59 public List<LqInventoryUsageListOutput> UsageRecords { get; set; }
  60 +
  61 + /// <summary>
  62 + /// 申请记录信息(如果有)
  63 + /// </summary>
  64 + public ApplicationInfo ApplicationInfo { get; set; }
  65 + }
  66 +
  67 + /// <summary>
  68 + /// 申请记录信息
  69 + /// </summary>
  70 + public class ApplicationInfo
  71 + {
  72 + /// <summary>
  73 + /// 申请ID
  74 + /// </summary>
  75 + public string Id { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 申请人ID
  79 + /// </summary>
  80 + public string ApplicationUserId { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 申请人姓名
  84 + /// </summary>
  85 + public string ApplicationUserName { get; set; }
  86 +
  87 + /// <summary>
  88 + /// 申请门店ID
  89 + /// </summary>
  90 + public string ApplicationStoreId { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 申请时间
  94 + /// </summary>
  95 + public DateTime ApplicationTime { get; set; }
  96 +
  97 + /// <summary>
  98 + /// 审批状态
  99 + /// </summary>
  100 + public string ApprovalStatus { get; set; }
  101 +
  102 + /// <summary>
  103 + /// 是否已领取
  104 + /// </summary>
  105 + public int IsReceived { get; set; }
  106 +
  107 + /// <summary>
  108 + /// 领取时间
  109 + /// </summary>
  110 + public DateTime? ReceiveTime { get; set; }
  111 +
  112 + /// <summary>
  113 + /// 领取人ID
  114 + /// </summary>
  115 + public string ReceiveUser { get; set; }
  116 +
  117 + /// <summary>
  118 + /// 备注
  119 + /// </summary>
  120 + public string Remarks { get; set; }
60 121 }
61 122 }
62 123  
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/StoreReceiveStatisticsInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
  4 +{
  5 + /// <summary>
  6 + /// 门店领取统计查询输入
  7 + /// </summary>
  8 + public class StoreReceiveStatisticsInput
  9 + {
  10 + /// <summary>
  11 + /// 统计年份
  12 + /// </summary>
  13 + [Required(ErrorMessage = "统计年份不能为空")]
  14 + [Range(2020, 2100, ErrorMessage = "年份必须在2020-2100之间")]
  15 + [Display(Name = "统计年份")]
  16 + public int Year { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 统计月份(1-12)
  20 + /// </summary>
  21 + [Required(ErrorMessage = "统计月份不能为空")]
  22 + [Range(1, 12, ErrorMessage = "月份必须在1-12之间")]
  23 + [Display(Name = "统计月份")]
  24 + public int Month { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 门店ID(可选,用于筛选特定门店)
  28 + /// </summary>
  29 + [StringLength(50, ErrorMessage = "门店ID长度不能超过50个字符")]
  30 + [Display(Name = "门店ID")]
  31 + public string StoreId { get; set; }
  32 + }
  33 +}
  34 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationApproveInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsageApplication
  2 +{
  3 + /// <summary>
  4 + /// 库存使用申请审批输入
  5 + /// </summary>
  6 + public class LqInventoryUsageApplicationApproveInput
  7 + {
  8 + /// <summary>
  9 + /// 审批结果(通过/不通过/退回)
  10 + /// </summary>
  11 + public string result { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 审批意见
  15 + /// </summary>
  16 + public string opinion { get; set; }
  17 + }
  18 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationCrInput.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsageApplication
  4 +{
  5 + /// <summary>
  6 + /// 库存使用申请创建输入
  7 + /// </summary>
  8 + public class LqInventoryUsageApplicationCrInput
  9 + {
  10 + /// <summary>
  11 + /// 使用批次ID(关联lq_inventory_usage.F_UsageBatchId)
  12 + /// </summary>
  13 + public string usageBatchId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 申请门店ID
  17 + /// </summary>
  18 + public string applicationStoreId { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 备注
  22 + /// </summary>
  23 + public string remarks { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 审批节点配置(单节点,固定为1个节点)
  27 + /// </summary>
  28 + public ApprovalNodeConfig node { get; set; }
  29 + }
  30 +
  31 + /// <summary>
  32 + /// 审批节点配置
  33 + /// </summary>
  34 + public class ApprovalNodeConfig
  35 + {
  36 + /// <summary>
  37 + /// 节点序号(固定为1)
  38 + /// </summary>
  39 + public int nodeOrder { get; set; } = 1;
  40 +
  41 + /// <summary>
  42 + /// 节点名称(如:财务审批、库管审批)
  43 + /// </summary>
  44 + public string nodeName { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 审批类型(会签/或签,单节点通常使用"或签")
  48 + /// </summary>
  49 + public string approvalType { get; set; } = "或签";
  50 +
  51 + /// <summary>
  52 + /// 审批人ID列表
  53 + /// </summary>
  54 + public List<string> approverIds { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 审批人姓名列表
  58 + /// </summary>
  59 + public List<string> approverNames { get; set; }
  60 + }
  61 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationInfoOutput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsageApplication
  5 +{
  6 + /// <summary>
  7 + /// 库存使用申请详情输出
  8 + /// </summary>
  9 + public class LqInventoryUsageApplicationInfoOutput
  10 + {
  11 + /// <summary>
  12 + /// 申请编号
  13 + /// </summary>
  14 + public string id { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 使用批次ID
  18 + /// </summary>
  19 + public string usageBatchId { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 申请人ID
  23 + /// </summary>
  24 + public string applicationUserId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 申请人姓名
  28 + /// </summary>
  29 + public string applicationUserName { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 申请门店ID
  33 + /// </summary>
  34 + public string applicationStoreId { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 申请门店名称
  38 + /// </summary>
  39 + public string applicationStoreName { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 申请时间
  43 + /// </summary>
  44 + public DateTime applicationTime { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 节点数量(固定为1)
  48 + /// </summary>
  49 + public int nodeCount { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 当前审批节点(0-待审批,1-审批中,2-已完成)
  53 + /// </summary>
  54 + public int currentNodeOrder { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 当前节点ID
  58 + /// </summary>
  59 + public string currentNodeId { get; set; }
  60 +
  61 + /// <summary>
  62 + /// 审批状态(待审批/审批中/已通过/未通过/已退回)
  63 + /// </summary>
  64 + public string approvalStatus { get; set; }
  65 +
  66 + /// <summary>
  67 + /// 是否已领取(1-已领取,0-未领取)
  68 + /// </summary>
  69 + public int isReceived { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 领取时间
  73 + /// </summary>
  74 + public DateTime? receiveTime { get; set; }
  75 +
  76 + /// <summary>
  77 + /// 领取人ID
  78 + /// </summary>
  79 + public string receiveUser { get; set; }
  80 +
  81 + /// <summary>
  82 + /// 领取人姓名
  83 + /// </summary>
  84 + public string receiveUserName { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 备注
  88 + /// </summary>
  89 + public string remarks { get; set; }
  90 +
  91 + /// <summary>
  92 + /// 创建时间
  93 + /// </summary>
  94 + public DateTime createTime { get; set; }
  95 +
  96 + /// <summary>
  97 + /// 创建人ID
  98 + /// </summary>
  99 + public string createUser { get; set; }
  100 +
  101 + /// <summary>
  102 + /// 创建人姓名
  103 + /// </summary>
  104 + public string createUserName { get; set; }
  105 +
  106 + /// <summary>
  107 + /// 更新时间
  108 + /// </summary>
  109 + public DateTime? updateTime { get; set; }
  110 +
  111 + /// <summary>
  112 + /// 更新人ID
  113 + /// </summary>
  114 + public string updateUser { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 更新人姓名
  118 + /// </summary>
  119 + public string updateUserName { get; set; }
  120 +
  121 + /// <summary>
  122 + /// 是否有效(1-有效,0-无效)
  123 + /// </summary>
  124 + public int isEffective { get; set; }
  125 +
  126 + /// <summary>
  127 + /// 审批节点信息
  128 + /// </summary>
  129 + public ApprovalNodeInfo nodeInfo { get; set; }
  130 +
  131 + /// <summary>
  132 + /// 审批记录列表
  133 + /// </summary>
  134 + public List<ApprovalRecordInfo> approvalRecords { get; set; }
  135 +
  136 + /// <summary>
  137 + /// 使用记录列表(该批次的所有使用记录)
  138 + /// </summary>
  139 + public List<UsageRecordInfo> usageRecords { get; set; }
  140 + }
  141 +
  142 + /// <summary>
  143 + /// 审批节点信息
  144 + /// </summary>
  145 + public class ApprovalNodeInfo
  146 + {
  147 + /// <summary>
  148 + /// 节点ID
  149 + /// </summary>
  150 + public string nodeId { get; set; }
  151 +
  152 + /// <summary>
  153 + /// 节点序号
  154 + /// </summary>
  155 + public int nodeOrder { get; set; }
  156 +
  157 + /// <summary>
  158 + /// 节点名称
  159 + /// </summary>
  160 + public string nodeName { get; set; }
  161 +
  162 + /// <summary>
  163 + /// 审批类型(会签/或签)
  164 + /// </summary>
  165 + public string approvalType { get; set; }
  166 +
  167 + /// <summary>
  168 + /// 审批人列表
  169 + /// </summary>
  170 + public List<ApproverInfo> approvers { get; set; }
  171 + }
  172 +
  173 + /// <summary>
  174 + /// 审批人信息
  175 + /// </summary>
  176 + public class ApproverInfo
  177 + {
  178 + /// <summary>
  179 + /// 审批人ID
  180 + /// </summary>
  181 + public string userId { get; set; }
  182 +
  183 + /// <summary>
  184 + /// 审批人姓名
  185 + /// </summary>
  186 + public string userName { get; set; }
  187 + }
  188 +
  189 + /// <summary>
  190 + /// 审批记录信息
  191 + /// </summary>
  192 + public class ApprovalRecordInfo
  193 + {
  194 + /// <summary>
  195 + /// 审批记录ID
  196 + /// </summary>
  197 + public string id { get; set; }
  198 +
  199 + /// <summary>
  200 + /// 节点ID
  201 + /// </summary>
  202 + public string nodeId { get; set; }
  203 +
  204 + /// <summary>
  205 + /// 节点序号
  206 + /// </summary>
  207 + public int nodeOrder { get; set; }
  208 +
  209 + /// <summary>
  210 + /// 审批人ID
  211 + /// </summary>
  212 + public string approverId { get; set; }
  213 +
  214 + /// <summary>
  215 + /// 审批人姓名
  216 + /// </summary>
  217 + public string approverName { get; set; }
  218 +
  219 + /// <summary>
  220 + /// 审批结果(待审批/已通过/未通过/已退回)
  221 + /// </summary>
  222 + public string approvalResult { get; set; }
  223 +
  224 + /// <summary>
  225 + /// 审批意见
  226 + /// </summary>
  227 + public string approvalOpinion { get; set; }
  228 +
  229 + /// <summary>
  230 + /// 审批时间
  231 + /// </summary>
  232 + public DateTime? approvalTime { get; set; }
  233 +
  234 + /// <summary>
  235 + /// 是否当前节点(1-是,0-否)
  236 + /// </summary>
  237 + public int isCurrentNode { get; set; }
  238 + }
  239 +
  240 + /// <summary>
  241 + /// 使用记录信息
  242 + /// </summary>
  243 + public class UsageRecordInfo
  244 + {
  245 + /// <summary>
  246 + /// 使用记录ID
  247 + /// </summary>
  248 + public string id { get; set; }
  249 +
  250 + /// <summary>
  251 + /// 产品ID
  252 + /// </summary>
  253 + public string productId { get; set; }
  254 +
  255 + /// <summary>
  256 + /// 产品名称
  257 + /// </summary>
  258 + public string productName { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 产品类别
  262 + /// </summary>
  263 + public string productCategory { get; set; }
  264 +
  265 + /// <summary>
  266 + /// 产品价格
  267 + /// </summary>
  268 + public decimal productPrice { get; set; }
  269 +
  270 + /// <summary>
  271 + /// 门店ID
  272 + /// </summary>
  273 + public string storeId { get; set; }
  274 +
  275 + /// <summary>
  276 + /// 门店名称
  277 + /// </summary>
  278 + public string storeName { get; set; }
  279 +
  280 + /// <summary>
  281 + /// 使用时间
  282 + /// </summary>
  283 + public DateTime usageTime { get; set; }
  284 +
  285 + /// <summary>
  286 + /// 使用数量
  287 + /// </summary>
  288 + public int usageQuantity { get; set; }
  289 +
  290 + /// <summary>
  291 + /// 单价
  292 + /// </summary>
  293 + public decimal unitPrice { get; set; }
  294 +
  295 + /// <summary>
  296 + /// 合计金额
  297 + /// </summary>
  298 + public decimal totalAmount { get; set; }
  299 +
  300 + /// <summary>
  301 + /// 关联消耗ID
  302 + /// </summary>
  303 + public string relatedConsumeId { get; set; }
  304 + }
  305 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationListOutput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsageApplication
  5 +{
  6 + /// <summary>
  7 + /// 库存使用申请列表输出
  8 + /// </summary>
  9 + public class LqInventoryUsageApplicationListOutput
  10 + {
  11 + /// <summary>
  12 + /// 申请编号
  13 + /// </summary>
  14 + public string id { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 使用批次ID
  18 + /// </summary>
  19 + public string usageBatchId { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 申请人ID
  23 + /// </summary>
  24 + public string applicationUserId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 申请人姓名
  28 + /// </summary>
  29 + public string applicationUserName { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 申请门店ID
  33 + /// </summary>
  34 + public string applicationStoreId { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 申请门店名称
  38 + /// </summary>
  39 + public string applicationStoreName { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 申请时间
  43 + /// </summary>
  44 + public DateTime applicationTime { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 审批状态(待审批/审批中/已通过/未通过/已退回)
  48 + /// </summary>
  49 + public string approvalStatus { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 是否已领取(1-已领取,0-未领取)
  53 + /// </summary>
  54 + public int isReceived { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 领取时间
  58 + /// </summary>
  59 + public DateTime? receiveTime { get; set; }
  60 +
  61 + /// <summary>
  62 + /// 领取人ID
  63 + /// </summary>
  64 + public string receiveUser { get; set; }
  65 +
  66 + /// <summary>
  67 + /// 领取人姓名
  68 + /// </summary>
  69 + public string receiveUserName { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 备注
  73 + /// </summary>
  74 + public string remarks { get; set; }
  75 +
  76 + /// <summary>
  77 + /// 创建时间
  78 + /// </summary>
  79 + public DateTime createTime { get; set; }
  80 +
  81 + /// <summary>
  82 + /// 当前审批人信息列表
  83 + /// </summary>
  84 + public List<CurrentApproverInfo> currentApprovers { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 使用记录统计信息(批次总数量、总金额等)
  88 + /// </summary>
  89 + public BatchUsageInfo batchUsageInfo { get; set; }
  90 + }
  91 +
  92 + /// <summary>
  93 + /// 当前审批人信息
  94 + /// </summary>
  95 + public class CurrentApproverInfo
  96 + {
  97 + /// <summary>
  98 + /// 审批人ID
  99 + /// </summary>
  100 + public string approverId { get; set; }
  101 +
  102 + /// <summary>
  103 + /// 审批人姓名
  104 + /// </summary>
  105 + public string approverName { get; set; }
  106 +
  107 + /// <summary>
  108 + /// 审批结果(待审批/已通过/未通过/已退回)
  109 + /// </summary>
  110 + public string approvalResult { get; set; }
  111 + }
  112 +
  113 + /// <summary>
  114 + /// 批次使用统计信息
  115 + /// </summary>
  116 + public class BatchUsageInfo
  117 + {
  118 + /// <summary>
  119 + /// 使用记录总数
  120 + /// </summary>
  121 + public int totalCount { get; set; }
  122 +
  123 + /// <summary>
  124 + /// 总使用数量
  125 + /// </summary>
  126 + public int totalQuantity { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 总金额
  130 + /// </summary>
  131 + public decimal totalAmount { get; set; }
  132 + }
  133 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationListQueryInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsageApplication
  2 +{
  3 + /// <summary>
  4 + /// 库存使用申请列表查询输入
  5 + /// </summary>
  6 + public class LqInventoryUsageApplicationListQueryInput
  7 + {
  8 + /// <summary>
  9 + /// 当前页码
  10 + /// </summary>
  11 + public int currentPage { get; set; } = 1;
  12 +
  13 + /// <summary>
  14 + /// 每页数量
  15 + /// </summary>
  16 + public int pageSize { get; set; } = 20;
  17 +
  18 + /// <summary>
  19 + /// 排序字段
  20 + /// </summary>
  21 + public string sidx { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 排序方式(asc/desc)
  25 + /// </summary>
  26 + public string sort { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 关键字搜索
  30 + /// </summary>
  31 + public string keyword { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 申请编号
  35 + /// </summary>
  36 + public string id { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 使用批次ID
  40 + /// </summary>
  41 + public string usageBatchId { get; set; }
  42 +
  43 + /// <summary>
  44 + /// 申请人ID
  45 + /// </summary>
  46 + public string applicationUserId { get; set; }
  47 +
  48 + /// <summary>
  49 + /// 申请门店ID
  50 + /// </summary>
  51 + public string applicationStoreId { get; set; }
  52 +
  53 + /// <summary>
  54 + /// 审批状态(待审批/审批中/已通过/未通过/已退回)
  55 + /// </summary>
  56 + public string approvalStatus { get; set; }
  57 +
  58 + /// <summary>
  59 + /// 是否已领取(1-已领取,0-未领取)
  60 + /// </summary>
  61 + public int? isReceived { get; set; }
  62 +
  63 + /// <summary>
  64 + /// 申请时间开始
  65 + /// </summary>
  66 + public System.DateTime? applicationTimeStart { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 申请时间结束
  70 + /// </summary>
  71 + public System.DateTime? applicationTimeEnd { get; set; }
  72 +
  73 + /// <summary>
  74 + /// 是否有效(1-有效,0-无效)
  75 + /// </summary>
  76 + public int? isEffective { get; set; }
  77 +
  78 + /// <summary>
  79 + /// 查询JSON(用于复杂查询条件)
  80 + /// </summary>
  81 + public string queryJson { get; set; }
  82 + }
  83 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationReceiveInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsageApplication
  2 +{
  3 + /// <summary>
  4 + /// 库存使用申请领取输入
  5 + /// </summary>
  6 + public class LqInventoryUsageApplicationReceiveInput
  7 + {
  8 + /// <summary>
  9 + /// 是否已领取(1-已领取,0-未领取)
  10 + /// </summary>
  11 + public int isReceived { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 备注
  15 + /// </summary>
  16 + public string remarks { get; set; }
  17 + }
  18 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationUpInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqInventoryUsageApplication
  2 +{
  3 + /// <summary>
  4 + /// 库存使用申请更新输入
  5 + /// </summary>
  6 + public class LqInventoryUsageApplicationUpInput
  7 + {
  8 + /// <summary>
  9 + /// 申请编号
  10 + /// </summary>
  11 + public string id { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 是否已领取(1-已领取,0-未领取)
  15 + /// </summary>
  16 + public int? isReceived { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 备注
  20 + /// </summary>
  21 + public string remarks { get; set; }
  22 + }
  23 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReimbursementApplicationInfoOutput.cs
... ... @@ -27,7 +27,7 @@ namespace NCC.Extend.Entitys.Dto.LqReimbursementApplication
27 27 /// 申请门店
28 28 /// </summary>
29 29 public string applicationStoreId { get; set; }
30   -
  30 +
31 31 /// <summary>
32 32 /// 申请门店名称
33 33 /// </summary>
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherSalaryInput.cs
... ... @@ -30,3 +30,5 @@ namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary
30 30 }
31 31 }
32 32  
  33 +
  34 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherSalaryOutput.cs
... ... @@ -319,3 +319,5 @@ namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary
319 319 }
320 320 }
321 321  
  322 +
  323 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_contract/LqContractEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_contract
  6 +{
  7 + /// <summary>
  8 + /// 合同表
  9 + /// </summary>
  10 + [SugarTable("lq_contract")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqContractEntity
  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_StoreName")]
  30 + public string StoreName { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 标题
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_Title")]
  36 + public string Title { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 分类(string类型,自己填写)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_Category")]
  42 + public string Category { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 户名
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_TenantName")]
  48 + public string TenantName { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 合同起始日期
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_ContractStartDate")]
  54 + public DateTime ContractStartDate { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 合同结束日期
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_ContractEndDate")]
  60 + public DateTime ContractEndDate { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 提前多少天提醒
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_ReminderDays")]
  66 + public int ReminderDays { get; set; } = 0;
  67 +
  68 + /// <summary>
  69 + /// 押金
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_Deposit", DecimalDigits = 2)]
  72 + public decimal Deposit { get; set; } = 0;
  73 +
  74 + /// <summary>
  75 + /// 下次应交时间
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_NextPaymentDate")]
  78 + public DateTime? NextPaymentDate { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 月租
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_MonthlyRent", DecimalDigits = 2)]
  84 + public decimal MonthlyRent { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 缴租金额(每次交租的金额,通常=月租×交租周期)
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_PaymentAmount", DecimalDigits = 2)]
  90 + public decimal PaymentAmount { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 交租周期(数字,表示几个月,如1、3、6等)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_PaymentCycle")]
  96 + public int PaymentCycle { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 备注
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_Remarks")]
  102 + public string Remarks { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 附件(存储附件路径或JSON)
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_Attachment")]
  108 + public string Attachment { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 创建人ID
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_CreateUser")]
  114 + public string CreateUser { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 创建时间
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_CreateTime")]
  120 + public DateTime CreateTime { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 更新人ID
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_UpdateUser")]
  126 + public string UpdateUser { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 更新时间
  130 + /// </summary>
  131 + [SugarColumn(ColumnName = "F_UpdateTime")]
  132 + public DateTime? UpdateTime { get; set; }
  133 +
  134 + /// <summary>
  135 + /// 是否有效(1-有效,0-无效)
  136 + /// </summary>
  137 + [SugarColumn(ColumnName = "F_IsEffective")]
  138 + public int IsEffective { get; set; } = 1;
  139 + }
  140 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_contract_rent_detail/LqContractRentDetailEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_contract_rent_detail
  6 +{
  7 + /// <summary>
  8 + /// 月租明细表
  9 + /// </summary>
  10 + [SugarTable("lq_contract_rent_detail")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqContractRentDetailEntity
  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_contract.F_Id)
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_ContractId")]
  24 + public string ContractId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 应缴月份(表示哪个月份,格式:YYYY-MM-01)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_PaymentMonth")]
  30 + public DateTime PaymentMonth { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 应缴日期(具体应缴日期)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_DueDate")]
  36 + public DateTime DueDate { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 应缴金额
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_DueAmount", DecimalDigits = 2)]
  42 + public decimal DueAmount { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 是否已缴(0-未缴,1-已缴)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_IsPaid")]
  48 + public int IsPaid { get; set; } = 0;
  49 +
  50 + /// <summary>
  51 + /// 实际缴费时间
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_ActualPaymentDate")]
  54 + public DateTime? ActualPaymentDate { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 实际缴费金额
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_ActualPaymentAmount", DecimalDigits = 2)]
  60 + public decimal? ActualPaymentAmount { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 备注
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_Remarks")]
  66 + public string Remarks { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 创建人ID
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_CreateUser")]
  72 + public string CreateUser { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 创建时间
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_CreateTime")]
  78 + public DateTime CreateTime { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 更新人ID
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_UpdateUser")]
  84 + public string UpdateUser { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 更新时间
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_UpdateTime")]
  90 + public DateTime? UpdateTime { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 是否有效(1-有效,0-无效)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_IsEffective")]
  96 + public int IsEffective { get; set; } = 1;
  97 + }
  98 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage/LqInventoryUsageEntity.cs
... ... @@ -42,6 +42,18 @@ namespace NCC.Extend.Entitys.lq_inventory_usage
42 42 public int UsageQuantity { get; set; }
43 43  
44 44 /// <summary>
  45 + /// 单价(从产品表F_Price获取)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_UnitPrice")]
  48 + public decimal UnitPrice { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 合计金额(单价×数量)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_TotalAmount")]
  54 + public decimal TotalAmount { get; set; }
  55 +
  56 + /// <summary>
45 57 /// 关联消耗ID
46 58 /// </summary>
47 59 [SugarColumn(ColumnName = "F_RelatedConsumeId")]
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_inventory_usage_application
  6 +{
  7 + /// <summary>
  8 + /// 库存使用申请表
  9 + /// </summary>
  10 + [SugarTable("lq_inventory_usage_application")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqInventoryUsageApplicationEntity
  13 + {
  14 + /// <summary>
  15 + /// 申请编号
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 使用批次ID(关联lq_inventory_usage.F_UsageBatchId)
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_UsageBatchId")]
  24 + public string UsageBatchId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 申请人ID
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_ApplicationUserId")]
  30 + public string ApplicationUserId { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 申请人姓名
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_ApplicationUserName")]
  36 + public string ApplicationUserName { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 申请门店ID
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_ApplicationStoreId")]
  42 + public string ApplicationStoreId { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 申请时间
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_ApplicationTime")]
  48 + public DateTime ApplicationTime { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 节点数量(固定为1)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_NodeCount")]
  54 + public int NodeCount { get; set; } = 1;
  55 +
  56 + /// <summary>
  57 + /// 当前审批节点(0-待审批,1-审批中,2-已完成)
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_CurrentNodeOrder")]
  60 + public int CurrentNodeOrder { get; set; } = 0;
  61 +
  62 + /// <summary>
  63 + /// 当前节点ID
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_CurrentNodeId")]
  66 + public string CurrentNodeId { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 审批状态(待审批/审批中/已通过/未通过/已退回)
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_ApprovalStatus")]
  72 + public string ApprovalStatus { get; set; } = "待审批";
  73 +
  74 + /// <summary>
  75 + /// 是否已领取(1-已领取,0-未领取)
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_IsReceived")]
  78 + public int IsReceived { get; set; } = 0;
  79 +
  80 + /// <summary>
  81 + /// 领取时间
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_ReceiveTime")]
  84 + public DateTime? ReceiveTime { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 领取人ID
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_ReceiveUser")]
  90 + public string ReceiveUser { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 备注
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_Remarks")]
  96 + public string Remarks { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 创建时间
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_CreateTime")]
  102 + public DateTime CreateTime { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 创建人ID
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_CreateUser")]
  108 + public string CreateUser { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 更新时间
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_UpdateTime")]
  114 + public DateTime? UpdateTime { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 更新人ID
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_UpdateUser")]
  120 + public string UpdateUser { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 是否有效(1-有效,0-无效)
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_IsEffective")]
  126 + public int IsEffective { get; set; } = 1;
  127 + }
  128 +}
  129 +
  130 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_inventory_usage_application_node
  6 +{
  7 + /// <summary>
  8 + /// 库存使用申请节点表(每个申请的节点配置,固定为1个节点)
  9 + /// </summary>
  10 + [SugarTable("lq_inventory_usage_application_node")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqInventoryUsageApplicationNodeEntity
  13 + {
  14 + /// <summary>
  15 + /// 节点编号
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 申请ID
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_ApplicationId")]
  24 + public string ApplicationId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 节点顺序(固定为1)
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_NodeOrder")]
  30 + public int NodeOrder { get; set; } = 1;
  31 +
  32 + /// <summary>
  33 + /// 节点名称
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_NodeName")]
  36 + public string NodeName { get; set; } = "审批";
  37 +
  38 + /// <summary>
  39 + /// 审批类型(会签/或签)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_ApprovalType")]
  42 + public string ApprovalType { get; set; } = "会签";
  43 +
  44 + /// <summary>
  45 + /// 是否必审(1-必审,0-可选)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_IsRequired")]
  48 + public int IsRequired { get; set; } = 1;
  49 +
  50 + /// <summary>
  51 + /// 创建时间
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_CreateTime")]
  54 + public DateTime CreateTime { get; set; }
  55 + }
  56 +}
  57 +
  58 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_inventory_usage_approval_record
  6 +{
  7 + /// <summary>
  8 + /// 库存使用申请审批记录表
  9 + /// </summary>
  10 + [SugarTable("lq_inventory_usage_approval_record")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqInventoryUsageApprovalRecordEntity
  13 + {
  14 + /// <summary>
  15 + /// 记录编号
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 申请ID
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "F_ApplicationId")]
  24 + public string ApplicationId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 节点编号
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "F_NodeId")]
  30 + public string NodeId { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 节点顺序(固定为1)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_NodeOrder")]
  36 + public int NodeOrder { get; set; } = 1;
  37 +
  38 + /// <summary>
  39 + /// 审批人ID
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_ApproverId")]
  42 + public string ApproverId { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 审批人姓名
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_ApproverName")]
  48 + public string ApproverName { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 审批结果(通过/不通过/退回)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_ApprovalResult")]
  54 + public string ApprovalResult { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 审批意见
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_ApprovalOpinion")]
  60 + public string ApprovalOpinion { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 审批时间
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_ApprovalTime")]
  66 + public DateTime ApprovalTime { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 是否当前节点(1-是,0-否)
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_IsCurrentNode")]
  72 + public int IsCurrentNode { get; set; } = 0;
  73 + }
  74 +}
  75 +
  76 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqInventoryUsageApplication/ILqInventoryUsageApplicationService.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +using System.Threading.Tasks;
  3 +using NCC.Extend.Entitys.Dto.LqInventoryUsageApplication;
  4 +
  5 +namespace NCC.Extend.Interfaces.LqInventoryUsageApplication
  6 +{
  7 + /// <summary>
  8 + /// 库存使用申请服务接口
  9 + /// </summary>
  10 + public interface ILqInventoryUsageApplicationService
  11 + {
  12 + /// <summary>
  13 + /// 获取库存使用申请列表
  14 + /// </summary>
  15 + /// <param name="input">查询参数</param>
  16 + /// <returns>分页列表</returns>
  17 + Task<dynamic> GetList(LqInventoryUsageApplicationListQueryInput input);
  18 +
  19 + /// <summary>
  20 + /// 获取库存使用申请详情
  21 + /// </summary>
  22 + /// <param name="id">申请编号</param>
  23 + /// <returns>申请详情</returns>
  24 + Task<dynamic> GetInfo(string id);
  25 +
  26 + /// <summary>
  27 + /// 创建库存使用申请
  28 + /// </summary>
  29 + /// <param name="input">创建参数</param>
  30 + /// <returns>创建的申请ID</returns>
  31 + Task<dynamic> Create(LqInventoryUsageApplicationCrInput input);
  32 +
  33 + /// <summary>
  34 + /// 更新库存使用申请
  35 + /// </summary>
  36 + /// <param name="id">申请编号</param>
  37 + /// <param name="input">更新参数</param>
  38 + /// <returns></returns>
  39 + Task Update(string id, LqInventoryUsageApplicationUpInput input);
  40 +
  41 + /// <summary>
  42 + /// 提交审批
  43 + /// </summary>
  44 + /// <param name="id">申请编号</param>
  45 + /// <returns></returns>
  46 + Task SubmitApproval(string id);
  47 +
  48 + /// <summary>
  49 + /// 审批操作(通过/不通过/退回)
  50 + /// </summary>
  51 + /// <param name="id">申请编号</param>
  52 + /// <param name="input">审批参数</param>
  53 + /// <returns></returns>
  54 + Task Approve(string id, LqInventoryUsageApplicationApproveInput input);
  55 +
  56 + /// <summary>
  57 + /// 领取操作
  58 + /// </summary>
  59 + /// <param name="id">申请编号</param>
  60 + /// <param name="input">领取参数</param>
  61 + /// <returns></returns>
  62 + Task Receive(string id, LqInventoryUsageApplicationReceiveInput input);
  63 +
  64 + /// <summary>
  65 + /// 查询待审批列表
  66 + /// </summary>
  67 + /// <param name="input">查询参数</param>
  68 + /// <returns>分页列表</returns>
  69 + Task<dynamic> PendingApproval(LqInventoryUsageApplicationListQueryInput input);
  70 +
  71 + /// <summary>
  72 + /// 查询审批历史
  73 + /// </summary>
  74 + /// <param name="id">申请编号</param>
  75 + /// <returns>审批历史记录列表</returns>
  76 + Task<dynamic> ApprovalHistory(string id);
  77 + }
  78 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqMdMajorProjectTeacherAssignment/ILqMdMajorProjectTeacherAssignmentService.cs
... ... @@ -12,8 +12,9 @@ namespace NCC.Extend.Interfaces.LqMdMajorProjectTeacherAssignment
12 12 /// <summary>
13 13 /// 复制上月设置
14 14 /// </summary>
15   - /// <param name="targetMonth">目标月份(YYYYMM格式),如果为空则复制到当前月份</param>
  15 + /// <param name="targetYear">目标年份(YYYY格式),如果为空则复制到当前年份</param>
  16 + /// <param name="targetMonth">目标月份(MM格式),如果为空则复制到当前月份</param>
16 17 /// <returns></returns>
17   - Task<dynamic> CopyLastMonthData(string targetMonth = null);
  18 + Task<dynamic> CopyLastMonthData(string targetYear = null, string targetMonth = null);
18 19 }
19 20 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqContractService.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.Filter;
  9 +using NCC.Dependency;
  10 +using NCC.DynamicApiController;
  11 +using NCC.Extend.Entitys.Dto.LqContract;
  12 +using NCC.Extend.Entitys.Enum;
  13 +using NCC.Extend.Entitys.lq_contract;
  14 +using NCC.Extend.Entitys.lq_contract_rent_detail;
  15 +using NCC.Extend.Entitys.lq_mdxx;
  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 = "LqContract", Order = 250)]
  27 + [Route("api/Extend/LqContract")]
  28 + public class LqContractService : IDynamicApiController, ITransient
  29 + {
  30 + private readonly IUserManager _userManager;
  31 + private readonly ILogger<LqContractService> _logger;
  32 + private readonly ISqlSugarClient _db;
  33 +
  34 + /// <summary>
  35 + /// 构造函数
  36 + /// </summary>
  37 + /// <param name="userManager">用户管理器</param>
  38 + /// <param name="logger">日志记录器</param>
  39 + /// <param name="db">数据库客户端</param>
  40 + public LqContractService(IUserManager userManager, ILogger<LqContractService> logger, ISqlSugarClient db)
  41 + {
  42 + _userManager = userManager;
  43 + _logger = logger;
  44 + _db = db;
  45 + }
  46 +
  47 + #region 创建合同
  48 +
  49 + /// <summary>
  50 + /// 创建合同
  51 + /// </summary>
  52 + /// <remarks>
  53 + /// 创建新合同,系统会自动根据合同信息生成月租明细
  54 + ///
  55 + /// 示例请求:
  56 + /// ```json
  57 + /// {
  58 + /// "storeId": "门店ID",
  59 + /// "title": "门店租赁合同",
  60 + /// "category": "租赁合同",
  61 + /// "tenantName": "张三",
  62 + /// "contractStartDate": "2025-01-01T00:00:00",
  63 + /// "contractEndDate": "2025-12-31T23:59:59",
  64 + /// "reminderDays": 7,
  65 + /// "deposit": 5000.00,
  66 + /// "monthlyRent": 1000.00,
  67 + /// "paymentAmount": 3000.00,
  68 + /// "paymentCycle": 3,
  69 + /// "remarks": "季度交租",
  70 + /// "attachment": ""
  71 + /// }
  72 + /// ```
  73 + ///
  74 + /// 业务流程:
  75 + /// 1. 验证合同信息
  76 + /// 2. 查询门店信息(获取店名)
  77 + /// 3. 创建合同记录
  78 + /// 4. 自动生成月租明细(根据合同起始日期、结束日期、交租周期、缴租金额)
  79 + /// 5. 计算下次应交时间
  80 + /// </remarks>
  81 + /// <param name="input">创建输入</param>
  82 + /// <returns>创建结果</returns>
  83 + /// <response code="200">创建成功</response>
  84 + /// <response code="400">请求参数错误</response>
  85 + /// <response code="500">服务器错误</response>
  86 + [HttpPost("Create")]
  87 + public async Task CreateAsync([FromBody] LqContractCrInput input)
  88 + {
  89 + try
  90 + {
  91 + // 验证合同日期
  92 + if (input.ContractStartDate >= input.ContractEndDate)
  93 + {
  94 + throw NCCException.Oh("合同起始日期必须小于合同结束日期");
  95 + }
  96 +
  97 + // 验证交租周期
  98 + if (input.PaymentCycle <= 0 || input.PaymentCycle > 12)
  99 + {
  100 + throw NCCException.Oh("交租周期必须在1-12个月之间");
  101 + }
  102 +
  103 + // 查询门店信息
  104 + var store = await _db.Queryable<LqMdxxEntity>()
  105 + .Where(x => x.Id == input.StoreId)
  106 + .Select(x => new { x.Dm })
  107 + .FirstAsync();
  108 +
  109 + if (store == null)
  110 + {
  111 + throw NCCException.Oh("门店不存在");
  112 + }
  113 +
  114 + _db.Ado.BeginTran();
  115 +
  116 + try
  117 + {
  118 + // 创建合同
  119 + var contractEntity = new LqContractEntity
  120 + {
  121 + Id = YitIdHelper.NextId().ToString(),
  122 + StoreId = input.StoreId,
  123 + StoreName = store.Dm ?? "",
  124 + Title = input.Title,
  125 + Category = input.Category,
  126 + TenantName = input.TenantName,
  127 + ContractStartDate = input.ContractStartDate,
  128 + ContractEndDate = input.ContractEndDate,
  129 + ReminderDays = input.ReminderDays,
  130 + Deposit = input.Deposit,
  131 + MonthlyRent = input.MonthlyRent,
  132 + PaymentAmount = input.PaymentAmount,
  133 + PaymentCycle = input.PaymentCycle,
  134 + Remarks = input.Remarks,
  135 + Attachment = input.Attachment,
  136 + CreateUser = _userManager.UserId,
  137 + CreateTime = DateTime.Now,
  138 + IsEffective = StatusEnum.有效.GetHashCode()
  139 + };
  140 +
  141 + var insertCount = await _db.Insertable(contractEntity).ExecuteCommandAsync();
  142 + if (insertCount <= 0)
  143 + {
  144 + throw NCCException.Oh("创建合同失败");
  145 + }
  146 +
  147 + // 自动生成月租明细
  148 + await GenerateRentDetailsAsync(contractEntity.Id, contractEntity.ContractStartDate, contractEntity.ContractEndDate, contractEntity.PaymentCycle, contractEntity.PaymentAmount);
  149 +
  150 + // 计算下次应交时间
  151 + await CalculateNextPaymentDate(contractEntity.Id);
  152 +
  153 + _db.Ado.CommitTran();
  154 + }
  155 + catch
  156 + {
  157 + _db.Ado.RollbackTran();
  158 + throw;
  159 + }
  160 + }
  161 + catch (Exception ex)
  162 + {
  163 + _db.Ado.RollbackTran();
  164 + _logger.LogError(ex, "创建合同失败");
  165 + throw NCCException.Oh($"创建失败:{ex.Message}");
  166 + }
  167 + }
  168 +
  169 + #endregion
  170 +
  171 + #region 更新合同
  172 +
  173 + /// <summary>
  174 + /// 更新合同
  175 + /// </summary>
  176 + /// <remarks>
  177 + /// 更新合同信息。如果修改了合同起始日期、结束日期、交租周期或缴租金额,系统会重新生成月租明细。
  178 + ///
  179 + /// 示例请求:
  180 + /// ```json
  181 + /// {
  182 + /// "id": "合同ID",
  183 + /// "storeId": "门店ID",
  184 + /// "title": "门店租赁合同",
  185 + /// "category": "租赁合同",
  186 + /// "tenantName": "张三",
  187 + /// "contractStartDate": "2025-01-01T00:00:00",
  188 + /// "contractEndDate": "2025-12-31T23:59:59",
  189 + /// "reminderDays": 7,
  190 + /// "deposit": 5000.00,
  191 + /// "monthlyRent": 1000.00,
  192 + /// "paymentAmount": 3000.00,
  193 + /// "paymentCycle": 3,
  194 + /// "remarks": "季度交租",
  195 + /// "attachment": ""
  196 + /// }
  197 + /// ```
  198 + /// </remarks>
  199 + /// <param name="input">更新输入</param>
  200 + /// <returns>更新结果</returns>
  201 + /// <response code="200">更新成功</response>
  202 + /// <response code="400">请求参数错误或合同不存在</response>
  203 + /// <response code="500">服务器错误</response>
  204 + [HttpPut("Update")]
  205 + public async Task UpdateAsync([FromBody] LqContractUpInput input)
  206 + {
  207 + try
  208 + {
  209 + // 查询合同
  210 + var contract = await _db.Queryable<LqContractEntity>()
  211 + .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
  212 + .FirstAsync();
  213 +
  214 + if (contract == null)
  215 + {
  216 + throw NCCException.Oh("合同不存在或已失效");
  217 + }
  218 +
  219 + // 验证合同日期
  220 + if (input.ContractStartDate >= input.ContractEndDate)
  221 + {
  222 + throw NCCException.Oh("合同起始日期必须小于合同结束日期");
  223 + }
  224 +
  225 + // 验证交租周期
  226 + if (input.PaymentCycle <= 0 || input.PaymentCycle > 12)
  227 + {
  228 + throw NCCException.Oh("交租周期必须在1-12个月之间");
  229 + }
  230 +
  231 + // 查询门店信息
  232 + var store = await _db.Queryable<LqMdxxEntity>()
  233 + .Where(x => x.Id == input.StoreId)
  234 + .Select(x => new { x.Dm })
  235 + .FirstAsync();
  236 +
  237 + if (store == null)
  238 + {
  239 + throw NCCException.Oh("门店不存在");
  240 + }
  241 +
  242 + _db.Ado.BeginTran();
  243 +
  244 + try
  245 + {
  246 + // 判断是否需要重新生成明细
  247 + bool needRegenerateDetails = contract.ContractStartDate != input.ContractStartDate ||
  248 + contract.ContractEndDate != input.ContractEndDate ||
  249 + contract.PaymentCycle != input.PaymentCycle ||
  250 + contract.PaymentAmount != input.PaymentAmount;
  251 +
  252 + // 更新合同信息
  253 + contract.StoreId = input.StoreId;
  254 + contract.StoreName = store.Dm ?? "";
  255 + contract.Title = input.Title;
  256 + contract.Category = input.Category;
  257 + contract.TenantName = input.TenantName;
  258 + contract.ContractStartDate = input.ContractStartDate;
  259 + contract.ContractEndDate = input.ContractEndDate;
  260 + contract.ReminderDays = input.ReminderDays;
  261 + contract.Deposit = input.Deposit;
  262 + contract.MonthlyRent = input.MonthlyRent;
  263 + contract.PaymentAmount = input.PaymentAmount;
  264 + contract.PaymentCycle = input.PaymentCycle;
  265 + contract.Remarks = input.Remarks;
  266 + contract.Attachment = input.Attachment;
  267 + contract.UpdateUser = _userManager.UserId;
  268 + contract.UpdateTime = DateTime.Now;
  269 +
  270 + var updateCount = await _db.Updateable(contract).ExecuteCommandAsync();
  271 + if (updateCount <= 0)
  272 + {
  273 + throw NCCException.Oh("更新合同失败");
  274 + }
  275 +
  276 + // 如果需要重新生成明细,先删除旧明细,再生成新明细
  277 + if (needRegenerateDetails)
  278 + {
  279 + // 删除旧的明细(逻辑删除)
  280 + await _db.Updateable<LqContractRentDetailEntity>()
  281 + .SetColumns(x => new LqContractRentDetailEntity
  282 + {
  283 + IsEffective = StatusEnum.无效.GetHashCode(),
  284 + UpdateUser = _userManager.UserId,
  285 + UpdateTime = DateTime.Now
  286 + })
  287 + .Where(x => x.ContractId == contract.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
  288 + .ExecuteCommandAsync();
  289 +
  290 + // 生成新的明细
  291 + await GenerateRentDetailsAsync(contract.Id, contract.ContractStartDate, contract.ContractEndDate, contract.PaymentCycle, contract.PaymentAmount);
  292 + }
  293 +
  294 + // 重新计算下次应交时间
  295 + await CalculateNextPaymentDate(contract.Id);
  296 +
  297 + _db.Ado.CommitTran();
  298 + }
  299 + catch
  300 + {
  301 + _db.Ado.RollbackTran();
  302 + throw;
  303 + }
  304 + }
  305 + catch (Exception ex)
  306 + {
  307 + _db.Ado.RollbackTran();
  308 + _logger.LogError(ex, "更新合同失败");
  309 + throw NCCException.Oh($"更新失败:{ex.Message}");
  310 + }
  311 + }
  312 +
  313 + #endregion
  314 +
  315 + #region 删除合同
  316 +
  317 + /// <summary>
  318 + /// 删除合同
  319 + /// </summary>
  320 + /// <remarks>
  321 + /// 删除合同,同时会级联删除该合同的所有月租明细(逻辑删除)
  322 + ///
  323 + /// 示例请求:
  324 + /// ```
  325 + /// DELETE /api/Extend/LqContract/{id}
  326 + /// ```
  327 + /// </remarks>
  328 + /// <param name="id">合同ID</param>
  329 + /// <returns>删除结果</returns>
  330 + /// <response code="200">删除成功</response>
  331 + /// <response code="400">合同不存在</response>
  332 + /// <response code="500">服务器错误</response>
  333 + [HttpDelete("{id}")]
  334 + public async Task DeleteAsync([FromRoute] string id)
  335 + {
  336 + try
  337 + {
  338 + if (string.IsNullOrWhiteSpace(id))
  339 + {
  340 + throw NCCException.Oh("合同ID不能为空");
  341 + }
  342 +
  343 + // 查询合同
  344 + var contract = await _db.Queryable<LqContractEntity>()
  345 + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  346 + .FirstAsync();
  347 +
  348 + if (contract == null)
  349 + {
  350 + throw NCCException.Oh("合同不存在或已失效");
  351 + }
  352 +
  353 + _db.Ado.BeginTran();
  354 +
  355 + try
  356 + {
  357 + // 删除该合同的所有月租明细(逻辑删除)
  358 + await _db.Updateable<LqContractRentDetailEntity>()
  359 + .SetColumns(x => new LqContractRentDetailEntity
  360 + {
  361 + IsEffective = StatusEnum.无效.GetHashCode(),
  362 + UpdateUser = _userManager.UserId,
  363 + UpdateTime = DateTime.Now
  364 + })
  365 + .Where(x => x.ContractId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  366 + .ExecuteCommandAsync();
  367 +
  368 + // 删除合同(逻辑删除)
  369 + contract.IsEffective = StatusEnum.无效.GetHashCode();
  370 + contract.UpdateUser = _userManager.UserId;
  371 + contract.UpdateTime = DateTime.Now;
  372 +
  373 + var updateCount = await _db.Updateable(contract).ExecuteCommandAsync();
  374 + if (updateCount <= 0)
  375 + {
  376 + throw NCCException.Oh("删除合同失败");
  377 + }
  378 +
  379 + _db.Ado.CommitTran();
  380 + }
  381 + catch
  382 + {
  383 + _db.Ado.RollbackTran();
  384 + throw;
  385 + }
  386 + }
  387 + catch (Exception ex)
  388 + {
  389 + _db.Ado.RollbackTran();
  390 + _logger.LogError(ex, "删除合同失败");
  391 + throw NCCException.Oh($"删除失败:{ex.Message}");
  392 + }
  393 + }
  394 +
  395 + #endregion
  396 +
  397 + #region 获取合同列表
  398 +
  399 + /// <summary>
  400 + /// 获取合同列表
  401 + /// </summary>
  402 + /// <remarks>
  403 + /// 分页查询合同列表,支持多条件筛选
  404 + ///
  405 + /// 示例请求:
  406 + /// ```
  407 + /// GET /api/Extend/LqContract/GetList?currentPage=1&pageSize=10&storeId=门店ID&title=合同标题
  408 + /// ```
  409 + /// </remarks>
  410 + /// <param name="input">查询输入</param>
  411 + /// <returns>合同列表</returns>
  412 + /// <response code="200">查询成功</response>
  413 + /// <response code="500">服务器错误</response>
  414 + [HttpGet("GetList")]
  415 + public async Task<dynamic> GetListAsync([FromQuery] LqContractListQueryInput input)
  416 + {
  417 + try
  418 + {
  419 + var sidx = string.IsNullOrEmpty(input.sidx) ? "createTime" : input.sidx;
  420 + var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
  421 +
  422 + var data = await _db.Queryable<LqContractEntity>()
  423 + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreId), x => x.StoreId == input.StoreId)
  424 + .WhereIF(!string.IsNullOrWhiteSpace(input.StoreName), x => x.StoreName != null && x.StoreName.Contains(input.StoreName))
  425 + .WhereIF(!string.IsNullOrWhiteSpace(input.Category), x => x.Category == input.Category)
  426 + .WhereIF(!string.IsNullOrWhiteSpace(input.Title), x => x.Title != null && x.Title.Contains(input.Title))
  427 + .WhereIF(input.ContractStartDateBegin.HasValue, x => x.ContractStartDate >= input.ContractStartDateBegin.Value)
  428 + .WhereIF(input.ContractStartDateEnd.HasValue, x => x.ContractStartDate <= input.ContractStartDateEnd.Value)
  429 + .WhereIF(input.ContractEndDateBegin.HasValue, x => x.ContractEndDate >= input.ContractEndDateBegin.Value)
  430 + .WhereIF(input.ContractEndDateEnd.HasValue, x => x.ContractEndDate <= input.ContractEndDateEnd.Value)
  431 + .WhereIF(input.IsEffective.HasValue, x => x.IsEffective == input.IsEffective.Value)
  432 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  433 + .Select(x => new LqContractListOutput
  434 + {
  435 + id = x.Id,
  436 + storeId = x.StoreId,
  437 + storeName = x.StoreName,
  438 + title = x.Title,
  439 + category = x.Category,
  440 + tenantName = x.TenantName,
  441 + contractStartDate = x.ContractStartDate,
  442 + contractEndDate = x.ContractEndDate,
  443 + reminderDays = x.ReminderDays,
  444 + deposit = x.Deposit,
  445 + nextPaymentDate = x.NextPaymentDate,
  446 + monthlyRent = x.MonthlyRent,
  447 + paymentAmount = x.PaymentAmount,
  448 + paymentCycle = x.PaymentCycle,
  449 + remarks = x.Remarks,
  450 + attachment = x.Attachment,
  451 + createUser = x.CreateUser,
  452 + createUserName = "",
  453 + createTime = x.CreateTime,
  454 + updateUser = x.UpdateUser,
  455 + updateUserName = "",
  456 + updateTime = x.UpdateTime,
  457 + isEffective = x.IsEffective
  458 + })
  459 + .MergeTable()
  460 + .OrderBy(sidx + " " + sort)
  461 + .ToPagedListAsync(input.currentPage, input.pageSize);
  462 +
  463 + // 补充用户信息
  464 + var userIds = data.list.SelectMany(x => new[] { x.createUser, x.updateUser })
  465 + .Where(x => !string.IsNullOrEmpty(x))
  466 + .Distinct()
  467 + .ToList();
  468 +
  469 + if (userIds.Any())
  470 + {
  471 + var userList = await _db.Queryable<UserEntity>()
  472 + .Where(x => userIds.Contains(x.Id))
  473 + .Select(x => new { x.Id, x.RealName })
  474 + .ToListAsync();
  475 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  476 +
  477 + foreach (var item in data.list)
  478 + {
  479 + if (!string.IsNullOrEmpty(item.createUser) && userDict.ContainsKey(item.createUser))
  480 + item.createUserName = userDict[item.createUser];
  481 + if (!string.IsNullOrEmpty(item.updateUser) && userDict.ContainsKey(item.updateUser))
  482 + item.updateUserName = userDict[item.updateUser];
  483 + }
  484 + }
  485 +
  486 + return PageResult<LqContractListOutput>.SqlSugarPageResult(data);
  487 + }
  488 + catch (Exception ex)
  489 + {
  490 + _logger.LogError(ex, "获取合同列表失败");
  491 + throw NCCException.Oh($"获取合同列表失败:{ex.Message}");
  492 + }
  493 + }
  494 +
  495 + #endregion
  496 +
  497 + #region 获取合同详情
  498 +
  499 + /// <summary>
  500 + /// 获取合同详情
  501 + /// </summary>
  502 + /// <remarks>
  503 + /// 根据合同ID获取合同详细信息,包括月租明细列表
  504 + ///
  505 + /// 示例请求:
  506 + /// ```
  507 + /// GET /api/Extend/LqContract/GetInfo?id=合同ID
  508 + /// ```
  509 + /// </remarks>
  510 + /// <param name="id">合同ID</param>
  511 + /// <returns>合同详情</returns>
  512 + /// <response code="200">查询成功</response>
  513 + /// <response code="400">合同ID不能为空</response>
  514 + /// <response code="404">合同不存在</response>
  515 + /// <response code="500">服务器错误</response>
  516 + [HttpGet("GetInfo")]
  517 + public async Task<LqContractInfoOutput> GetInfoAsync([FromQuery] string id)
  518 + {
  519 + try
  520 + {
  521 + if (string.IsNullOrWhiteSpace(id))
  522 + {
  523 + throw NCCException.Oh("合同ID不能为空");
  524 + }
  525 +
  526 + // 查询合同
  527 + var contract = await _db.Queryable<LqContractEntity>()
  528 + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  529 + .FirstAsync();
  530 +
  531 + if (contract == null)
  532 + {
  533 + throw NCCException.Oh("合同不存在或已失效");
  534 + }
  535 +
  536 + // 查询月租明细
  537 + var rentDetails = await _db.Queryable<LqContractRentDetailEntity>()
  538 + .Where(x => x.ContractId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  539 + .OrderBy(x => x.PaymentMonth)
  540 + .Select(x => new LqContractRentDetailListOutput
  541 + {
  542 + id = x.Id,
  543 + contractId = x.ContractId,
  544 + paymentMonth = x.PaymentMonth,
  545 + dueDate = x.DueDate,
  546 + dueAmount = x.DueAmount,
  547 + isPaid = x.IsPaid,
  548 + actualPaymentDate = x.ActualPaymentDate,
  549 + actualPaymentAmount = x.ActualPaymentAmount,
  550 + remarks = x.Remarks,
  551 + createUser = x.CreateUser,
  552 + createUserName = "",
  553 + createTime = x.CreateTime,
  554 + updateUser = x.UpdateUser,
  555 + updateUserName = "",
  556 + updateTime = x.UpdateTime,
  557 + isEffective = x.IsEffective
  558 + })
  559 + .ToListAsync();
  560 +
  561 + // 补充用户信息
  562 + var userIds = new List<string> { contract.CreateUser, contract.UpdateUser };
  563 + userIds.AddRange(rentDetails.SelectMany(x => new[] { x.createUser, x.updateUser }).Where(x => !string.IsNullOrEmpty(x)));
  564 +
  565 + string createUserName = "";
  566 + string updateUserName = "";
  567 +
  568 + if (userIds.Any())
  569 + {
  570 + var userList = await _db.Queryable<UserEntity>()
  571 + .Where(x => userIds.Distinct().Contains(x.Id))
  572 + .Select(x => new { x.Id, x.RealName })
  573 + .ToListAsync();
  574 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  575 +
  576 + if (!string.IsNullOrEmpty(contract.CreateUser) && userDict.ContainsKey(contract.CreateUser))
  577 + createUserName = userDict[contract.CreateUser];
  578 + if (!string.IsNullOrEmpty(contract.UpdateUser) && userDict.ContainsKey(contract.UpdateUser))
  579 + updateUserName = userDict[contract.UpdateUser];
  580 +
  581 + foreach (var detail in rentDetails)
  582 + {
  583 + if (!string.IsNullOrEmpty(detail.createUser) && userDict.ContainsKey(detail.createUser))
  584 + detail.createUserName = userDict[detail.createUser];
  585 + if (!string.IsNullOrEmpty(detail.updateUser) && userDict.ContainsKey(detail.updateUser))
  586 + detail.updateUserName = userDict[detail.updateUser];
  587 + }
  588 + }
  589 +
  590 + return new LqContractInfoOutput
  591 + {
  592 + id = contract.Id,
  593 + storeId = contract.StoreId,
  594 + storeName = contract.StoreName,
  595 + title = contract.Title,
  596 + category = contract.Category,
  597 + tenantName = contract.TenantName,
  598 + contractStartDate = contract.ContractStartDate,
  599 + contractEndDate = contract.ContractEndDate,
  600 + reminderDays = contract.ReminderDays,
  601 + deposit = contract.Deposit,
  602 + nextPaymentDate = contract.NextPaymentDate,
  603 + monthlyRent = contract.MonthlyRent,
  604 + paymentAmount = contract.PaymentAmount,
  605 + paymentCycle = contract.PaymentCycle,
  606 + remarks = contract.Remarks,
  607 + attachment = contract.Attachment,
  608 + createUser = contract.CreateUser,
  609 + createUserName = createUserName,
  610 + createTime = contract.CreateTime,
  611 + updateUser = contract.UpdateUser,
  612 + updateUserName = updateUserName,
  613 + updateTime = contract.UpdateTime,
  614 + isEffective = contract.IsEffective,
  615 + rentDetails = rentDetails
  616 + };
  617 + }
  618 + catch (Exception ex)
  619 + {
  620 + _logger.LogError(ex, "获取合同详情失败");
  621 + throw NCCException.Oh($"获取合同详情失败:{ex.Message}");
  622 + }
  623 + }
  624 +
  625 + #endregion
  626 +
  627 + #region 获取月租明细列表
  628 +
  629 + /// <summary>
  630 + /// 获取月租明细列表
  631 + /// </summary>
  632 + /// <remarks>
  633 + /// 根据合同ID获取该合同的所有月租明细
  634 + ///
  635 + /// 示例请求:
  636 + /// ```
  637 + /// GET /api/Extend/LqContract/GetRentDetails?contractId=合同ID
  638 + /// ```
  639 + /// </remarks>
  640 + /// <param name="contractId">合同ID</param>
  641 + /// <returns>月租明细列表</returns>
  642 + /// <response code="200">查询成功</response>
  643 + /// <response code="400">合同ID不能为空</response>
  644 + /// <response code="500">服务器错误</response>
  645 + [HttpGet("GetRentDetails")]
  646 + public async Task<dynamic> GetRentDetailsAsync([FromQuery] string contractId)
  647 + {
  648 + try
  649 + {
  650 + if (string.IsNullOrWhiteSpace(contractId))
  651 + {
  652 + throw NCCException.Oh("合同ID不能为空");
  653 + }
  654 +
  655 + var rentDetails = await _db.Queryable<LqContractRentDetailEntity>()
  656 + .Where(x => x.ContractId == contractId && x.IsEffective == StatusEnum.有效.GetHashCode())
  657 + .OrderBy(x => x.PaymentMonth)
  658 + .Select(x => new LqContractRentDetailListOutput
  659 + {
  660 + id = x.Id,
  661 + contractId = x.ContractId,
  662 + paymentMonth = x.PaymentMonth,
  663 + dueDate = x.DueDate,
  664 + dueAmount = x.DueAmount,
  665 + isPaid = x.IsPaid,
  666 + actualPaymentDate = x.ActualPaymentDate,
  667 + actualPaymentAmount = x.ActualPaymentAmount,
  668 + remarks = x.Remarks,
  669 + createUser = x.CreateUser,
  670 + createUserName = "",
  671 + createTime = x.CreateTime,
  672 + updateUser = x.UpdateUser,
  673 + updateUserName = "",
  674 + updateTime = x.UpdateTime,
  675 + isEffective = x.IsEffective
  676 + })
  677 + .ToListAsync();
  678 +
  679 + // 补充用户信息
  680 + var userIds = rentDetails.SelectMany(x => new[] { x.createUser, x.updateUser })
  681 + .Where(x => !string.IsNullOrEmpty(x))
  682 + .Distinct()
  683 + .ToList();
  684 +
  685 + if (userIds.Any())
  686 + {
  687 + var userList = await _db.Queryable<UserEntity>()
  688 + .Where(x => userIds.Contains(x.Id))
  689 + .Select(x => new { x.Id, x.RealName })
  690 + .ToListAsync();
  691 + var userDict = userList.ToDictionary(k => k.Id, v => v.RealName);
  692 +
  693 + foreach (var detail in rentDetails)
  694 + {
  695 + if (!string.IsNullOrEmpty(detail.createUser) && userDict.ContainsKey(detail.createUser))
  696 + detail.createUserName = userDict[detail.createUser];
  697 + if (!string.IsNullOrEmpty(detail.updateUser) && userDict.ContainsKey(detail.updateUser))
  698 + detail.updateUserName = userDict[detail.updateUser];
  699 + }
  700 + }
  701 +
  702 + return rentDetails;
  703 + }
  704 + catch (Exception ex)
  705 + {
  706 + _logger.LogError(ex, "获取月租明细列表失败");
  707 + throw NCCException.Oh($"获取月租明细列表失败:{ex.Message}");
  708 + }
  709 + }
  710 +
  711 + #endregion
  712 +
  713 + #region 标记明细已缴费
  714 +
  715 + /// <summary>
  716 + /// 标记月租明细已缴费
  717 + /// </summary>
  718 + /// <remarks>
  719 + /// 标记某条月租明细已缴费,记录实际缴费时间和金额
  720 + ///
  721 + /// 示例请求:
  722 + /// ```json
  723 + /// {
  724 + /// "id": "明细ID",
  725 + /// "actualPaymentDate": "2025-01-15T00:00:00",
  726 + /// "actualPaymentAmount": 3000.00,
  727 + /// "remarks": "已缴费"
  728 + /// }
  729 + /// ```
  730 + /// </remarks>
  731 + /// <param name="input">标记已缴费输入</param>
  732 + /// <returns>标记结果</returns>
  733 + /// <response code="200">标记成功</response>
  734 + /// <response code="400">明细不存在或已缴费</response>
  735 + /// <response code="500">服务器错误</response>
  736 + [HttpPut("MarkRentDetailPaid")]
  737 + public async Task MarkRentDetailPaidAsync([FromBody] LqContractRentDetailMarkPaidInput input)
  738 + {
  739 + try
  740 + {
  741 + // 查询明细
  742 + var detail = await _db.Queryable<LqContractRentDetailEntity>()
  743 + .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
  744 + .FirstAsync();
  745 +
  746 + if (detail == null)
  747 + {
  748 + throw NCCException.Oh("月租明细不存在或已失效");
  749 + }
  750 +
  751 + if (detail.IsPaid == 1)
  752 + {
  753 + throw NCCException.Oh("该明细已标记为已缴费");
  754 + }
  755 +
  756 + _db.Ado.BeginTran();
  757 +
  758 + try
  759 + {
  760 + // 更新明细
  761 + detail.IsPaid = 1;
  762 + detail.ActualPaymentDate = input.ActualPaymentDate;
  763 + detail.ActualPaymentAmount = input.ActualPaymentAmount;
  764 + detail.Remarks = input.Remarks;
  765 + detail.UpdateUser = _userManager.UserId;
  766 + detail.UpdateTime = DateTime.Now;
  767 +
  768 + var updateCount = await _db.Updateable(detail).ExecuteCommandAsync();
  769 + if (updateCount <= 0)
  770 + {
  771 + throw NCCException.Oh("标记已缴费失败");
  772 + }
  773 +
  774 + // 重新计算下次应交时间
  775 + await CalculateNextPaymentDate(detail.ContractId);
  776 +
  777 + _db.Ado.CommitTran();
  778 + }
  779 + catch
  780 + {
  781 + _db.Ado.RollbackTran();
  782 + throw;
  783 + }
  784 + }
  785 + catch (Exception ex)
  786 + {
  787 + _db.Ado.RollbackTran();
  788 + _logger.LogError(ex, "标记明细已缴费失败");
  789 + throw NCCException.Oh($"标记失败:{ex.Message}");
  790 + }
  791 + }
  792 +
  793 + #endregion
  794 +
  795 + #region 私有方法
  796 +
  797 + /// <summary>
  798 + /// 自动生成月租明细
  799 + /// </summary>
  800 + /// <param name="contractId">合同ID</param>
  801 + /// <param name="contractStartDate">合同起始日期</param>
  802 + /// <param name="contractEndDate">合同结束日期</param>
  803 + /// <param name="paymentCycle">交租周期(月)</param>
  804 + /// <param name="paymentAmount">缴租金额</param>
  805 + private async Task GenerateRentDetailsAsync(string contractId, DateTime contractStartDate, DateTime contractEndDate, int paymentCycle, decimal paymentAmount)
  806 + {
  807 + var details = new List<LqContractRentDetailEntity>();
  808 + var currentDate = contractStartDate.Date;
  809 +
  810 + // 从合同起始日期开始,每隔交租周期生成一条明细
  811 + while (currentDate <= contractEndDate)
  812 + {
  813 + // 应缴月份:当前日期的月份第一天(格式:YYYY-MM-01)
  814 + var paymentMonth = new DateTime(currentDate.Year, currentDate.Month, 1);
  815 + // 应缴日期:应缴月份的第一天(根据业务规则,应缴日期为应缴月份的第一天)
  816 + var dueDate = paymentMonth;
  817 +
  818 + var detail = new LqContractRentDetailEntity
  819 + {
  820 + Id = YitIdHelper.NextId().ToString(),
  821 + ContractId = contractId,
  822 + PaymentMonth = paymentMonth,
  823 + DueDate = dueDate,
  824 + DueAmount = paymentAmount,
  825 + IsPaid = 0,
  826 + CreateUser = _userManager.UserId,
  827 + CreateTime = DateTime.Now,
  828 + IsEffective = StatusEnum.有效.GetHashCode()
  829 + };
  830 +
  831 + details.Add(detail);
  832 +
  833 + // 计算下一个交租日期(加上交租周期)
  834 + currentDate = currentDate.AddMonths(paymentCycle);
  835 + }
  836 +
  837 + // 批量插入
  838 + if (details.Any())
  839 + {
  840 + await _db.Insertable(details).ExecuteCommandAsync();
  841 + }
  842 + }
  843 +
  844 + /// <summary>
  845 + /// 计算下次应交时间(匿名方法)
  846 + /// </summary>
  847 + /// <param name="contractId">合同ID</param>
  848 + private async Task CalculateNextPaymentDate(string contractId)
  849 + {
  850 + // 查询合同信息
  851 + var contract = await _db.Queryable<LqContractEntity>()
  852 + .Where(x => x.Id == contractId && x.IsEffective == StatusEnum.有效.GetHashCode())
  853 + .FirstAsync();
  854 +
  855 + if (contract == null)
  856 + {
  857 + return;
  858 + }
  859 +
  860 + // 查询该合同未缴费的明细,按应缴日期升序排列
  861 + var unpaidDetail = await _db.Queryable<LqContractRentDetailEntity>()
  862 + .Where(x => x.ContractId == contractId && x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsPaid == 0)
  863 + .OrderBy(x => x.DueDate)
  864 + .FirstAsync();
  865 +
  866 + if (unpaidDetail != null)
  867 + {
  868 + // 计算:下次应交时间 = 最早应缴日期 - 提前提醒天数
  869 + var nextPaymentDate = unpaidDetail.DueDate.AddDays(-contract.ReminderDays);
  870 +
  871 + // 更新合同的下次应交时间
  872 + contract.NextPaymentDate = nextPaymentDate;
  873 + contract.UpdateUser = _userManager.UserId;
  874 + contract.UpdateTime = DateTime.Now;
  875 +
  876 + await _db.Updateable(contract).ExecuteCommandAsync();
  877 + }
  878 + else
  879 + {
  880 + // 如果没有未缴费的明细,清空下次应交时间
  881 + contract.NextPaymentDate = null;
  882 + contract.UpdateUser = _userManager.UserId;
  883 + contract.UpdateTime = DateTime.Now;
  884 +
  885 + await _db.Updateable(contract).ExecuteCommandAsync();
  886 + }
  887 + }
  888 +
  889 + #endregion
  890 +
  891 + #region 统计门店合同费用
  892 +
  893 + /// <summary>
  894 + /// 统计门店合同费用
  895 + /// </summary>
  896 + /// <remarks>
  897 + /// 根据门店ID和月份统计该门店的合同费用,支持按分类统计(租门店、员工宿舍、车辆、场所等)
  898 + ///
  899 + /// 示例请求:
  900 + /// ```json
  901 + /// {
  902 + /// "storeId": "1649328471923847168",
  903 + /// "year": 2025,
  904 + /// "month": 1,
  905 + /// "categories": ["租门店", "员工宿舍", "车辆", "场所"]
  906 + /// }
  907 + /// ```
  908 + ///
  909 + /// 参数说明:
  910 + /// - storeId: 门店ID(必填)
  911 + /// - year: 统计年份(必填)
  912 + /// - month: 统计月份(必填,1-12)
  913 + /// - categories: 分类列表(可选,不传则统计所有分类)
  914 + ///
  915 + /// 返回数据说明:
  916 + /// - storeId: 门店ID
  917 + /// - storeName: 门店名称
  918 + /// - year: 统计年份
  919 + /// - month: 统计月份
  920 + /// - totalAmount: 总费用(所有分类的费用总和)
  921 + /// - categoryDetails: 按分类统计的费用明细
  922 + /// - category: 分类名称
  923 + /// - amount: 该分类的费用总额
  924 + /// - detailCount: 该分类的明细数量
  925 + /// - details: 明细列表
  926 + /// </remarks>
  927 + /// <param name="input">统计输入</param>
  928 + /// <returns>费用统计结果</returns>
  929 + /// <response code="200">统计成功</response>
  930 + /// <response code="400">请求参数错误</response>
  931 + /// <response code="500">服务器错误</response>
  932 + [HttpPost("GetExpenseStatistics")]
  933 + public async Task<ContractExpenseStatisticsOutput> GetExpenseStatisticsAsync([FromBody] ContractExpenseStatisticsInput input)
  934 + {
  935 + try
  936 + {
  937 + // 验证月份
  938 + if (input.Month < 1 || input.Month > 12)
  939 + {
  940 + throw NCCException.Oh("月份必须在1-12之间");
  941 + }
  942 +
  943 + // 构建统计月份的开始和结束时间
  944 + var statisticsMonth = new DateTime(input.Year, input.Month, 1);
  945 + var monthStart = statisticsMonth;
  946 + var monthEnd = statisticsMonth.AddMonths(1).AddDays(-1);
  947 +
  948 + // 查询门店信息
  949 + var store = await _db.Queryable<LqMdxxEntity>()
  950 + .Where(x => x.Id == input.StoreId)
  951 + .Select(x => new { x.Dm })
  952 + .FirstAsync();
  953 +
  954 + if (store == null)
  955 + {
  956 + throw NCCException.Oh("门店不存在");
  957 + }
  958 +
  959 + // 查询该门店在指定月份的月租明细
  960 + var details = await _db.Queryable<LqContractRentDetailEntity, LqContractEntity>(
  961 + (detail, contract) => new JoinQueryInfos(
  962 + JoinType.Inner, detail.ContractId == contract.Id))
  963 + .Where((detail, contract) =>
  964 + contract.StoreId == input.StoreId &&
  965 + contract.IsEffective == StatusEnum.有效.GetHashCode() &&
  966 + detail.IsEffective == StatusEnum.有效.GetHashCode() &&
  967 + detail.PaymentMonth >= monthStart &&
  968 + detail.PaymentMonth <= monthEnd)
  969 + .WhereIF(input.Categories != null && input.Categories.Length > 0,
  970 + (detail, contract) => contract.Category != null && input.Categories.Contains(contract.Category))
  971 + .Select((detail, contract) => new
  972 + {
  973 + DetailId = detail.Id,
  974 + ContractId = contract.Id,
  975 + ContractTitle = contract.Title,
  976 + Category = contract.Category ?? "未分类",
  977 + DueAmount = detail.DueAmount,
  978 + IsPaid = detail.IsPaid,
  979 + ActualPaymentAmount = detail.ActualPaymentAmount
  980 + })
  981 + .ToListAsync();
  982 +
  983 + // 按分类分组统计
  984 + var categoryGroups = details.GroupBy(x => x.Category).ToList();
  985 +
  986 + var categoryDetails = new List<CategoryExpenseDetail>();
  987 + decimal totalAmount = 0;
  988 +
  989 + foreach (var group in categoryGroups)
  990 + {
  991 + var categoryName = group.Key;
  992 + var categoryItems = group.ToList();
  993 + var categoryAmount = categoryItems.Sum(x => x.DueAmount);
  994 + totalAmount += categoryAmount;
  995 +
  996 + var expenseDetails = categoryItems.Select(x => new ExpenseDetailItem
  997 + {
  998 + detailId = x.DetailId,
  999 + contractId = x.ContractId,
  1000 + contractTitle = x.ContractTitle,
  1001 + category = x.Category,
  1002 + dueAmount = x.DueAmount,
  1003 + isPaid = x.IsPaid,
  1004 + actualPaymentAmount = x.ActualPaymentAmount
  1005 + }).ToList();
  1006 +
  1007 + categoryDetails.Add(new CategoryExpenseDetail
  1008 + {
  1009 + category = categoryName,
  1010 + amount = categoryAmount,
  1011 + detailCount = categoryItems.Count,
  1012 + details = expenseDetails
  1013 + });
  1014 + }
  1015 +
  1016 + // 按分类名称排序
  1017 + categoryDetails = categoryDetails.OrderBy(x => x.category).ToList();
  1018 +
  1019 + return new ContractExpenseStatisticsOutput
  1020 + {
  1021 + storeId = input.StoreId,
  1022 + storeName = store.Dm ?? "",
  1023 + year = input.Year,
  1024 + month = input.Month,
  1025 + totalAmount = totalAmount,
  1026 + categoryDetails = categoryDetails
  1027 + };
  1028 + }
  1029 + catch (Exception ex)
  1030 + {
  1031 + _logger.LogError(ex, "统计门店合同费用失败");
  1032 + throw NCCException.Oh($"统计失败:{ex.Message}");
  1033 + }
  1034 + }
  1035 +
  1036 + #endregion
  1037 + }
  1038 +}
  1039 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
... ... @@ -14,6 +14,8 @@ using NCC.Extend.Entitys.Enum;
14 14 using NCC.Extend.Entitys.lq_inventory;
15 15 using NCC.Extend.Entitys.lq_product;
16 16 using NCC.Extend.Entitys.lq_inventory_usage;
  17 +using NCC.Extend.Entitys.lq_inventory_usage_application;
  18 +using NCC.Extend.Entitys.lq_inventory_usage_approval_record;
17 19 using NCC.Extend.Entitys.lq_mdxx;
18 20 using NCC.Extend.Interfaces.LqInventoryUsage;
19 21 using NCC.FriendlyException;
... ... @@ -89,6 +91,10 @@ namespace NCC.Extend
89 91  
90 92 _db.Ado.BeginTran();
91 93  
  94 + // 自动计算单价和合计金额
  95 + var unitPrice = product.Price;
  96 + var totalAmount = unitPrice * input.UsageQuantity;
  97 +
92 98 // 创建使用记录
93 99 var usageEntity = new LqInventoryUsageEntity
94 100 {
... ... @@ -97,6 +103,8 @@ namespace NCC.Extend
97 103 StoreId = input.StoreId,
98 104 UsageTime = input.UsageTime,
99 105 UsageQuantity = input.UsageQuantity,
  106 + UnitPrice = unitPrice,
  107 + TotalAmount = totalAmount,
100 108 RelatedConsumeId = input.RelatedConsumeId,
101 109 CreateUser = _userManager.UserId,
102 110 CreateTime = DateTime.Now,
... ... @@ -120,15 +128,19 @@ namespace NCC.Extend
120 128  
121 129 #region 批量添加库存使用记录
122 130 /// <summary>
123   - /// 批量添加库存使用记录
  131 + /// 批量添加库存使用记录(带审批流程)
124 132 /// </summary>
125 133 /// <remarks>
126   - /// 一次性添加多条库存使用记录,同一批次的所有记录使用相同的批次ID
  134 + /// 一次性添加多条库存使用记录,同一批次的所有记录使用相同的批次ID。创建使用记录后会自动创建申请记录并提交审批。
  135 + /// 系统会自动计算单价(从产品表获取)和合计金额(单价×数量)。
127 136 ///
128 137 /// 示例请求:
129 138 /// ```json
130 139 /// {
131 140 /// "batchId": "可选,不传则自动生成",
  141 + /// "approverId": "审批人ID(财务老师或库管,必填)",
  142 + /// "applicationStoreId": "申请门店ID(必填)",
  143 + /// "remarks": "备注(可选)",
132 144 /// "usageItems": [
133 145 /// {
134 146 /// "productId": "产品ID",
... ... @@ -143,12 +155,21 @@ namespace NCC.Extend
143 155 ///
144 156 /// 参数说明:
145 157 /// - batchId: 批次ID,可选。如果不传,系统会自动生成一个唯一的批次ID
  158 + /// - approverId: 审批人ID(财务老师或库管),必填。创建申请记录后会自动提交审批
  159 + /// - applicationStoreId: 申请门店ID,必填
  160 + /// - remarks: 备注,可选
146 161 /// - usageItems: 使用记录列表,至少需要一条记录
147 162 /// - productId: 产品ID(必填)
148 163 /// - storeId: 门店ID(必填)
149 164 /// - usageTime: 使用时间(必填)
150 165 /// - usageQuantity: 使用数量(必填,必须大于0)
151 166 /// - relatedConsumeId: 关联消耗ID(可选)
  167 + ///
  168 + /// 业务流程:
  169 + /// 1. 验证库存是否充足
  170 + /// 2. 创建使用记录(自动计算单价和合计金额)
  171 + /// 3. 创建申请记录(状态:审批中)
  172 + /// 4. 返回批次ID和创建结果
152 173 /// </remarks>
153 174 /// <param name="input">批量创建输入</param>
154 175 /// <returns>批量创建结果,包含批次ID和成功/失败信息</returns>
... ... @@ -183,12 +204,13 @@ namespace NCC.Extend
183 204 var usageList = await _db.Queryable<LqInventoryUsageEntity>().Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()).GroupBy(x => x.ProductId).Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum(x.UsageQuantity) }).ToListAsync();
184 205 var usageMap = usageList.ToDictionary(x => x.ProductId, x => Convert.ToInt32(x.TotalUsage));
185 206  
186   - // 批量查询所有产品的名称
  207 + // 批量查询所有产品的名称和价格
187 208 var productDict = await _db.Queryable<LqProductEntity>()
188 209 .Where(x => productIds.Contains(x.Id))
189   - .Select(x => new { x.Id, x.ProductName })
  210 + .Select(x => new { x.Id, x.ProductName, x.Price })
190 211 .ToListAsync();
191 212 var productNameMap = productDict.ToDictionary(x => x.Id, x => x.ProductName ?? "未知产品");
  213 + var productPriceMap = productDict.ToDictionary(x => x.Id, x => x.Price);
192 214  
193 215 // 检查每个产品的库存
194 216 foreach (var productGroup in productGroups)
... ... @@ -213,12 +235,16 @@ namespace NCC.Extend
213 235  
214 236 try
215 237 {
216   - // 创建使用记录
  238 + // 创建使用记录(自动计算单价和合计金额)
217 239 var entitiesToInsert = new List<LqInventoryUsageEntity>();
218 240 for (int i = 0; i < input.UsageItems.Count; i++)
219 241 {
220 242 var item = input.UsageItems[i];
221 243  
  244 + // 从产品价格字典获取单价
  245 + var unitPrice = productPriceMap.GetValueOrDefault(item.ProductId, 0);
  246 + var totalAmount = unitPrice * item.UsageQuantity;
  247 +
222 248 var usageEntity = new LqInventoryUsageEntity
223 249 {
224 250 Id = YitIdHelper.NextId().ToString(),
... ... @@ -226,6 +252,8 @@ namespace NCC.Extend
226 252 StoreId = item.StoreId,
227 253 UsageTime = item.UsageTime,
228 254 UsageQuantity = item.UsageQuantity,
  255 + UnitPrice = unitPrice,
  256 + TotalAmount = totalAmount,
229 257 RelatedConsumeId = item.RelatedConsumeId,
230 258 UsageBatchId = batchId,
231 259 CreateUser = _userManager.UserId,
... ... @@ -247,6 +275,64 @@ namespace NCC.Extend
247 275 }
248 276 }
249 277  
  278 + // 创建申请记录并提交审批(审批人ID为必填)
  279 + if (string.IsNullOrWhiteSpace(input.ApproverId))
  280 + {
  281 + throw NCCException.Oh("审批人ID不能为空");
  282 + }
  283 + if (string.IsNullOrWhiteSpace(input.ApplicationStoreId))
  284 + {
  285 + throw NCCException.Oh("申请门店ID不能为空");
  286 + }
  287 +
  288 + {
  289 + // 获取申请人信息
  290 + var applicantUser = await _db.Queryable<UserEntity>()
  291 + .Where(x => x.Id == _userManager.UserId)
  292 + .Select(x => new { x.Id, x.RealName })
  293 + .FirstAsync();
  294 +
  295 + // 获取审批人信息
  296 + var approverUser = await _db.Queryable<UserEntity>()
  297 + .Where(x => x.Id == input.ApproverId)
  298 + .Select(x => new { x.Id, x.RealName })
  299 + .FirstAsync();
  300 +
  301 + if (approverUser == null)
  302 + {
  303 + throw NCCException.Oh("审批人不存在");
  304 + }
  305 +
  306 + // 生成节点ID(使用批次ID作为节点ID,因为只有一个节点)
  307 + var nodeId = YitIdHelper.NextId().ToString();
  308 +
  309 + // 创建申请记录
  310 + var applicationEntity = new LqInventoryUsageApplicationEntity
  311 + {
  312 + Id = YitIdHelper.NextId().ToString(),
  313 + UsageBatchId = batchId,
  314 + ApplicationUserId = applicantUser?.Id ?? _userManager.UserId,
  315 + ApplicationUserName = applicantUser?.RealName ?? "",
  316 + ApplicationStoreId = input.ApplicationStoreId,
  317 + ApplicationTime = DateTime.Now,
  318 + NodeCount = 1,
  319 + CurrentNodeOrder = 1, // 审批中
  320 + CurrentNodeId = nodeId,
  321 + ApprovalStatus = "审批中",
  322 + IsReceived = 0,
  323 + Remarks = input.Remarks,
  324 + CreateUser = _userManager.UserId,
  325 + CreateTime = DateTime.Now,
  326 + IsEffective = StatusEnum.有效.GetHashCode()
  327 + };
  328 +
  329 + var applicationInsertCount = await _db.Insertable(applicationEntity).ExecuteCommandAsync();
  330 + if (applicationInsertCount <= 0)
  331 + {
  332 + throw NCCException.Oh("创建申请记录失败");
  333 + }
  334 + }
  335 +
250 336 _db.Ado.CommitTran();
251 337  
252 338 return new LqInventoryUsageBatchCreateOutput
... ... @@ -420,17 +506,23 @@ namespace NCC.Extend
420 506 /// 根据批次号获取批次信息
421 507 /// </summary>
422 508 /// <remarks>
423   - /// 根据批次ID查询该批次的所有使用记录信息,包括批次基本信息和详细的使用记录列表
  509 + /// 根据批次ID查询该批次的所有使用记录信息,包括批次基本信息和详细的使用记录列表。
  510 + /// 兼容旧数据:如果该批次没有申请记录(旧数据),ApplicationInfo字段为null。
424 511 ///
425 512 /// 返回数据结构:
426 513 /// - 批次基本信息:批次ID、创建时间、创建人、统计信息等
427 514 /// - 使用记录列表:该批次的所有使用记录详情
  515 + /// - 申请记录信息(ApplicationInfo):如果存在申请记录,包含审批状态、是否已领取等信息;如果不存在(旧数据),则为null
428 516 ///
429 517 /// 参数说明:
430 518 /// - batchId: 批次ID(必填)
  519 + ///
  520 + /// 兼容性说明:
  521 + /// - 新数据:包含完整的申请记录信息
  522 + /// - 旧数据:ApplicationInfo为null,但不影响使用记录的查询和显示
431 523 /// </remarks>
432 524 /// <param name="batchId">批次ID</param>
433   - /// <returns>批次信息,包含该批次的所有使用记录</returns>
  525 + /// <returns>批次信息,包含该批次的所有使用记录和申请记录(如果有)</returns>
434 526 /// <response code="200">查询成功,返回批次信息和使用记录列表</response>
435 527 /// <response code="400">批次ID不能为空</response>
436 528 /// <response code="404">批次不存在</response>
... ... @@ -512,6 +604,29 @@ namespace NCC.Extend
512 604 var effectiveRecords = usageRecords.Where(x => x.isEffective == StatusEnum.有效.GetHashCode()).ToList();
513 605 var ineffectiveRecords = usageRecords.Where(x => x.isEffective == StatusEnum.无效.GetHashCode()).ToList();
514 606  
  607 + // 查询申请记录(如果有)
  608 + var application = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  609 + .Where(x => x.UsageBatchId == batchId && x.IsEffective == StatusEnum.有效.GetHashCode())
  610 + .FirstAsync();
  611 +
  612 + ApplicationInfo applicationInfo = null;
  613 + if (application != null)
  614 + {
  615 + applicationInfo = new ApplicationInfo
  616 + {
  617 + Id = application.Id,
  618 + ApplicationUserId = application.ApplicationUserId,
  619 + ApplicationUserName = application.ApplicationUserName,
  620 + ApplicationStoreId = application.ApplicationStoreId,
  621 + ApplicationTime = application.ApplicationTime,
  622 + ApprovalStatus = application.ApprovalStatus,
  623 + IsReceived = application.IsReceived,
  624 + ReceiveTime = application.ReceiveTime,
  625 + ReceiveUser = application.ReceiveUser,
  626 + Remarks = application.Remarks
  627 + };
  628 + }
  629 +
515 630 var batchInfo = new LqInventoryUsageBatchInfoOutput
516 631 {
517 632 BatchId = batchId,
... ... @@ -523,7 +638,8 @@ namespace NCC.Extend
523 638 IneffectiveCount = ineffectiveRecords.Count,
524 639 TotalUsageQuantity = effectiveRecords.Sum(x => x.usageQuantity),
525 640 TotalUsageAmount = effectiveRecords.Sum(x => x.usageTotalValue),
526   - UsageRecords = usageRecords
  641 + UsageRecords = usageRecords,
  642 + ApplicationInfo = applicationInfo
527 643 };
528 644  
529 645 return batchInfo;
... ... @@ -768,6 +884,457 @@ namespace NCC.Extend
768 884 }
769 885 #endregion
770 886  
  887 + #region 审批相关功能
  888 +
  889 + /// <summary>
  890 + /// 审批库存使用申请(通过/不通过)
  891 + /// </summary>
  892 + /// <remarks>
  893 + /// 审批库存使用申请,支持通过和不通过两种结果
  894 + ///
  895 + /// 示例请求:
  896 + /// ```
  897 + /// POST /api/Extend/LqInventoryUsage/Approve/{applicationId}?result=通过&amp;opinion=审批意见
  898 + /// ```
  899 + ///
  900 + /// 参数说明:
  901 + /// - applicationId: 申请ID(必填)
  902 + /// - result: 审批结果(必填,值:通过/不通过)
  903 + /// - opinion: 审批意见(可选)
  904 + /// </remarks>
  905 + /// <param name="applicationId">申请ID</param>
  906 + /// <param name="result">审批结果(通过/不通过)</param>
  907 + /// <param name="opinion">审批意见</param>
  908 + /// <returns>审批结果</returns>
  909 + /// <response code="200">审批成功</response>
  910 + /// <response code="400">申请不存在或状态不正确</response>
  911 + /// <response code="500">服务器错误</response>
  912 + [HttpPost("Approve/{applicationId}")]
  913 + public async Task ApproveApplicationAsync([FromRoute] string applicationId, [FromQuery] string result, [FromQuery] string opinion = "")
  914 + {
  915 + try
  916 + {
  917 + if (string.IsNullOrWhiteSpace(applicationId))
  918 + {
  919 + throw NCCException.Oh("申请ID不能为空");
  920 + }
  921 +
  922 + if (string.IsNullOrWhiteSpace(result) || (result != "通过" && result != "不通过"))
  923 + {
  924 + throw NCCException.Oh("审批结果必须为'通过'或'不通过'");
  925 + }
  926 +
  927 + // 获取申请记录
  928 + var application = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  929 + .Where(x => x.Id == applicationId && x.IsEffective == StatusEnum.有效.GetHashCode())
  930 + .FirstAsync();
  931 +
  932 + if (application == null)
  933 + {
  934 + throw NCCException.Oh("申请记录不存在或已失效");
  935 + }
  936 +
  937 + // 验证申请状态
  938 + if (application.ApprovalStatus != "审批中")
  939 + {
  940 + throw NCCException.Oh($"申请当前状态为{application.ApprovalStatus},无法进行审批操作");
  941 + }
  942 +
  943 + // 获取当前用户信息
  944 + var userInfo = await _userManager.GetUserInfo();
  945 + var approverUser = await _db.Queryable<UserEntity>()
  946 + .Where(x => x.Id == userInfo.userId)
  947 + .Select(x => new { x.Id, x.RealName })
  948 + .FirstAsync();
  949 +
  950 + _db.Ado.BeginTran();
  951 +
  952 + try
  953 + {
  954 + // 创建审批记录
  955 + var approvalRecord = new LqInventoryUsageApprovalRecordEntity
  956 + {
  957 + Id = YitIdHelper.NextId().ToString(),
  958 + ApplicationId = applicationId,
  959 + NodeId = application.CurrentNodeId,
  960 + NodeOrder = 1,
  961 + ApproverId = approverUser.Id,
  962 + ApproverName = approverUser.RealName ?? "",
  963 + ApprovalResult = result,
  964 + ApprovalOpinion = opinion,
  965 + ApprovalTime = DateTime.Now,
  966 + IsCurrentNode = 1
  967 + };
  968 +
  969 + await _db.Insertable(approvalRecord).ExecuteCommandAsync();
  970 +
  971 + // 更新申请状态
  972 + if (result == "通过")
  973 + {
  974 + application.ApprovalStatus = "已通过";
  975 + application.CurrentNodeOrder = 2; // 已完成
  976 + }
  977 + else
  978 + {
  979 + application.ApprovalStatus = "未通过";
  980 + application.CurrentNodeOrder = 0; // 回到待审批状态
  981 + }
  982 +
  983 + application.UpdateUser = userInfo.userId;
  984 + application.UpdateTime = DateTime.Now;
  985 +
  986 + await _db.Updateable(application).ExecuteCommandAsync();
  987 +
  988 + _db.Ado.CommitTran();
  989 + }
  990 + catch
  991 + {
  992 + _db.Ado.RollbackTran();
  993 + throw;
  994 + }
  995 + }
  996 + catch (Exception ex)
  997 + {
  998 + _db.Ado.RollbackTran();
  999 + _logger.LogError(ex, "审批申请失败");
  1000 + throw NCCException.Oh($"审批失败:{ex.Message}");
  1001 + }
  1002 + }
  1003 +
  1004 + /// <summary>
  1005 + /// 标记申请已领取
  1006 + /// </summary>
  1007 + /// <remarks>
  1008 + /// 审批通过后,可以标记申请已领取
  1009 + ///
  1010 + /// 示例请求:
  1011 + /// ```
  1012 + /// PUT /api/Extend/LqInventoryUsage/MarkReceived/{applicationId}
  1013 + /// ```
  1014 + ///
  1015 + /// 参数说明:
  1016 + /// - applicationId: 申请ID(必填)
  1017 + /// </remarks>
  1018 + /// <param name="applicationId">申请ID</param>
  1019 + /// <returns>标记结果</returns>
  1020 + /// <response code="200">标记成功</response>
  1021 + /// <response code="400">申请不存在或状态不正确</response>
  1022 + /// <response code="500">服务器错误</response>
  1023 + [HttpPut("MarkReceived/{applicationId}")]
  1024 + public async Task MarkReceivedAsync([FromRoute] string applicationId)
  1025 + {
  1026 + try
  1027 + {
  1028 + if (string.IsNullOrWhiteSpace(applicationId))
  1029 + {
  1030 + throw NCCException.Oh("申请ID不能为空");
  1031 + }
  1032 +
  1033 + // 获取申请记录
  1034 + var application = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  1035 + .Where(x => x.Id == applicationId && x.IsEffective == StatusEnum.有效.GetHashCode())
  1036 + .FirstAsync();
  1037 +
  1038 + if (application == null)
  1039 + {
  1040 + throw NCCException.Oh("申请记录不存在或已失效");
  1041 + }
  1042 +
  1043 + // 验证申请状态(只有已通过的申请才能标记已领取)
  1044 + if (application.ApprovalStatus != "已通过")
  1045 + {
  1046 + throw NCCException.Oh($"申请当前状态为{application.ApprovalStatus},只有已通过的申请才能标记已领取");
  1047 + }
  1048 +
  1049 + // 获取当前用户信息
  1050 + var userInfo = await _userManager.GetUserInfo();
  1051 +
  1052 + // 更新申请记录
  1053 + application.IsReceived = 1;
  1054 + application.ReceiveTime = DateTime.Now;
  1055 + application.ReceiveUser = userInfo.userId;
  1056 + application.UpdateUser = userInfo.userId;
  1057 + application.UpdateTime = DateTime.Now;
  1058 +
  1059 + var isOk = await _db.Updateable(application).ExecuteCommandAsync();
  1060 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1003);
  1061 + }
  1062 + catch (Exception ex)
  1063 + {
  1064 + _logger.LogError(ex, "标记已领取失败");
  1065 + throw NCCException.Oh($"标记失败:{ex.Message}");
  1066 + }
  1067 + }
  1068 +
  1069 + /// <summary>
  1070 + /// 根据使用批次ID查询申请记录
  1071 + /// </summary>
  1072 + /// <remarks>
  1073 + /// 根据使用批次ID查询对应的申请记录信息。兼容旧数据:如果该批次没有申请记录(旧数据),会返回success=false,data=null。
  1074 + ///
  1075 + /// 示例请求:
  1076 + /// ```
  1077 + /// GET /api/Extend/LqInventoryUsage/GetApplicationByBatchId?batchId=批次ID
  1078 + /// ```
  1079 + ///
  1080 + /// 参数说明:
  1081 + /// - batchId: 使用批次ID(必填)
  1082 + ///
  1083 + /// 返回说明:
  1084 + /// - 如果存在申请记录:返回完整的申请信息,包括审批状态、是否已领取、审批记录等
  1085 + /// - 如果不存在申请记录(旧数据):返回success=false,message="该批次没有对应的申请记录",data=null
  1086 + /// </remarks>
  1087 + /// <param name="batchId">使用批次ID</param>
  1088 + /// <returns>申请记录信息</returns>
  1089 + /// <response code="200">查询成功(可能没有申请记录,需要检查success字段)</response>
  1090 + /// <response code="400">批次ID不能为空</response>
  1091 + /// <response code="500">服务器错误</response>
  1092 + [HttpGet("GetApplicationByBatchId")]
  1093 + public async Task<dynamic> GetApplicationByBatchIdAsync([FromQuery] string batchId)
  1094 + {
  1095 + try
  1096 + {
  1097 + if (string.IsNullOrWhiteSpace(batchId))
  1098 + {
  1099 + throw NCCException.Oh("批次ID不能为空");
  1100 + }
  1101 +
  1102 + // 查询申请记录
  1103 + var application = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  1104 + .Where(x => x.UsageBatchId == batchId && x.IsEffective == StatusEnum.有效.GetHashCode())
  1105 + .FirstAsync();
  1106 +
  1107 + if (application == null)
  1108 + {
  1109 + return new
  1110 + {
  1111 + success = false,
  1112 + message = "该批次没有对应的申请记录",
  1113 + data = (object)null
  1114 + };
  1115 + }
  1116 +
  1117 + // 查询审批记录
  1118 + var approvalRecords = await _db.Queryable<LqInventoryUsageApprovalRecordEntity>()
  1119 + .Where(x => x.ApplicationId == application.Id)
  1120 + .OrderBy(x => x.ApprovalTime)
  1121 + .ToListAsync();
  1122 +
  1123 + // 补充审批人姓名(如果审批记录中没有)
  1124 + var approverIds = approvalRecords.Where(x => !string.IsNullOrEmpty(x.ApproverId))
  1125 + .Select(x => x.ApproverId)
  1126 + .Distinct()
  1127 + .ToList();
  1128 +
  1129 + var approverDict = new Dictionary<string, string>();
  1130 + if (approverIds.Any())
  1131 + {
  1132 + var approvers = await _db.Queryable<UserEntity>()
  1133 + .Where(x => approverIds.Contains(x.Id))
  1134 + .Select(x => new { x.Id, x.RealName })
  1135 + .ToListAsync();
  1136 + approverDict = approvers.ToDictionary(k => k.Id, v => v.RealName ?? "");
  1137 + }
  1138 +
  1139 + return new
  1140 + {
  1141 + success = true,
  1142 + data = new
  1143 + {
  1144 + id = application.Id,
  1145 + usageBatchId = application.UsageBatchId,
  1146 + applicationUserId = application.ApplicationUserId,
  1147 + applicationUserName = application.ApplicationUserName,
  1148 + applicationStoreId = application.ApplicationStoreId,
  1149 + applicationTime = application.ApplicationTime,
  1150 + approvalStatus = application.ApprovalStatus,
  1151 + isReceived = application.IsReceived,
  1152 + receiveTime = application.ReceiveTime,
  1153 + receiveUser = application.ReceiveUser,
  1154 + remarks = application.Remarks,
  1155 + approvalRecords = approvalRecords.Select(x => new
  1156 + {
  1157 + id = x.Id,
  1158 + approverId = x.ApproverId,
  1159 + approverName = string.IsNullOrEmpty(x.ApproverName) && approverDict.ContainsKey(x.ApproverId)
  1160 + ? approverDict[x.ApproverId]
  1161 + : x.ApproverName,
  1162 + approvalResult = x.ApprovalResult,
  1163 + approvalOpinion = x.ApprovalOpinion,
  1164 + approvalTime = x.ApprovalTime
  1165 + }).ToList()
  1166 + }
  1167 + };
  1168 + }
  1169 + catch (Exception ex)
  1170 + {
  1171 + _logger.LogError(ex, "查询申请记录失败");
  1172 + throw NCCException.Oh($"查询失败:{ex.Message}");
  1173 + }
  1174 + }
  1175 +
  1176 + #endregion
  1177 +
  1178 + #region 门店领取统计
  1179 +
  1180 + /// <summary>
  1181 + /// 门店领取统计(按月份统计每个门店的领取总金额)
  1182 + /// </summary>
  1183 + /// <remarks>
  1184 + /// 统计指定月份每个门店已领取的库存使用申请的总金额。只统计审批通过且已领取的申请。
  1185 + ///
  1186 + /// 示例请求:
  1187 + /// ```json
  1188 + /// POST /api/Extend/LqInventoryUsage/GetStoreReceiveStatistics
  1189 + /// {
  1190 + /// "year": 2025,
  1191 + /// "month": 11,
  1192 + /// "storeId": "门店ID(可选)"
  1193 + /// }
  1194 + /// ```
  1195 + ///
  1196 + /// 参数说明:
  1197 + /// - year: 统计年份(必填,范围:2020-2100)
  1198 + /// - month: 统计月份(必填,范围:1-12)
  1199 + /// - storeId: 门店ID(可选,不传则统计所有门店)
  1200 + ///
  1201 + /// 返回说明:
  1202 + /// - 按门店分组统计
  1203 + /// - 每个门店包含:门店ID、门店名称、申请数量、领取总金额
  1204 + /// - 按总金额降序排列
  1205 + /// </remarks>
  1206 + /// <param name="input">统计查询输入</param>
  1207 + /// <returns>门店领取统计列表</returns>
  1208 + /// <response code="200">查询成功</response>
  1209 + /// <response code="400">输入参数错误</response>
  1210 + /// <response code="500">服务器错误</response>
  1211 + [HttpPost("GetStoreReceiveStatistics")]
  1212 + public async Task<dynamic> GetStoreReceiveStatisticsAsync([FromBody] StoreReceiveStatisticsInput input)
  1213 + {
  1214 + try
  1215 + {
  1216 + if (input == null)
  1217 + {
  1218 + throw NCCException.Oh("输入参数不能为空");
  1219 + }
  1220 +
  1221 + // 计算月份的开始和结束时间
  1222 + var startDate = new DateTime(input.Year, input.Month, 1);
  1223 + var endDate = startDate.AddMonths(1).AddDays(-1).Date.AddHours(23).AddMinutes(59).AddSeconds(59);
  1224 +
  1225 + // 查询已领取的申请记录(审批通过且已领取)
  1226 + var applicationQuery = _db.Queryable<LqInventoryUsageApplicationEntity>()
  1227 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  1228 + .Where(x => x.ApprovalStatus == "已通过")
  1229 + .Where(x => x.IsReceived == 1)
  1230 + .Where(x => x.ReceiveTime >= startDate && x.ReceiveTime <= endDate);
  1231 +
  1232 + if (!string.IsNullOrWhiteSpace(input.StoreId))
  1233 + {
  1234 + applicationQuery = applicationQuery.Where(x => x.ApplicationStoreId == input.StoreId);
  1235 + }
  1236 +
  1237 + var applications = await applicationQuery.ToListAsync();
  1238 +
  1239 + if (!applications.Any())
  1240 + {
  1241 + return new
  1242 + {
  1243 + success = true,
  1244 + data = new List<object>(),
  1245 + total = 0,
  1246 + message = $"未找到{input.Year}年{input.Month}月的门店领取记录"
  1247 + };
  1248 + }
  1249 +
  1250 + // 获取所有批次ID
  1251 + var batchIds = applications.Select(x => x.UsageBatchId).Distinct().ToList();
  1252 +
  1253 + // 查询这些批次的使用记录,计算总金额
  1254 + var usageRecords = await _db.Queryable<LqInventoryUsageEntity>()
  1255 + .Where(x => batchIds.Contains(x.UsageBatchId))
  1256 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  1257 + .Select(x => new
  1258 + {
  1259 + x.UsageBatchId,
  1260 + x.StoreId,
  1261 + x.TotalAmount
  1262 + })
  1263 + .ToListAsync();
  1264 +
  1265 + // 按门店分组统计
  1266 + var storeStatistics = new Dictionary<string, StoreReceiveStatisticsItem>();
  1267 +
  1268 + foreach (var application in applications)
  1269 + {
  1270 + var batchUsageRecords = usageRecords.Where(x => x.UsageBatchId == application.UsageBatchId).ToList();
  1271 + var storeId = application.ApplicationStoreId;
  1272 +
  1273 + if (string.IsNullOrWhiteSpace(storeId))
  1274 + {
  1275 + // 如果没有申请门店ID,使用使用记录中的门店ID
  1276 + storeId = batchUsageRecords.FirstOrDefault()?.StoreId;
  1277 + }
  1278 +
  1279 + if (string.IsNullOrWhiteSpace(storeId))
  1280 + {
  1281 + continue;
  1282 + }
  1283 +
  1284 + if (!storeStatistics.ContainsKey(storeId))
  1285 + {
  1286 + // 查询门店名称
  1287 + var store = await _db.Queryable<LqMdxxEntity>()
  1288 + .Where(x => x.Id == storeId)
  1289 + .Select(x => new { x.Dm })
  1290 + .FirstAsync();
  1291 +
  1292 + storeStatistics[storeId] = new StoreReceiveStatisticsItem
  1293 + {
  1294 + StoreId = storeId,
  1295 + StoreName = store?.Dm ?? "未知门店",
  1296 + ApplicationCount = 0,
  1297 + TotalAmount = 0
  1298 + };
  1299 + }
  1300 +
  1301 + var totalAmount = batchUsageRecords.Sum(x => x.TotalAmount);
  1302 + storeStatistics[storeId].ApplicationCount++;
  1303 + storeStatistics[storeId].TotalAmount += totalAmount;
  1304 + }
  1305 +
  1306 + // 转换为列表并按总金额降序排列
  1307 + var result = storeStatistics.Values
  1308 + .OrderByDescending(x => x.TotalAmount)
  1309 + .ToList();
  1310 +
  1311 + return new
  1312 + {
  1313 + success = true,
  1314 + data = result,
  1315 + total = result.Count,
  1316 + message = $"查询成功,共{result.Count}个门店"
  1317 + };
  1318 + }
  1319 + catch (Exception ex)
  1320 + {
  1321 + _logger.LogError(ex, "查询门店领取统计失败");
  1322 + throw NCCException.Oh($"查询失败:{ex.Message}");
  1323 + }
  1324 + }
  1325 +
  1326 + /// <summary>
  1327 + /// 门店领取统计项
  1328 + /// </summary>
  1329 + private class StoreReceiveStatisticsItem
  1330 + {
  1331 + public string StoreId { get; set; }
  1332 + public string StoreName { get; set; }
  1333 + public int ApplicationCount { get; set; }
  1334 + public decimal TotalAmount { get; set; }
  1335 + }
  1336 +
  1337 + #endregion
771 1338  
772 1339 }
773 1340 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqMdMajorProjectTeacherAssignmentService.cs
... ... @@ -248,7 +248,7 @@ namespace NCC.Extend
248 248 /// 从上个月复制门店大项目部老师归属设置数据到目标月份
249 249 ///
250 250 /// 示例请求:
251   - /// POST /api/Extend/LqMdMajorProjectTeacherAssignment/CopyLastMonthData?targetYear=2025&targetMonth=02
  251 + /// POST /api/Extend/LqMdMajorProjectTeacherAssignment/CopyLastMonthData?targetYear=2025&amp;targetMonth=02
252 252 ///
253 253 /// 业务逻辑:
254 254 /// 1. 确定目标年份和月份(如果未指定,使用当前年月)
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
... ... @@ -835,8 +835,15 @@ namespace NCC.Extend.LqReimbursementApplication
835 835  
836 836 // 如果存在"待审批"记录,说明可以审批,不需要抛出异常
837 837 // 如果不存在"待审批"记录,需要检查是否有"通过"或"不通过"的记录(不包括"退回",因为退回后可以重新审批)
  838 + // 注意:如果状态已经是"已通过",说明整个流程已完成,不允许再次审批
838 839 if (existingPendingApprovalRecord == null)
839 840 {
  841 + // 如果申请状态已经是"已通过",不允许再次审批
  842 + if (entity.ApprovalStatus == "已通过")
  843 + {
  844 + throw new Exception("该申请已经审批通过,无法再次审批");
  845 + }
  846 +
840 847 // 检查是否有"通过"或"不通过"的记录(不包括"退回")
841 848 var completedRecord = await _db.Queryable<LqReimbursementApprovalRecordEntity>()
842 849 .Where(x => x.ApplicationId == id
... ... @@ -847,6 +854,7 @@ namespace NCC.Extend.LqReimbursementApplication
847 854  
848 855 if (completedRecord != null)
849 856 {
  857 + // 如果申请状态还是"审批中",说明还有其他审批人未审批,当前用户已经审批过,不允许重复审批
850 858 throw new Exception("您已经审批过该节点");
851 859 }
852 860 }
... ...
sql/创建合同管理表结构.sql 0 → 100644
  1 +-- ============================================
  2 +-- 合同管理系统表结构SQL
  3 +-- ============================================
  4 +-- 说明:用于管理门店合同信息,自动生成月租明细,统计每个月每个门店的合同支付明细以及成本费用
  5 +-- 执行时间:2025年
  6 +-- ============================================
  7 +
  8 +-- ============================================
  9 +-- 1. 创建合同表(lq_contract)
  10 +-- ============================================
  11 +CREATE TABLE IF NOT EXISTS `lq_contract` (
  12 + `F_Id` varchar(50) NOT NULL COMMENT '主键ID',
  13 + `F_StoreId` varchar(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)',
  14 + `F_StoreName` varchar(200) NOT NULL COMMENT '店名(冗余字段,便于查询)',
  15 + `F_Title` varchar(200) NOT NULL COMMENT '标题',
  16 + `F_Category` varchar(100) DEFAULT NULL COMMENT '分类(string类型,自己填写)',
  17 + `F_TenantName` varchar(200) DEFAULT NULL COMMENT '户名',
  18 + `F_ContractStartDate` datetime NOT NULL COMMENT '合同起始日期',
  19 + `F_ContractEndDate` datetime NOT NULL COMMENT '合同结束日期',
  20 + `F_ReminderDays` int DEFAULT 0 COMMENT '提前多少天提醒',
  21 + `F_Deposit` decimal(18,2) DEFAULT 0.00 COMMENT '押金',
  22 + `F_NextPaymentDate` datetime DEFAULT NULL COMMENT '下次应交时间',
  23 + `F_MonthlyRent` decimal(18,2) NOT NULL COMMENT '月租',
  24 + `F_PaymentAmount` decimal(18,2) NOT NULL COMMENT '缴租金额(每次交租的金额,通常=月租×交租周期)',
  25 + `F_PaymentCycle` int NOT NULL COMMENT '交租周期(数字,表示几个月,如1、3、6等)',
  26 + `F_Remarks` varchar(1000) DEFAULT NULL COMMENT '备注',
  27 + `F_Attachment` varchar(500) DEFAULT NULL COMMENT '附件(存储附件路径或JSON)',
  28 + `F_CreateUser` varchar(50) NOT NULL COMMENT '创建人ID',
  29 + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  30 + `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人ID',
  31 + `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
  32 + `F_IsEffective` int DEFAULT 1 COMMENT '是否有效(1-有效,0-无效)',
  33 + PRIMARY KEY (`F_Id`),
  34 + KEY `idx_store_id` (`F_StoreId`),
  35 + KEY `idx_contract_start_date` (`F_ContractStartDate`),
  36 + KEY `idx_contract_end_date` (`F_ContractEndDate`),
  37 + KEY `idx_next_payment_date` (`F_NextPaymentDate`),
  38 + KEY `idx_category` (`F_Category`),
  39 + KEY `idx_is_effective` (`F_IsEffective`),
  40 + KEY `idx_create_time` (`F_CreateTime`)
  41 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同表';
  42 +
  43 +-- ============================================
  44 +-- 2. 创建月租明细表(lq_contract_rent_detail)
  45 +-- ============================================
  46 +CREATE TABLE IF NOT EXISTS `lq_contract_rent_detail` (
  47 + `F_Id` varchar(50) NOT NULL COMMENT '主键ID',
  48 + `F_ContractId` varchar(50) NOT NULL COMMENT '合同ID(关联lq_contract.F_Id)',
  49 + `F_PaymentMonth` datetime NOT NULL COMMENT '应缴月份(表示哪个月份,格式:YYYY-MM-01)',
  50 + `F_DueDate` datetime NOT NULL COMMENT '应缴日期(具体应缴日期)',
  51 + `F_DueAmount` decimal(18,2) NOT NULL COMMENT '应缴金额',
  52 + `F_IsPaid` int DEFAULT 0 COMMENT '是否已缴(0-未缴,1-已缴)',
  53 + `F_ActualPaymentDate` datetime DEFAULT NULL COMMENT '实际缴费时间',
  54 + `F_ActualPaymentAmount` decimal(18,2) DEFAULT NULL COMMENT '实际缴费金额',
  55 + `F_Remarks` varchar(500) DEFAULT NULL COMMENT '备注',
  56 + `F_CreateUser` varchar(50) NOT NULL COMMENT '创建人ID',
  57 + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  58 + `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人ID',
  59 + `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
  60 + `F_IsEffective` int DEFAULT 1 COMMENT '是否有效(1-有效,0-无效)',
  61 + PRIMARY KEY (`F_Id`),
  62 + KEY `idx_contract_id` (`F_ContractId`),
  63 + KEY `idx_payment_month` (`F_PaymentMonth`),
  64 + KEY `idx_due_date` (`F_DueDate`),
  65 + KEY `idx_is_paid` (`F_IsPaid`),
  66 + KEY `idx_contract_payment_month` (`F_ContractId`, `F_PaymentMonth`),
  67 + KEY `idx_is_effective` (`F_IsEffective`),
  68 + KEY `idx_create_time` (`F_CreateTime`)
  69 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='月租明细表';
  70 +
  71 +-- ============================================
  72 +-- 3. 添加外键约束(可选,根据实际需求决定是否添加)
  73 +-- ============================================
  74 +-- 注意:如果添加外键约束,删除合同时需要先删除明细,或者使用级联删除
  75 +-- 建议:不添加外键约束,在应用层控制数据一致性,更灵活
  76 +
  77 +-- ALTER TABLE `lq_contract_rent_detail`
  78 +-- ADD CONSTRAINT `fk_contract_rent_detail_contract`
  79 +-- FOREIGN KEY (`F_ContractId`) REFERENCES `lq_contract` (`F_Id`)
  80 +-- ON DELETE CASCADE ON UPDATE CASCADE;
  81 +
  82 +-- ============================================
  83 +-- 4. 数据示例说明
  84 +-- ============================================
  85 +-- 合同示例:
  86 +-- F_Id: '768041985045955845'
  87 +-- F_StoreId: '1649328471923847168'
  88 +-- F_StoreName: '绿纤总部'
  89 +-- F_Title: '门店租赁合同'
  90 +-- F_Category: '租赁合同'
  91 +-- F_TenantName: '张三'
  92 +-- F_ContractStartDate: '2025-01-01 00:00:00'
  93 +-- F_ContractEndDate: '2025-12-31 23:59:59'
  94 +-- F_ReminderDays: 7
  95 +-- F_Deposit: 5000.00
  96 +-- F_NextPaymentDate: '2025-01-25 00:00:00'
  97 +-- F_MonthlyRent: 1000.00
  98 +-- F_PaymentAmount: 3000.00
  99 +-- F_PaymentCycle: 3
  100 +-- F_Remarks: '季度交租'
  101 +--
  102 +-- 对应的月租明细(自动生成):
  103 +-- 明细1: F_PaymentMonth='2025-01-01', F_DueDate='2025-01-01', F_DueAmount=3000.00, F_IsPaid=0
  104 +-- 明细2: F_PaymentMonth='2025-04-01', F_DueDate='2025-04-01', F_DueAmount=3000.00, F_IsPaid=0
  105 +-- 明细3: F_PaymentMonth='2025-07-01', F_DueDate='2025-07-01', F_DueAmount=3000.00, F_IsPaid=0
  106 +-- 明细4: F_PaymentMonth='2025-10-01', F_DueDate='2025-10-01', F_DueAmount=3000.00, F_IsPaid=0
  107 +
  108 +-- ============================================
  109 +-- SQL脚本执行完成
  110 +-- ============================================
  111 +-- 说明:
  112 +-- 1. lq_contract 表:存储合同基本信息
  113 +-- 2. lq_contract_rent_detail 表:存储月租明细,根据合同自动生成
  114 +-- 3. 删除合同时需要级联删除明细(在应用层实现)
  115 +-- 4. 更新合同时如果影响明细,需要重新生成明细
  116 +
... ...
sql/创建库存使用申请审批流程表.sql 0 → 100644
  1 +-- 库存使用申请审批流程 - 数据库表结构
  2 +-- 执行时间:2025年
  3 +-- 说明:支持单节点审批流程,审批人可配置(财务老师或库管),审批通过后可标记是否已领取
  4 +
  5 +-- 1. 库存使用申请表(申请记录)
  6 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_application` (
  7 + `F_Id` varchar(50) NOT NULL COMMENT '申请编号',
  8 + `F_UsageBatchId` varchar(50) NOT NULL COMMENT '使用批次ID(关联lq_inventory_usage.F_UsageBatchId)',
  9 + `F_ApplicationUserId` varchar(50) NOT NULL COMMENT '申请人ID',
  10 + `F_ApplicationUserName` varchar(100) DEFAULT NULL COMMENT '申请人姓名',
  11 + `F_ApplicationStoreId` varchar(50) DEFAULT NULL COMMENT '申请门店ID',
  12 + `F_ApplicationTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间',
  13 + `F_NodeCount` int DEFAULT 1 COMMENT '节点数量(固定为1)',
  14 + `F_CurrentNodeOrder` int DEFAULT 0 COMMENT '当前审批节点(0-待审批,1-审批中,2-已完成)',
  15 + `F_CurrentNodeId` varchar(50) DEFAULT NULL COMMENT '当前节点ID',
  16 + `F_ApprovalStatus` varchar(20) DEFAULT '待审批' COMMENT '审批状态(待审批/审批中/已通过/未通过/已退回)',
  17 + `F_IsReceived` int DEFAULT 0 COMMENT '是否已领取(1-已领取,0-未领取)',
  18 + `F_ReceiveTime` datetime DEFAULT NULL COMMENT '领取时间',
  19 + `F_ReceiveUser` varchar(50) DEFAULT NULL COMMENT '领取人ID',
  20 + `F_Remarks` varchar(500) DEFAULT NULL COMMENT '备注',
  21 + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  22 + `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人ID',
  23 + `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
  24 + `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人ID',
  25 + `F_IsEffective` int DEFAULT 1 COMMENT '是否有效(1-有效,0-无效)',
  26 + PRIMARY KEY (`F_Id`),
  27 + UNIQUE KEY `uk_usage_batch_id` (`F_UsageBatchId`),
  28 + KEY `idx_application_user_id` (`F_ApplicationUserId`),
  29 + KEY `idx_application_store_id` (`F_ApplicationStoreId`),
  30 + KEY `idx_current_node` (`F_CurrentNodeId`),
  31 + KEY `idx_approval_status` (`F_ApprovalStatus`),
  32 + KEY `idx_node_count` (`F_NodeCount`),
  33 + KEY `idx_create_time` (`F_CreateTime`)
  34 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='库存使用申请表';
  35 +
  36 +-- 2. 库存使用申请节点表(每个申请的节点配置,固定为1个节点)
  37 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_application_node` (
  38 + `F_Id` varchar(50) NOT NULL COMMENT '节点编号',
  39 + `F_ApplicationId` varchar(50) NOT NULL COMMENT '申请ID',
  40 + `F_NodeOrder` int NOT NULL DEFAULT 1 COMMENT '节点顺序(固定为1)',
  41 + `F_NodeName` varchar(100) DEFAULT '审批' COMMENT '节点名称',
  42 + `F_ApprovalType` varchar(20) DEFAULT '会签' COMMENT '审批类型(会签/或签)',
  43 + `F_IsRequired` int DEFAULT 1 COMMENT '是否必审(1-必审,0-可选)',
  44 + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  45 + PRIMARY KEY (`F_Id`),
  46 + KEY `idx_application_id` (`F_ApplicationId`),
  47 + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`),
  48 + UNIQUE KEY `uk_application_node_order` (`F_ApplicationId`, `F_NodeOrder`)
  49 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='库存使用申请节点表';
  50 +
  51 +-- 3. 库存使用申请节点审批人表(每个节点的审批人,在创建申请时指定)
  52 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_application_node_user` (
  53 + `F_Id` varchar(50) NOT NULL COMMENT '记录编号',
  54 + `F_ApplicationId` varchar(50) NOT NULL COMMENT '申请ID',
  55 + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号',
  56 + `F_NodeOrder` int NOT NULL DEFAULT 1 COMMENT '节点顺序(固定为1)',
  57 + `F_UserId` varchar(50) NOT NULL COMMENT '审批人ID',
  58 + `F_UserName` varchar(100) DEFAULT NULL COMMENT '审批人姓名',
  59 + `F_SortOrder` int DEFAULT 0 COMMENT '排序',
  60 + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  61 + PRIMARY KEY (`F_Id`),
  62 + KEY `idx_application_id` (`F_ApplicationId`),
  63 + KEY `idx_node_id` (`F_NodeId`),
  64 + KEY `idx_user_id` (`F_UserId`),
  65 + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`),
  66 + UNIQUE KEY `uk_application_node_user` (`F_ApplicationId`, `F_NodeId`, `F_UserId`)
  67 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='库存使用申请节点审批人表';
  68 +
  69 +-- 4. 审批记录表
  70 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_approval_record` (
  71 + `F_Id` varchar(50) NOT NULL COMMENT '记录编号',
  72 + `F_ApplicationId` varchar(50) NOT NULL COMMENT '申请ID',
  73 + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号',
  74 + `F_NodeOrder` int NOT NULL DEFAULT 1 COMMENT '节点顺序(固定为1)',
  75 + `F_ApproverId` varchar(50) NOT NULL COMMENT '审批人ID',
  76 + `F_ApproverName` varchar(100) DEFAULT NULL COMMENT '审批人姓名',
  77 + `F_ApprovalResult` varchar(20) NOT NULL COMMENT '审批结果(通过/不通过/退回)',
  78 + `F_ApprovalOpinion` varchar(500) DEFAULT NULL COMMENT '审批意见',
  79 + `F_ApprovalTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '审批时间',
  80 + `F_IsCurrentNode` int DEFAULT 0 COMMENT '是否当前节点(1-是,0-否)',
  81 + PRIMARY KEY (`F_Id`),
  82 + KEY `idx_application_id` (`F_ApplicationId`),
  83 + KEY `idx_node_id` (`F_NodeId`),
  84 + KEY `idx_approver_id` (`F_ApproverId`),
  85 + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`)
  86 +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='库存使用申请审批记录表';
  87 +
  88 +-- 5. 修改库存使用记录表,添加单价和合计金额字段
  89 +ALTER TABLE `lq_inventory_usage`
  90 + ADD COLUMN `F_UnitPrice` decimal(18,2) DEFAULT 0.00 COMMENT '单价(从产品表F_Price获取)' AFTER `F_UsageQuantity`,
  91 + ADD COLUMN `F_TotalAmount` decimal(18,2) DEFAULT 0.00 COMMENT '合计金额(单价×数量)' AFTER `F_UnitPrice`,
  92 + ADD KEY `idx_unit_price` (`F_UnitPrice`),
  93 + ADD KEY `idx_total_amount` (`F_TotalAmount`);
  94 +
  95 +-- 6. 数据迁移:更新已有数据的单价和合计金额(从产品表获取价格)
  96 +UPDATE `lq_inventory_usage` u
  97 +INNER JOIN `lq_product` p ON u.`F_ProductId` = p.`F_Id`
  98 +SET u.`F_UnitPrice` = p.`F_Price`,
  99 + u.`F_TotalAmount` = p.`F_Price` * u.`F_UsageQuantity`
  100 +WHERE u.`F_UnitPrice` = 0 OR u.`F_UnitPrice` IS NULL;
  101 +
  102 +
... ...
sql/库存管理审批功能相关表结构.sql 0 → 100644
  1 +-- ============================================
  2 +-- 库存管理审批功能相关表结构SQL
  3 +-- ============================================
  4 +-- 说明:为库存使用记录添加审批功能,包括申请记录、审批记录等
  5 +-- 执行时间:2025年
  6 +-- ============================================
  7 +
  8 +-- ============================================
  9 +-- 1. 修改库存使用记录表,添加单价和合计金额字段(如果不存在)
  10 +-- ============================================
  11 +-- 说明:用于统计成本费用,单价从产品表获取,合计金额 = 单价 × 数量
  12 +
  13 +-- 检查并添加单价字段
  14 +SET @dbname = DATABASE();
  15 +SET @tablename = 'lq_inventory_usage';
  16 +SET @columnname = 'F_UnitPrice';
  17 +SET @preparedStatement = (SELECT IF(
  18 + (
  19 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  20 + WHERE
  21 + (TABLE_SCHEMA = @dbname)
  22 + AND (TABLE_NAME = @tablename)
  23 + AND (COLUMN_NAME = @columnname)
  24 + ) > 0,
  25 + 'SELECT 1',
  26 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` decimal(18,2) DEFAULT 0.00 COMMENT ''单价(从产品表F_Price获取)'' AFTER `F_UsageQuantity`')
  27 +));
  28 +PREPARE alterIfNotExists FROM @preparedStatement;
  29 +EXECUTE alterIfNotExists;
  30 +DEALLOCATE PREPARE alterIfNotExists;
  31 +
  32 +-- 检查并添加合计金额字段
  33 +SET @columnname = 'F_TotalAmount';
  34 +SET @preparedStatement = (SELECT IF(
  35 + (
  36 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  37 + WHERE
  38 + (TABLE_SCHEMA = @dbname)
  39 + AND (TABLE_NAME = @tablename)
  40 + AND (COLUMN_NAME = @columnname)
  41 + ) > 0,
  42 + 'SELECT 1',
  43 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` decimal(18,2) DEFAULT 0.00 COMMENT ''合计金额(单价×数量)'' AFTER `F_UnitPrice`')
  44 +));
  45 +PREPARE alterIfNotExists FROM @preparedStatement;
  46 +EXECUTE alterIfNotExists;
  47 +DEALLOCATE PREPARE alterIfNotExists;
  48 +
  49 +-- 添加索引(如果不存在)
  50 +ALTER TABLE `lq_inventory_usage`
  51 + ADD INDEX IF NOT EXISTS `idx_unit_price` (`F_UnitPrice`),
  52 + ADD INDEX IF NOT EXISTS `idx_total_amount` (`F_TotalAmount`);
  53 +
  54 +-- 数据迁移:更新已有数据的单价和合计金额(从产品表获取价格)
  55 +UPDATE `lq_inventory_usage` u
  56 +INNER JOIN `lq_product` p ON u.`F_ProductId` = p.`F_Id`
  57 +SET u.`F_UnitPrice` = p.`F_Price`,
  58 + u.`F_TotalAmount` = p.`F_Price` * u.`F_UsageQuantity`
  59 +WHERE (u.`F_UnitPrice` = 0 OR u.`F_UnitPrice` IS NULL) AND p.`F_Price` IS NOT NULL;
  60 +
  61 +-- ============================================
  62 +-- 2. 创建库存使用申请表(如果不存在)
  63 +-- ============================================
  64 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_application` (
  65 + `F_Id` varchar(50) NOT NULL COMMENT '申请编号',
  66 + `F_UsageBatchId` varchar(50) NOT NULL COMMENT '使用批次ID(关联lq_inventory_usage.F_UsageBatchId)',
  67 + `F_ApplicationUserId` varchar(50) NOT NULL COMMENT '申请人ID',
  68 + `F_ApplicationUserName` varchar(100) DEFAULT NULL COMMENT '申请人姓名',
  69 + `F_ApplicationStoreId` varchar(50) DEFAULT NULL COMMENT '申请门店ID',
  70 + `F_ApplicationTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间',
  71 + `F_NodeCount` int DEFAULT 1 COMMENT '节点数量(固定为1)',
  72 + `F_CurrentNodeOrder` int DEFAULT 0 COMMENT '当前审批节点(0-待审批,1-审批中,2-已完成)',
  73 + `F_CurrentNodeId` varchar(50) DEFAULT NULL COMMENT '当前节点ID',
  74 + `F_ApprovalStatus` varchar(20) DEFAULT '待审批' COMMENT '审批状态(待审批/审批中/已通过/未通过/已退回)',
  75 + `F_IsReceived` int DEFAULT 0 COMMENT '是否已领取(1-已领取,0-未领取)',
  76 + `F_ReceiveTime` datetime DEFAULT NULL COMMENT '领取时间',
  77 + `F_ReceiveUser` varchar(50) DEFAULT NULL COMMENT '领取人ID',
  78 + `F_Remarks` varchar(500) DEFAULT NULL COMMENT '备注',
  79 + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  80 + `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人ID',
  81 + `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
  82 + `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人ID',
  83 + `F_IsEffective` int DEFAULT 1 COMMENT '是否有效(1-有效,0-无效)',
  84 + PRIMARY KEY (`F_Id`),
  85 + UNIQUE KEY `uk_usage_batch_id` (`F_UsageBatchId`),
  86 + KEY `idx_application_user_id` (`F_ApplicationUserId`),
  87 + KEY `idx_application_store_id` (`F_ApplicationStoreId`),
  88 + KEY `idx_current_node` (`F_CurrentNodeId`),
  89 + KEY `idx_approval_status` (`F_ApprovalStatus`),
  90 + KEY `idx_is_received` (`F_IsReceived`),
  91 + KEY `idx_create_time` (`F_CreateTime`)
  92 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存使用申请表';
  93 +
  94 +-- ============================================
  95 +-- 3. 创建库存使用申请审批记录表(如果不存在)
  96 +-- ============================================
  97 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_approval_record` (
  98 + `F_Id` varchar(50) NOT NULL COMMENT '记录编号',
  99 + `F_ApplicationId` varchar(50) NOT NULL COMMENT '申请ID',
  100 + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号',
  101 + `F_NodeOrder` int NOT NULL DEFAULT 1 COMMENT '节点顺序(固定为1)',
  102 + `F_ApproverId` varchar(50) NOT NULL COMMENT '审批人ID',
  103 + `F_ApproverName` varchar(100) DEFAULT NULL COMMENT '审批人姓名',
  104 + `F_ApprovalResult` varchar(20) NOT NULL COMMENT '审批结果(通过/不通过/退回)',
  105 + `F_ApprovalOpinion` varchar(500) DEFAULT NULL COMMENT '审批意见',
  106 + `F_ApprovalTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '审批时间',
  107 + `F_IsCurrentNode` int DEFAULT 0 COMMENT '是否当前节点(1-是,0-否)',
  108 + PRIMARY KEY (`F_Id`),
  109 + KEY `idx_application_id` (`F_ApplicationId`),
  110 + KEY `idx_node_id` (`F_NodeId`),
  111 + KEY `idx_approver_id` (`F_ApproverId`),
  112 + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`),
  113 + KEY `idx_approval_result` (`F_ApprovalResult`),
  114 + KEY `idx_approval_time` (`F_ApprovalTime`)
  115 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存使用申请审批记录表';
  116 +
  117 +-- ============================================
  118 +-- 4. 检查并添加可能缺失的字段(兼容性处理)
  119 +-- ============================================
  120 +
  121 +-- 检查 lq_inventory_usage_application 表是否有所需字段
  122 +-- 如果表已存在但字段缺失,则添加字段
  123 +
  124 +-- 检查并添加 F_IsReceived 字段(如果不存在)
  125 +SET @tablename = 'lq_inventory_usage_application';
  126 +SET @columnname = 'F_IsReceived';
  127 +SET @preparedStatement = (SELECT IF(
  128 + (
  129 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  130 + WHERE
  131 + (TABLE_SCHEMA = @dbname)
  132 + AND (TABLE_NAME = @tablename)
  133 + AND (COLUMN_NAME = @columnname)
  134 + ) > 0,
  135 + 'SELECT 1',
  136 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` int DEFAULT 0 COMMENT ''是否已领取(1-已领取,0-未领取)'' AFTER `F_ApprovalStatus`')
  137 +));
  138 +PREPARE alterIfNotExists FROM @preparedStatement;
  139 +EXECUTE alterIfNotExists;
  140 +DEALLOCATE PREPARE alterIfNotExists;
  141 +
  142 +-- 检查并添加 F_ReceiveTime 字段(如果不存在)
  143 +SET @columnname = 'F_ReceiveTime';
  144 +SET @preparedStatement = (SELECT IF(
  145 + (
  146 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  147 + WHERE
  148 + (TABLE_SCHEMA = @dbname)
  149 + AND (TABLE_NAME = @tablename)
  150 + AND (COLUMN_NAME = @columnname)
  151 + ) > 0,
  152 + 'SELECT 1',
  153 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` datetime DEFAULT NULL COMMENT ''领取时间'' AFTER `F_IsReceived`')
  154 +));
  155 +PREPARE alterIfNotExists FROM @preparedStatement;
  156 +EXECUTE alterIfNotExists;
  157 +DEALLOCATE PREPARE alterIfNotExists;
  158 +
  159 +-- 检查并添加 F_ReceiveUser 字段(如果不存在)
  160 +SET @columnname = 'F_ReceiveUser';
  161 +SET @preparedStatement = (SELECT IF(
  162 + (
  163 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  164 + WHERE
  165 + (TABLE_SCHEMA = @dbname)
  166 + AND (TABLE_NAME = @tablename)
  167 + AND (COLUMN_NAME = @columnname)
  168 + ) > 0,
  169 + 'SELECT 1',
  170 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` varchar(50) DEFAULT NULL COMMENT ''领取人ID'' AFTER `F_ReceiveTime`')
  171 +));
  172 +PREPARE alterIfNotExists FROM @preparedStatement;
  173 +EXECUTE alterIfNotExists;
  174 +DEALLOCATE PREPARE alterIfNotExists;
  175 +
  176 +-- ============================================
  177 +-- 5. 验证表结构
  178 +-- ============================================
  179 +-- 可以执行以下查询来验证表结构是否正确
  180 +
  181 +-- 查看 lq_inventory_usage 表结构
  182 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
  183 +-- FROM INFORMATION_SCHEMA.COLUMNS
  184 +-- WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lq_inventory_usage'
  185 +-- ORDER BY ORDINAL_POSITION;
  186 +
  187 +-- 查看 lq_inventory_usage_application 表结构
  188 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
  189 +-- FROM INFORMATION_SCHEMA.COLUMNS
  190 +-- WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lq_inventory_usage_application'
  191 +-- ORDER BY ORDINAL_POSITION;
  192 +
  193 +-- 查看 lq_inventory_usage_approval_record 表结构
  194 +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
  195 +-- FROM INFORMATION_SCHEMA.COLUMNS
  196 +-- WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lq_inventory_usage_approval_record'
  197 +-- ORDER BY ORDINAL_POSITION;
  198 +
  199 +-- ============================================
  200 +-- SQL脚本执行完成
  201 +-- ============================================
  202 +
... ...
sql/库存管理审批功能表结构.sql 0 → 100644
  1 +-- ============================================
  2 +-- 库存管理审批功能相关表结构SQL
  3 +-- ============================================
  4 +-- 说明:为库存使用记录添加审批功能,包括申请记录、审批记录等
  5 +-- 执行时间:2025年
  6 +-- 注意:本SQL只包含实际使用的表和字段,已删除未使用的节点审批人表
  7 +-- ============================================
  8 +
  9 +-- ============================================
  10 +-- 1. 修改库存使用记录表,添加单价和合计金额字段(如果不存在)
  11 +-- ============================================
  12 +-- 说明:用于统计成本费用,单价从产品表获取,合计金额 = 单价 × 数量
  13 +
  14 +-- 检查并添加单价字段
  15 +SET @dbname = DATABASE();
  16 +SET @tablename = 'lq_inventory_usage';
  17 +SET @columnname = 'F_UnitPrice';
  18 +SET @preparedStatement = (SELECT IF(
  19 + (
  20 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  21 + WHERE
  22 + (TABLE_SCHEMA = @dbname)
  23 + AND (TABLE_NAME = @tablename)
  24 + AND (COLUMN_NAME = @columnname)
  25 + ) > 0,
  26 + 'SELECT 1',
  27 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` decimal(18,2) DEFAULT 0.00 COMMENT ''单价(从产品表F_Price获取)'' AFTER `F_UsageQuantity`')
  28 +));
  29 +PREPARE alterIfNotExists FROM @preparedStatement;
  30 +EXECUTE alterIfNotExists;
  31 +DEALLOCATE PREPARE alterIfNotExists;
  32 +
  33 +-- 检查并添加合计金额字段
  34 +SET @columnname = 'F_TotalAmount';
  35 +SET @preparedStatement = (SELECT IF(
  36 + (
  37 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  38 + WHERE
  39 + (TABLE_SCHEMA = @dbname)
  40 + AND (TABLE_NAME = @tablename)
  41 + AND (COLUMN_NAME = @columnname)
  42 + ) > 0,
  43 + 'SELECT 1',
  44 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` decimal(18,2) DEFAULT 0.00 COMMENT ''合计金额(单价×数量)'' AFTER `F_UnitPrice`')
  45 +));
  46 +PREPARE alterIfNotExists FROM @preparedStatement;
  47 +EXECUTE alterIfNotExists;
  48 +DEALLOCATE PREPARE alterIfNotExists;
  49 +
  50 +-- 添加索引(如果不存在)
  51 +-- 注意:MySQL 5.7+ 不支持 IF NOT EXISTS,使用存储过程或先检查
  52 +-- 这里先检查索引是否存在,如果不存在则添加
  53 +SET @indexname = 'idx_unit_price';
  54 +SET @preparedStatement = (SELECT IF(
  55 + (
  56 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
  57 + WHERE
  58 + (TABLE_SCHEMA = @dbname)
  59 + AND (TABLE_NAME = @tablename)
  60 + AND (INDEX_NAME = @indexname)
  61 + ) > 0,
  62 + 'SELECT 1',
  63 + CONCAT('ALTER TABLE ', @tablename, ' ADD INDEX `', @indexname, '` (`F_UnitPrice`)')
  64 +));
  65 +PREPARE alterIfNotExists FROM @preparedStatement;
  66 +EXECUTE alterIfNotExists;
  67 +DEALLOCATE PREPARE alterIfNotExists;
  68 +
  69 +SET @indexname = 'idx_total_amount';
  70 +SET @preparedStatement = (SELECT IF(
  71 + (
  72 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
  73 + WHERE
  74 + (TABLE_SCHEMA = @dbname)
  75 + AND (TABLE_NAME = @tablename)
  76 + AND (INDEX_NAME = @indexname)
  77 + ) > 0,
  78 + 'SELECT 1',
  79 + CONCAT('ALTER TABLE ', @tablename, ' ADD INDEX `', @indexname, '` (`F_TotalAmount`)')
  80 +));
  81 +PREPARE alterIfNotExists FROM @preparedStatement;
  82 +EXECUTE alterIfNotExists;
  83 +DEALLOCATE PREPARE alterIfNotExists;
  84 +
  85 +-- 数据迁移:更新已有数据的单价和合计金额(从产品表获取价格)
  86 +UPDATE `lq_inventory_usage` u
  87 +INNER JOIN `lq_product` p ON u.`F_ProductId` = p.`F_Id`
  88 +SET u.`F_UnitPrice` = COALESCE(p.`F_Price`, 0),
  89 + u.`F_TotalAmount` = COALESCE(p.`F_Price`, 0) * u.`F_UsageQuantity`
  90 +WHERE (u.`F_UnitPrice` = 0 OR u.`F_UnitPrice` IS NULL) AND p.`F_Price` IS NOT NULL;
  91 +
  92 +-- ============================================
  93 +-- 2. 创建库存使用申请表(如果不存在)
  94 +-- ============================================
  95 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_application` (
  96 + `F_Id` varchar(50) NOT NULL COMMENT '申请编号',
  97 + `F_UsageBatchId` varchar(50) NOT NULL COMMENT '使用批次ID(关联lq_inventory_usage.F_UsageBatchId)',
  98 + `F_ApplicationUserId` varchar(50) NOT NULL COMMENT '申请人ID',
  99 + `F_ApplicationUserName` varchar(100) DEFAULT NULL COMMENT '申请人姓名',
  100 + `F_ApplicationStoreId` varchar(50) DEFAULT NULL COMMENT '申请门店ID',
  101 + `F_ApplicationTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间',
  102 + `F_NodeCount` int DEFAULT 1 COMMENT '节点数量(固定为1)',
  103 + `F_CurrentNodeOrder` int DEFAULT 0 COMMENT '当前审批节点(0-待审批,1-审批中,2-已完成)',
  104 + `F_CurrentNodeId` varchar(50) DEFAULT NULL COMMENT '当前节点ID',
  105 + `F_ApprovalStatus` varchar(20) DEFAULT '待审批' COMMENT '审批状态(待审批/审批中/已通过/未通过/已退回)',
  106 + `F_IsReceived` int DEFAULT 0 COMMENT '是否已领取(1-已领取,0-未领取)',
  107 + `F_ReceiveTime` datetime DEFAULT NULL COMMENT '领取时间',
  108 + `F_ReceiveUser` varchar(50) DEFAULT NULL COMMENT '领取人ID',
  109 + `F_Remarks` varchar(500) DEFAULT NULL COMMENT '备注',
  110 + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  111 + `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人ID',
  112 + `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间',
  113 + `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人ID',
  114 + `F_IsEffective` int DEFAULT 1 COMMENT '是否有效(1-有效,0-无效)',
  115 + PRIMARY KEY (`F_Id`),
  116 + UNIQUE KEY `uk_usage_batch_id` (`F_UsageBatchId`),
  117 + KEY `idx_application_user_id` (`F_ApplicationUserId`),
  118 + KEY `idx_application_store_id` (`F_ApplicationStoreId`),
  119 + KEY `idx_current_node` (`F_CurrentNodeId`),
  120 + KEY `idx_approval_status` (`F_ApprovalStatus`),
  121 + KEY `idx_is_received` (`F_IsReceived`),
  122 + KEY `idx_create_time` (`F_CreateTime`)
  123 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存使用申请表';
  124 +
  125 +-- ============================================
  126 +-- 3. 创建库存使用申请审批记录表(如果不存在)
  127 +-- ============================================
  128 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_approval_record` (
  129 + `F_Id` varchar(50) NOT NULL COMMENT '记录编号',
  130 + `F_ApplicationId` varchar(50) NOT NULL COMMENT '申请ID',
  131 + `F_NodeId` varchar(50) NOT NULL COMMENT '节点编号',
  132 + `F_NodeOrder` int NOT NULL DEFAULT 1 COMMENT '节点顺序(固定为1)',
  133 + `F_ApproverId` varchar(50) NOT NULL COMMENT '审批人ID',
  134 + `F_ApproverName` varchar(100) DEFAULT NULL COMMENT '审批人姓名',
  135 + `F_ApprovalResult` varchar(20) NOT NULL COMMENT '审批结果(通过/不通过/退回)',
  136 + `F_ApprovalOpinion` varchar(500) DEFAULT NULL COMMENT '审批意见',
  137 + `F_ApprovalTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '审批时间',
  138 + `F_IsCurrentNode` int DEFAULT 0 COMMENT '是否当前节点(1-是,0-否)',
  139 + PRIMARY KEY (`F_Id`),
  140 + KEY `idx_application_id` (`F_ApplicationId`),
  141 + KEY `idx_node_id` (`F_NodeId`),
  142 + KEY `idx_approver_id` (`F_ApproverId`),
  143 + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`),
  144 + KEY `idx_approval_result` (`F_ApprovalResult`),
  145 + KEY `idx_approval_time` (`F_ApprovalTime`)
  146 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存使用申请审批记录表';
  147 +
  148 +-- ============================================
  149 +-- 4. 检查并添加可能缺失的字段(兼容性处理)
  150 +-- ============================================
  151 +
  152 +-- 检查 lq_inventory_usage_application 表是否有所需字段
  153 +-- 如果表已存在但字段缺失,则添加字段
  154 +
  155 +-- 检查并添加 F_IsReceived 字段(如果不存在)
  156 +SET @tablename = 'lq_inventory_usage_application';
  157 +SET @columnname = 'F_IsReceived';
  158 +SET @preparedStatement = (SELECT IF(
  159 + (
  160 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  161 + WHERE
  162 + (TABLE_SCHEMA = @dbname)
  163 + AND (TABLE_NAME = @tablename)
  164 + AND (COLUMN_NAME = @columnname)
  165 + ) > 0,
  166 + 'SELECT 1',
  167 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` int DEFAULT 0 COMMENT ''是否已领取(1-已领取,0-未领取)'' AFTER `F_ApprovalStatus`')
  168 +));
  169 +PREPARE alterIfNotExists FROM @preparedStatement;
  170 +EXECUTE alterIfNotExists;
  171 +DEALLOCATE PREPARE alterIfNotExists;
  172 +
  173 +-- 检查并添加 F_ReceiveTime 字段(如果不存在)
  174 +SET @columnname = 'F_ReceiveTime';
  175 +SET @preparedStatement = (SELECT IF(
  176 + (
  177 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  178 + WHERE
  179 + (TABLE_SCHEMA = @dbname)
  180 + AND (TABLE_NAME = @tablename)
  181 + AND (COLUMN_NAME = @columnname)
  182 + ) > 0,
  183 + 'SELECT 1',
  184 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` datetime DEFAULT NULL COMMENT ''领取时间'' AFTER `F_IsReceived`')
  185 +));
  186 +PREPARE alterIfNotExists FROM @preparedStatement;
  187 +EXECUTE alterIfNotExists;
  188 +DEALLOCATE PREPARE alterIfNotExists;
  189 +
  190 +-- 检查并添加 F_ReceiveUser 字段(如果不存在)
  191 +SET @columnname = 'F_ReceiveUser';
  192 +SET @preparedStatement = (SELECT IF(
  193 + (
  194 + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  195 + WHERE
  196 + (TABLE_SCHEMA = @dbname)
  197 + AND (TABLE_NAME = @tablename)
  198 + AND (COLUMN_NAME = @columnname)
  199 + ) > 0,
  200 + 'SELECT 1',
  201 + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` varchar(50) DEFAULT NULL COMMENT ''领取人ID'' AFTER `F_ReceiveTime`')
  202 +));
  203 +PREPARE alterIfNotExists FROM @preparedStatement;
  204 +EXECUTE alterIfNotExists;
  205 +DEALLOCATE PREPARE alterIfNotExists;
  206 +
  207 +-- ============================================
  208 +-- SQL脚本执行完成
  209 +-- ============================================
  210 +-- 说明:
  211 +-- 1. lq_inventory_usage 表:添加了 F_UnitPrice 和 F_TotalAmount 字段
  212 +-- 2. lq_inventory_usage_application 表:创建申请记录表
  213 +-- 3. lq_inventory_usage_approval_record 表:创建审批记录表
  214 +-- 4. 注意:未使用节点审批人表(lq_inventory_usage_application_node_user),已删除相关实体
... ...
合同管理系统-前端调用说明.md 0 → 100644
  1 +# 合同管理系统 - 前端调用说明
  2 +
  3 +## 📋 目录
  4 +- [业务流程概述](#业务流程概述)
  5 +- [接口列表](#接口列表)
  6 +- [完整流程示例](#完整流程示例)
  7 +- [接口详细说明](#接口详细说明)
  8 +- [数据验证说明](#数据验证说明)
  9 +- [注意事项](#注意事项)
  10 +
  11 +## 🔄 业务流程概述
  12 +
  13 +合同管理系统包含以下核心功能:
  14 +
  15 +1. **创建合同** - 录入合同信息,系统自动生成月租明细
  16 +2. **查询合同** - 支持列表查询和详情查询
  17 +3. **更新合同** - 修改合同信息,如果影响明细会自动重新生成
  18 +4. **标记缴费** - 标记月租明细已缴费,系统自动更新下次应交时间
  19 +5. **删除合同** - 删除合同及所有关联明细(逻辑删除)
  20 +
  21 +## 📡 接口列表
  22 +
  23 +| 接口 | 方法 | 路径 | 说明 |
  24 +|------|------|------|------|
  25 +| 创建合同 | POST | `/api/Extend/LqContract/Create` | 创建合同并自动生成月租明细 |
  26 +| 更新合同 | PUT | `/api/Extend/LqContract/Update` | 更新合同信息,影响明细时自动重新生成 |
  27 +| 删除合同 | DELETE | `/api/Extend/LqContract/{id}` | 删除合同及所有明细(逻辑删除) |
  28 +| 获取合同列表 | GET | `/api/Extend/LqContract/GetList` | 分页查询合同列表,支持多条件筛选 |
  29 +| 获取合同详情 | GET | `/api/Extend/LqContract/GetInfo` | 根据合同ID获取详情,包含月租明细列表 |
  30 +| 获取月租明细列表 | GET | `/api/Extend/LqContract/GetRentDetails` | 根据合同ID获取所有月租明细 |
  31 +| 标记明细已缴费 | PUT | `/api/Extend/LqContract/MarkRentDetailPaid` | 标记某条明细已缴费,记录实际缴费信息 |
  32 +| 统计门店合同费用 | POST | `/api/Extend/LqContract/GetExpenseStatistics` | 统计某个门店指定月份的合同费用,支持按分类统计 |
  33 +
  34 +## 🚀 完整流程示例
  35 +
  36 +### 步骤1:创建合同
  37 +
  38 +**接口:** `POST /api/Extend/LqContract/Create`
  39 +
  40 +**请求示例:**
  41 +```javascript
  42 +const response = await request({
  43 + url: '/api/Extend/LqContract/Create',
  44 + method: 'POST',
  45 + data: {
  46 + storeId: '1649328471923847168', // 门店ID(必填)
  47 + title: '门店租赁合同', // 标题(必填)
  48 + category: '租赁合同', // 分类(可选)
  49 + tenantName: '张三', // 户名(可选)
  50 + contractStartDate: '2025-01-01T00:00:00', // 合同起始日期(必填)
  51 + contractEndDate: '2025-12-31T23:59:59', // 合同结束日期(必填)
  52 + reminderDays: 7, // 提前提醒天数(可选,默认0)
  53 + deposit: 5000.00, // 押金(可选,默认0)
  54 + monthlyRent: 1000.00, // 月租(必填)
  55 + paymentAmount: 3000.00, // 缴租金额(必填,通常=月租×交租周期)
  56 + paymentCycle: 3, // 交租周期(必填,单位:月,如1、3、6等)
  57 + remarks: '季度交租', // 备注(可选)
  58 + attachment: '' // 附件(可选)
  59 + }
  60 +});
  61 +
  62 +// 响应示例
  63 +// {
  64 +// "code": 200,
  65 +// "msg": "操作成功",
  66 +// "data": null
  67 +// }
  68 +```
  69 +
  70 +**说明:**
  71 +- 系统会自动根据合同信息生成月租明细
  72 +- 生成规则:从合同起始日期开始,每隔 `paymentCycle` 个月生成一条明细
  73 +- 应缴月份为每次交租对应的月份(格式:YYYY-MM-01)
  74 +- 应缴日期为应缴月份的第一天
  75 +- 应缴金额等于 `paymentAmount`
  76 +- 系统会自动计算下次应交时间(最早未缴费明细的应缴日期 - 提前提醒天数)
  77 +
  78 +**示例:**
  79 +- 合同起始:2025-01-01
  80 +- 合同结束:2025-12-31
  81 +- 交租周期:3个月
  82 +- 缴租金额:3000元
  83 +
  84 +生成的明细:
  85 +- 2025-01-01,应缴金额:3000元(1-3月)
  86 +- 2025-04-01,应缴金额:3000元(4-6月)
  87 +- 2025-07-01,应缴金额:3000元(7-9月)
  88 +- 2025-10-01,应缴金额:3000元(10-12月)
  89 +
  90 +### 步骤2:查询合同列表
  91 +
  92 +**接口:** `GET /api/Extend/LqContract/GetList`
  93 +
  94 +**请求示例:**
  95 +```javascript
  96 +const response = await request({
  97 + url: '/api/Extend/LqContract/GetList',
  98 + method: 'GET',
  99 + params: {
  100 + currentPage: 1, // 当前页码(必填)
  101 + pageSize: 10, // 每页数量(必填)
  102 + sidx: 'createTime', // 排序字段(可选,默认createTime)
  103 + sort: 'desc', // 排序方式(可选,默认desc)
  104 + storeId: '1649328471923847168', // 门店ID(可选)
  105 + storeName: '绿纤总部', // 门店名称(可选,模糊查询)
  106 + category: '租赁合同', // 分类(可选)
  107 + title: '合同', // 标题(可选,模糊查询)
  108 + contractStartDateBegin: '2025-01-01', // 合同起始日期(开始)(可选)
  109 + contractStartDateEnd: '2025-12-31', // 合同起始日期(结束)(可选)
  110 + contractEndDateBegin: '2025-01-01', // 合同结束日期(开始)(可选)
  111 + contractEndDateEnd: '2025-12-31', // 合同结束日期(结束)(可选)
  112 + isEffective: 1 // 是否有效(可选,1-有效,0-无效)
  113 + }
  114 +});
  115 +
  116 +// 响应示例
  117 +// {
  118 +// "code": 200,
  119 +// "msg": "操作成功",
  120 +// "data": {
  121 +// "pagination": {
  122 +// "pageIndex": 1,
  123 +// "pageSize": 10,
  124 +// "total": 1
  125 +// },
  126 +// "list": [
  127 +// {
  128 +// "id": "768081482374710533",
  129 +// "storeId": "1649328471923847168",
  130 +// "storeName": "绿纤总部",
  131 +// "title": "门店租赁合同",
  132 +// "category": "租赁合同",
  133 +// "tenantName": "张三",
  134 +// "contractStartDate": 1735660800000,
  135 +// "contractEndDate": 1767196799000,
  136 +// "reminderDays": 7,
  137 +// "deposit": 5000.00,
  138 +// "nextPaymentDate": 1735056000000,
  139 +// "monthlyRent": 1000.00,
  140 +// "paymentAmount": 3000.00,
  141 +// "paymentCycle": 3,
  142 +// "remarks": "季度交租",
  143 +// "attachment": "",
  144 +// "createUser": "admin",
  145 +// "createUserName": "管理员",
  146 +// "createTime": 1765290098000,
  147 +// "updateUser": "admin",
  148 +// "updateUserName": "管理员",
  149 +// "updateTime": 1765290098000,
  150 +// "isEffective": 1
  151 +// }
  152 +// ]
  153 +// }
  154 +// }
  155 +```
  156 +
  157 +### 步骤3:查询合同详情
  158 +
  159 +**接口:** `GET /api/Extend/LqContract/GetInfo`
  160 +
  161 +**请求示例:**
  162 +```javascript
  163 +const response = await request({
  164 + url: '/api/Extend/LqContract/GetInfo',
  165 + method: 'GET',
  166 + params: {
  167 + id: '768081482374710533' // 合同ID(必填)
  168 + }
  169 +});
  170 +
  171 +// 响应示例
  172 +// {
  173 +// "code": 200,
  174 +// "msg": "操作成功",
  175 +// "data": {
  176 +// "id": "768081482374710533",
  177 +// "storeId": "1649328471923847168",
  178 +// "storeName": "绿纤总部",
  179 +// "title": "门店租赁合同",
  180 +// "category": "租赁合同",
  181 +// "tenantName": "张三",
  182 +// "contractStartDate": 1735660800000,
  183 +// "contractEndDate": 1767196799000,
  184 +// "reminderDays": 7,
  185 +// "deposit": 5000.00,
  186 +// "nextPaymentDate": 1735056000000,
  187 +// "monthlyRent": 1000.00,
  188 +// "paymentAmount": 3000.00,
  189 +// "paymentCycle": 3,
  190 +// "remarks": "季度交租",
  191 +// "attachment": "",
  192 +// "createUser": "admin",
  193 +// "createUserName": "管理员",
  194 +// "createTime": 1765290098000,
  195 +// "updateUser": "admin",
  196 +// "updateUserName": "管理员",
  197 +// "updateTime": 1765290098000,
  198 +// "isEffective": 1,
  199 +// "rentDetails": [
  200 +// {
  201 +// "id": "768081482571842821",
  202 +// "contractId": "768081482374710533",
  203 +// "paymentMonth": 1735660800000,
  204 +// "dueDate": 1735660800000,
  205 +// "dueAmount": 3000.00,
  206 +// "isPaid": 0,
  207 +// "actualPaymentDate": null,
  208 +// "actualPaymentAmount": null,
  209 +// "remarks": null,
  210 +// "createUser": "admin",
  211 +// "createUserName": "管理员",
  212 +// "createTime": 1765290098000,
  213 +// "updateUser": null,
  214 +// "updateUserName": "",
  215 +// "updateTime": null,
  216 +// "isEffective": 1
  217 +// }
  218 +// // ... 更多明细
  219 +// ]
  220 +// }
  221 +// }
  222 +```
  223 +
  224 +### 步骤4:查询月租明细列表
  225 +
  226 +**接口:** `GET /api/Extend/LqContract/GetRentDetails`
  227 +
  228 +**请求示例:**
  229 +```javascript
  230 +const response = await request({
  231 + url: '/api/Extend/LqContract/GetRentDetails',
  232 + method: 'GET',
  233 + params: {
  234 + contractId: '768081482374710533' // 合同ID(必填)
  235 + }
  236 +});
  237 +
  238 +// 响应示例
  239 +// {
  240 +// "code": 200,
  241 +// "msg": "操作成功",
  242 +// "data": [
  243 +// {
  244 +// "id": "768081482571842821",
  245 +// "contractId": "768081482374710533",
  246 +// "paymentMonth": 1735660800000,
  247 +// "dueDate": 1735660800000,
  248 +// "dueAmount": 3000.00,
  249 +// "isPaid": 0,
  250 +// "actualPaymentDate": null,
  251 +// "actualPaymentAmount": null,
  252 +// "remarks": null,
  253 +// "createUser": "admin",
  254 +// "createUserName": "管理员",
  255 +// "createTime": 1765290098000,
  256 +// "updateUser": null,
  257 +// "updateUserName": "",
  258 +// "updateTime": null,
  259 +// "isEffective": 1
  260 +// }
  261 +// // ... 更多明细
  262 +// ]
  263 +// }
  264 +```
  265 +
  266 +### 步骤5:标记明细已缴费
  267 +
  268 +**接口:** `PUT /api/Extend/LqContract/MarkRentDetailPaid`
  269 +
  270 +**请求示例:**
  271 +```javascript
  272 +const response = await request({
  273 + url: '/api/Extend/LqContract/MarkRentDetailPaid',
  274 + method: 'PUT',
  275 + data: {
  276 + id: '768081482571842821', // 明细ID(必填)
  277 + actualPaymentDate: '2025-01-15T00:00:00', // 实际缴费时间(必填)
  278 + actualPaymentAmount: 3000.00, // 实际缴费金额(必填)
  279 + remarks: '已缴费' // 备注(可选)
  280 + }
  281 +});
  282 +
  283 +// 响应示例
  284 +// {
  285 +// "code": 200,
  286 +// "msg": "操作成功",
  287 +// "data": null
  288 +// }
  289 +```
  290 +
  291 +**说明:**
  292 +- 标记成功后,系统会自动更新该明细的 `isPaid` 为 1
  293 +- 系统会自动重新计算合同的下次应交时间(找到最早未缴费明细,计算:应缴日期 - 提前提醒天数)
  294 +
  295 +### 步骤6:更新合同
  296 +
  297 +**接口:** `PUT /api/Extend/LqContract/Update`
  298 +
  299 +**请求示例:**
  300 +```javascript
  301 +const response = await request({
  302 + url: '/api/Extend/LqContract/Update',
  303 + method: 'PUT',
  304 + data: {
  305 + id: '768081482374710533', // 合同ID(必填)
  306 + storeId: '1649328471923847168', // 门店ID(必填)
  307 + title: '门店租赁合同-已更新', // 标题(必填)
  308 + category: '租赁合同', // 分类(可选)
  309 + tenantName: '张三', // 户名(可选)
  310 + contractStartDate: '2025-01-01T00:00:00', // 合同起始日期(必填)
  311 + contractEndDate: '2025-12-31T23:59:59', // 合同结束日期(必填)
  312 + reminderDays: 7, // 提前提醒天数(可选)
  313 + deposit: 5000.00, // 押金(可选)
  314 + monthlyRent: 1000.00, // 月租(必填)
  315 + paymentAmount: 6000.00, // 缴租金额(必填)
  316 + paymentCycle: 6, // 交租周期(必填)
  317 + remarks: '半年交租', // 备注(可选)
  318 + attachment: '' // 附件(可选)
  319 + }
  320 +});
  321 +
  322 +// 响应示例
  323 +// {
  324 +// "code": 200,
  325 +// "msg": "操作成功",
  326 +// "data": null
  327 +// }
  328 +```
  329 +
  330 +**说明:**
  331 +- 如果修改了以下字段,系统会重新生成月租明细:
  332 + - `contractStartDate`(合同起始日期)
  333 + - `contractEndDate`(合同结束日期)
  334 + - `paymentCycle`(交租周期)
  335 + - `paymentAmount`(缴租金额)
  336 +- 重新生成时,会先逻辑删除所有旧明细,然后生成新明细
  337 +- 系统会自动重新计算下次应交时间
  338 +
  339 +### 步骤7:删除合同
  340 +
  341 +**接口:** `DELETE /api/Extend/LqContract/{id}`
  342 +
  343 +**请求示例:**
  344 +```javascript
  345 +const response = await request({
  346 + url: '/api/Extend/LqContract/768081482374710533',
  347 + method: 'DELETE'
  348 +});
  349 +
  350 +// 响应示例
  351 +// {
  352 +// "code": 200,
  353 +// "msg": "操作成功",
  354 +// "data": null
  355 +// }
  356 +```
  357 +
  358 +**说明:**
  359 +- 删除合同会同时逻辑删除该合同的所有月租明细
  360 +- 删除后,合同和明细的 `isEffective` 字段会被设置为 0
  361 +- 删除后的合同在列表查询中不会显示(默认只查询有效的合同)
  362 +
  363 +## 📝 接口详细说明
  364 +
  365 +### 1. 创建合同
  366 +
  367 +**接口地址:** `POST /api/Extend/LqContract/Create`
  368 +
  369 +**请求参数:**
  370 +
  371 +| 参数名 | 类型 | 必填 | 说明 |
  372 +|--------|------|------|------|
  373 +| storeId | string | ✅ | 门店ID(关联lq_mdxx.F_Id) |
  374 +| title | string | ✅ | 标题 |
  375 +| category | string | ❌ | 分类(string类型,自己填写) |
  376 +| tenantName | string | ❌ | 户名 |
  377 +| contractStartDate | datetime | ✅ | 合同起始日期 |
  378 +| contractEndDate | datetime | ✅ | 合同结束日期 |
  379 +| reminderDays | int | ❌ | 提前多少天提醒(默认0) |
  380 +| deposit | decimal | ❌ | 押金(默认0) |
  381 +| monthlyRent | decimal | ✅ | 月租 |
  382 +| paymentAmount | decimal | ✅ | 缴租金额(每次交租的金额,通常=月租×交租周期) |
  383 +| paymentCycle | int | ✅ | 交租周期(数字,表示几个月,如1、3、6等,范围1-12) |
  384 +| remarks | string | ❌ | 备注 |
  385 +| attachment | string | ❌ | 附件(存储附件路径或JSON) |
  386 +
  387 +**响应说明:**
  388 +- 成功:`{"code": 200, "msg": "操作成功", "data": null}`
  389 +- 失败:返回错误信息
  390 +
  391 +**业务逻辑:**
  392 +1. 验证合同日期(起始日期必须小于结束日期)
  393 +2. 验证交租周期(必须在1-12个月之间)
  394 +3. 查询门店信息(获取店名)
  395 +4. 创建合同记录
  396 +5. 自动生成月租明细(根据合同起始日期、结束日期、交租周期、缴租金额)
  397 +6. 计算下次应交时间(最早未缴费明细的应缴日期 - 提前提醒天数)
  398 +
  399 +### 2. 更新合同
  400 +
  401 +**接口地址:** `PUT /api/Extend/LqContract/Update`
  402 +
  403 +**请求参数:**
  404 +
  405 +| 参数名 | 类型 | 必填 | 说明 |
  406 +|--------|------|------|------|
  407 +| id | string | ✅ | 合同ID |
  408 +| storeId | string | ✅ | 门店ID |
  409 +| title | string | ✅ | 标题 |
  410 +| category | string | ❌ | 分类 |
  411 +| tenantName | string | ❌ | 户名 |
  412 +| contractStartDate | datetime | ✅ | 合同起始日期 |
  413 +| contractEndDate | datetime | ✅ | 合同结束日期 |
  414 +| reminderDays | int | ❌ | 提前提醒天数 |
  415 +| deposit | decimal | ❌ | 押金 |
  416 +| monthlyRent | decimal | ✅ | 月租 |
  417 +| paymentAmount | decimal | ✅ | 缴租金额 |
  418 +| paymentCycle | int | ✅ | 交租周期 |
  419 +| remarks | string | ❌ | 备注 |
  420 +| attachment | string | ❌ | 附件 |
  421 +
  422 +**响应说明:**
  423 +- 成功:`{"code": 200, "msg": "操作成功", "data": null}`
  424 +- 失败:返回错误信息
  425 +
  426 +**业务逻辑:**
  427 +1. 验证合同是否存在
  428 +2. 验证合同日期和交租周期
  429 +3. 判断是否需要重新生成明细(如果修改了合同起始日期、结束日期、交租周期或缴租金额)
  430 +4. 如果需要重新生成,先逻辑删除旧明细,再生成新明细
  431 +5. 重新计算下次应交时间
  432 +
  433 +### 3. 删除合同
  434 +
  435 +**接口地址:** `DELETE /api/Extend/LqContract/{id}`
  436 +
  437 +**路径参数:**
  438 +
  439 +| 参数名 | 类型 | 必填 | 说明 |
  440 +|--------|------|------|------|
  441 +| id | string | ✅ | 合同ID |
  442 +
  443 +**响应说明:**
  444 +- 成功:`{"code": 200, "msg": "操作成功", "data": null}`
  445 +- 失败:返回错误信息
  446 +
  447 +**业务逻辑:**
  448 +1. 验证合同是否存在
  449 +2. 逻辑删除该合同的所有月租明细(设置 `isEffective = 0`)
  450 +3. 逻辑删除合同(设置 `isEffective = 0`)
  451 +
  452 +### 4. 获取合同列表
  453 +
  454 +**接口地址:** `GET /api/Extend/LqContract/GetList`
  455 +
  456 +**请求参数:**
  457 +
  458 +| 参数名 | 类型 | 必填 | 说明 |
  459 +|--------|------|------|------|
  460 +| currentPage | int | ✅ | 当前页码 |
  461 +| pageSize | int | ✅ | 每页数量 |
  462 +| sidx | string | ❌ | 排序字段(默认createTime) |
  463 +| sort | string | ❌ | 排序方式(默认desc) |
  464 +| storeId | string | ❌ | 门店ID |
  465 +| storeName | string | ❌ | 门店名称(模糊查询) |
  466 +| category | string | ❌ | 分类 |
  467 +| title | string | ❌ | 标题(模糊查询) |
  468 +| contractStartDateBegin | datetime | ❌ | 合同起始日期(开始) |
  469 +| contractStartDateEnd | datetime | ❌ | 合同起始日期(结束) |
  470 +| contractEndDateBegin | datetime | ❌ | 合同结束日期(开始) |
  471 +| contractEndDateEnd | datetime | ❌ | 合同结束日期(结束) |
  472 +| isEffective | int | ❌ | 是否有效(1-有效,0-无效) |
  473 +
  474 +**响应说明:**
  475 +- 成功:返回分页数据,包含合同列表和分页信息
  476 +- 失败:返回错误信息
  477 +
  478 +### 5. 获取合同详情
  479 +
  480 +**接口地址:** `GET /api/Extend/LqContract/GetInfo`
  481 +
  482 +**请求参数:**
  483 +
  484 +| 参数名 | 类型 | 必填 | 说明 |
  485 +|--------|------|------|------|
  486 +| id | string | ✅ | 合同ID |
  487 +
  488 +**响应说明:**
  489 +- 成功:返回合同详情,包含月租明细列表
  490 +- 失败:返回错误信息
  491 +
  492 +### 6. 获取月租明细列表
  493 +
  494 +**接口地址:** `GET /api/Extend/LqContract/GetRentDetails`
  495 +
  496 +**请求参数:**
  497 +
  498 +| 参数名 | 类型 | 必填 | 说明 |
  499 +|--------|------|------|------|
  500 +| contractId | string | ✅ | 合同ID |
  501 +
  502 +**响应说明:**
  503 +- 成功:返回月租明细数组
  504 +- 失败:返回错误信息
  505 +
  506 +### 7. 标记明细已缴费
  507 +
  508 +**接口地址:** `PUT /api/Extend/LqContract/MarkRentDetailPaid`
  509 +
  510 +**请求参数:**
  511 +
  512 +| 参数名 | 类型 | 必填 | 说明 |
  513 +|--------|------|------|------|
  514 +| id | string | ✅ | 明细ID |
  515 +| actualPaymentDate | datetime | ✅ | 实际缴费时间 |
  516 +| actualPaymentAmount | decimal | ✅ | 实际缴费金额 |
  517 +| remarks | string | ❌ | 备注 |
  518 +
  519 +**响应说明:**
  520 +- 成功:`{"code": 200, "msg": "操作成功", "data": null}`
  521 +- 失败:返回错误信息
  522 +
  523 +**业务逻辑:**
  524 +1. 验证明细是否存在
  525 +2. 验证明细是否已缴费(已缴费的不能重复标记)
  526 +3. 更新明细的缴费信息
  527 +4. 重新计算合同的下次应交时间
  528 +
  529 +### 8. 统计门店合同费用
  530 +
  531 +**接口地址:** `POST /api/Extend/LqContract/GetExpenseStatistics`
  532 +
  533 +**请求参数:**
  534 +
  535 +| 参数名 | 类型 | 必填 | 说明 |
  536 +|--------|------|------|------|
  537 +| storeId | string | ✅ | 门店ID |
  538 +| year | int | ✅ | 统计年份(2020-2100) |
  539 +| month | int | ✅ | 统计月份(1-12) |
  540 +| categories | string[] | ❌ | 分类列表(可选,不传则统计所有分类,如:["租门店", "员工宿舍", "车辆", "场所"]) |
  541 +
  542 +**响应说明:**
  543 +- 成功:返回统计结果,包含总费用和按分类的费用明细
  544 +- 失败:返回错误信息
  545 +
  546 +**业务逻辑:**
  547 +1. 验证月份范围(1-12)
  548 +2. 查询门店信息
  549 +3. 查询该门店在指定月份的月租明细(关联合同表获取分类信息)
  550 +4. 如果指定了分类,则只统计指定分类的明细
  551 +5. 按分类分组统计,计算每个分类的费用总额
  552 +6. 返回总费用和按分类的费用明细
  553 +
  554 +**响应数据结构:**
  555 +- `storeId`: 门店ID
  556 +- `storeName`: 门店名称
  557 +- `year`: 统计年份
  558 +- `month`: 统计月份
  559 +- `totalAmount`: 总费用(所有分类的费用总和)
  560 +- `categoryDetails`: 按分类统计的费用明细数组
  561 + - `category`: 分类名称
  562 + - `amount`: 该分类的费用总额
  563 + - `detailCount`: 该分类的明细数量
  564 + - `details`: 明细列表
  565 + - `detailId`: 明细ID
  566 + - `contractId`: 合同ID
  567 + - `contractTitle`: 合同标题
  568 + - `category`: 分类
  569 + - `dueAmount`: 应缴金额
  570 + - `isPaid`: 是否已缴(0-未缴,1-已缴)
  571 + - `actualPaymentAmount`: 实际缴费金额(如果已缴费)
  572 +
  573 +**使用示例:**
  574 +```javascript
  575 +// 统计某个门店2025年1月的所有合同费用
  576 +const response = await request({
  577 + url: '/api/Extend/LqContract/GetExpenseStatistics',
  578 + method: 'POST',
  579 + data: {
  580 + storeId: '1649328471923847168',
  581 + year: 2025,
  582 + month: 1
  583 + }
  584 +});
  585 +
  586 +// 只统计特定分类的费用(租门店、员工宿舍、车辆、场所)
  587 +const response2 = await request({
  588 + url: '/api/Extend/LqContract/GetExpenseStatistics',
  589 + method: 'POST',
  590 + data: {
  591 + storeId: '1649328471923847168',
  592 + year: 2025,
  593 + month: 1,
  594 + categories: ['租门店', '员工宿舍', '车辆', '场所']
  595 + }
  596 +});
  597 +```
  598 +
  599 +## 🔍 数据验证说明
  600 +
  601 +### 月租明细生成规则
  602 +
  603 +1. **生成时机:**
  604 + - 创建合同时自动生成
  605 + - 更新合同时,如果修改了影响明细的字段,会重新生成
  606 +
  607 +2. **生成规则:**
  608 + - 从合同起始日期开始,每隔 `paymentCycle` 个月生成一条明细
  609 + - 应缴月份:每次交租对应的月份(格式:YYYY-MM-01)
  610 + - 应缴日期:应缴月份的第一天
  611 + - 应缴金额:等于 `paymentAmount`
  612 + - 生成到合同结束日期为止
  613 +
  614 +3. **示例:**
  615 + - 合同起始:2025-01-01
  616 + - 合同结束:2025-12-31
  617 + - 交租周期:3个月
  618 + - 缴租金额:3000元
  619 +
  620 + 生成的明细:
  621 + - 2025-01-01,应缴金额:3000元
  622 + - 2025-04-01,应缴金额:3000元
  623 + - 2025-07-01,应缴金额:3000元
  624 + - 2025-10-01,应缴金额:3000元
  625 +
  626 +### 下次应交时间计算规则
  627 +
  628 +1. **计算时机:**
  629 + - 创建合同时自动计算
  630 + - 更新合同时自动重新计算
  631 + - 标记明细已缴费后自动重新计算
  632 +
  633 +2. **计算规则:**
  634 + - 查找该合同未缴费的明细中,应缴日期最早的一条
  635 + - 下次应交时间 = 最早应缴日期 - 提前提醒天数(`reminderDays`)
  636 + - 如果没有未缴费的明细,下次应交时间为 `null`
  637 +
  638 +3. **示例:**
  639 + - 最早未缴费明细的应缴日期:2025-01-01
  640 + - 提前提醒天数:7天
  641 + - 下次应交时间:2024-12-25(2025-01-01 - 7天)
  642 +
  643 +## ⚠️ 注意事项
  644 +
  645 +1. **日期格式:**
  646 + - 所有日期字段使用 ISO 8601 格式:`YYYY-MM-DDTHH:mm:ss`
  647 + - 时间戳字段返回的是毫秒级时间戳(需要除以1000转换为秒)
  648 +
  649 +2. **金额精度:**
  650 + - 所有金额字段保留2位小数
  651 + - 前端显示时注意格式化
  652 +
  653 +3. **删除操作:**
  654 + - 删除是逻辑删除,不会物理删除数据
  655 + - 删除后的合同在列表查询中默认不显示(`isEffective = 0`)
  656 + - 如果需要查询已删除的合同,可以设置 `isEffective = 0`
  657 +
  658 +4. **明细重新生成:**
  659 + - 更新合同时,如果修改了合同起始日期、结束日期、交租周期或缴租金额,会重新生成明细
  660 + - 重新生成时,旧明细会被逻辑删除,新明细会重新生成
  661 + - 如果旧明细中有已缴费的记录,重新生成后这些记录也会被删除
  662 +
  663 +5. **数据一致性:**
  664 + - 系统会自动维护合同和明细的一致性
  665 + - 删除合同时会自动删除所有明细
  666 + - 更新合同时会自动处理明细的重新生成
  667 +
  668 +6. **错误处理:**
  669 + - 所有接口都会返回标准的响应格式:`{"code": 200, "msg": "操作成功", "data": ...}`
  670 + - 如果发生错误,`code` 不为 200,`msg` 包含错误信息
  671 + - 前端需要根据 `code` 判断操作是否成功
  672 +
  673 +7. **权限控制:**
  674 + - 所有接口都需要在请求头中携带 `Authorization: Bearer {token}`
  675 + - Token 需要通过登录接口获取
  676 +
  677 +8. **分页查询:**
  678 + - 列表查询接口支持分页
  679 + - 默认按创建时间倒序排列
  680 + - 可以通过 `sidx` 和 `sort` 参数自定义排序
  681 +
  682 +### 8. 统计门店合同费用
  683 +
  684 +**接口:** `POST /api/Extend/LqContract/GetExpenseStatistics`
  685 +
  686 +**请求示例:**
  687 +```javascript
  688 +const response = await request({
  689 + url: '/api/Extend/LqContract/GetExpenseStatistics',
  690 + method: 'POST',
  691 + data: {
  692 + storeId: '1649328471923847168', // 门店ID(必填)
  693 + year: 2025, // 统计年份(必填)
  694 + month: 1, // 统计月份(必填,1-12)
  695 + categories: ['租门店', '员工宿舍', '车辆', '场所'] // 分类列表(可选,不传则统计所有分类)
  696 + }
  697 +});
  698 +
  699 +// 响应示例
  700 +// {
  701 +// "code": 200,
  702 +// "msg": "操作成功",
  703 +// "data": {
  704 +// "storeId": "1649328471923847168",
  705 +// "storeName": "绿纤总部",
  706 +// "year": 2025,
  707 +// "month": 1,
  708 +// "totalAmount": 6000.00,
  709 +// "categoryDetails": [
  710 +// {
  711 +// "category": "租赁合同",
  712 +// "amount": 6000.00,
  713 +// "detailCount": 2,
  714 +// "details": [
  715 +// {
  716 +// "detailId": "768081482571842821",
  717 +// "contractId": "768081482374710533",
  718 +// "contractTitle": "门店租赁合同",
  719 +// "category": "租赁合同",
  720 +// "dueAmount": 3000.00,
  721 +// "isPaid": 0,
  722 +// "actualPaymentAmount": null
  723 +// }
  724 +// ]
  725 +// }
  726 +// ]
  727 +// }
  728 +// }
  729 +```
  730 +
  731 +**说明:**
  732 +- 统计该门店在指定月份的合同费用
  733 +- 支持按分类筛选(如:租门店、员工宿舍、车辆、场所等)
  734 +- 返回总费用和按分类的费用明细
  735 +- 每个分类包含该分类的费用总额、明细数量和明细列表
  736 +
  737 +**使用场景:**
  738 +- 统计某个门店这个月的房租开销
  739 +- 按分类查看不同类型的合同费用
  740 +- 生成门店成本费用报表
  741 +
  742 +## 📊 数据字段说明
  743 +
  744 +### 合同字段
  745 +
  746 +| 字段名 | 类型 | 说明 |
  747 +|--------|------|------|
  748 +| id | string | 合同ID |
  749 +| storeId | string | 门店ID |
  750 +| storeName | string | 店名(冗余字段) |
  751 +| title | string | 标题 |
  752 +| category | string | 分类 |
  753 +| tenantName | string | 户名 |
  754 +| contractStartDate | long | 合同起始日期(时间戳,毫秒) |
  755 +| contractEndDate | long | 合同结束日期(时间戳,毫秒) |
  756 +| reminderDays | int | 提前提醒天数 |
  757 +| deposit | decimal | 押金 |
  758 +| nextPaymentDate | long | 下次应交时间(时间戳,毫秒,可能为null) |
  759 +| monthlyRent | decimal | 月租 |
  760 +| paymentAmount | decimal | 缴租金额 |
  761 +| paymentCycle | int | 交租周期(月) |
  762 +| remarks | string | 备注 |
  763 +| attachment | string | 附件 |
  764 +| createUser | string | 创建人ID |
  765 +| createUserName | string | 创建人姓名 |
  766 +| createTime | long | 创建时间(时间戳,毫秒) |
  767 +| updateUser | string | 更新人ID |
  768 +| updateUserName | string | 更新人姓名 |
  769 +| updateTime | long | 更新时间(时间戳,毫秒,可能为null) |
  770 +| isEffective | int | 是否有效(1-有效,0-无效) |
  771 +
  772 +### 月租明细字段
  773 +
  774 +| 字段名 | 类型 | 说明 |
  775 +|--------|------|------|
  776 +| id | string | 明细ID |
  777 +| contractId | string | 合同ID |
  778 +| paymentMonth | long | 应缴月份(时间戳,毫秒) |
  779 +| dueDate | long | 应缴日期(时间戳,毫秒) |
  780 +| dueAmount | decimal | 应缴金额 |
  781 +| isPaid | int | 是否已缴(0-未缴,1-已缴) |
  782 +| actualPaymentDate | long | 实际缴费时间(时间戳,毫秒,可能为null) |
  783 +| actualPaymentAmount | decimal | 实际缴费金额(可能为null) |
  784 +| remarks | string | 备注 |
  785 +| createUser | string | 创建人ID |
  786 +| createUserName | string | 创建人姓名 |
  787 +| createTime | long | 创建时间(时间戳,毫秒) |
  788 +| updateUser | string | 更新人ID |
  789 +| updateUserName | string | 更新人姓名 |
  790 +| updateTime | long | 更新时间(时间戳,毫秒,可能为null) |
  791 +| isEffective | int | 是否有效(1-有效,0-无效) |
  792 +
... ...
合同管理系统-逻辑梳理.md 0 → 100644
  1 +# 合同管理系统 - 逻辑梳理
  2 +
  3 +## 📋 需求概述
  4 +
  5 +建立一个合同管理系统,用于管理门店合同信息,并自动生成月租明细,用于统计每个月每个门店的合同支付明细以及成本费用。
  6 +
  7 +## 🗄️ 数据库表设计
  8 +
  9 +### 1. 合同表(lq_contract)
  10 +
  11 +**字段说明:**
  12 +
  13 +| 字段名 | 类型 | 说明 | 必填 |
  14 +|--------|------|------|------|
  15 +| F_Id | VARCHAR(50) | 主键ID | ✅ |
  16 +| F_StoreId | VARCHAR(50) | 门店ID(关联lq_mdxx.F_Id) | ✅ |
  17 +| F_StoreName | VARCHAR(200) | 店名(冗余字段,便于查询) | ✅ |
  18 +| F_Title | VARCHAR(200) | 标题 | ✅ |
  19 +| F_Category | VARCHAR(100) | 分类(string类型,自己填写) | ❌ |
  20 +| F_TenantName | VARCHAR(200) | 户名 | ❌ |
  21 +| F_ContractStartDate | DATETIME | 合同起始日期 | ✅ |
  22 +| F_ContractEndDate | DATETIME | 合同结束日期 | ✅ |
  23 +| F_ReminderDays | INT | 提前多少天提醒 | ❌(默认0) |
  24 +| F_Deposit | DECIMAL(18,2) | 押金 | ❌(默认0) |
  25 +| F_NextPaymentDate | DATETIME | 下次应交时间 | ❌ |
  26 +| F_MonthlyRent | DECIMAL(18,2) | 月租 | ✅ |
  27 +| F_PaymentAmount | DECIMAL(18,2) | 缴租金额(每次交租的金额,通常=月租×交租周期) | ✅ |
  28 +| F_PaymentCycle | INT | 交租周期(数字,表示几个月,如1、3、6等) | ✅ |
  29 +| F_Remarks | VARCHAR(1000) | 备注 | ❌ |
  30 +| F_Attachment | VARCHAR(500) | 附件(存储附件路径或JSON) | ❌ |
  31 +| F_CreateUser | VARCHAR(50) | 创建人ID | ✅ |
  32 +| F_CreateTime | DATETIME | 创建时间 | ✅ |
  33 +| F_UpdateUser | VARCHAR(50) | 更新人ID | ❌ |
  34 +| F_UpdateTime | DATETIME | 更新时间 | ❌ |
  35 +| F_IsEffective | INT | 是否有效(1-有效,0-无效) | ✅(默认1) |
  36 +
  37 +**索引:**
  38 +- 主键:F_Id
  39 +- 门店ID索引:F_StoreId
  40 +- 合同起始日期索引:F_ContractStartDate
  41 +- 合同结束日期索引:F_ContractEndDate
  42 +- 下次应交时间索引:F_NextPaymentDate
  43 +
  44 +### 2. 月租明细表(lq_contract_rent_detail)
  45 +
  46 +**字段说明:**
  47 +
  48 +| 字段名 | 类型 | 说明 | 必填 |
  49 +|--------|------|------|------|
  50 +| F_Id | VARCHAR(50) | 主键ID | ✅ |
  51 +| F_ContractId | VARCHAR(50) | 合同ID(关联lq_contract.F_Id) | ✅ |
  52 +| F_PaymentMonth | DATETIME | 应缴月份(表示哪个月份,格式:YYYY-MM-01) | ✅ |
  53 +| F_DueDate | DATETIME | 应缴日期(具体应缴日期) | ✅ |
  54 +| F_DueAmount | DECIMAL(18,2) | 应缴金额 | ✅ |
  55 +| F_IsPaid | INT | 是否已缴(0-未缴,1-已缴) | ✅(默认0) |
  56 +| F_ActualPaymentDate | DATETIME | 实际缴费时间 | ❌ |
  57 +| F_ActualPaymentAmount | DECIMAL(18,2) | 实际缴费金额 | ❌ |
  58 +| F_Remarks | VARCHAR(500) | 备注 | ❌ |
  59 +| F_CreateUser | VARCHAR(50) | 创建人ID | ✅ |
  60 +| F_CreateTime | DATETIME | 创建时间 | ✅ |
  61 +| F_UpdateUser | VARCHAR(50) | 更新人ID | ❌ |
  62 +| F_UpdateTime | DATETIME | 更新时间 | ❌ |
  63 +| F_IsEffective | INT | 是否有效(1-有效,0-无效) | ✅(默认1) |
  64 +
  65 +**索引:**
  66 +- 主键:F_Id
  67 +- 合同ID索引:F_ContractId
  68 +- 应缴月份索引:F_PaymentMonth
  69 +- 应缴日期索引:F_DueDate
  70 +- 是否已缴索引:F_IsPaid
  71 +- 联合索引:(F_ContractId, F_PaymentMonth) - 用于查询某个合同的某个月份明细
  72 +
  73 +## 🔄 业务流程逻辑
  74 +
  75 +### 1. 创建合同时自动生成月租明细
  76 +
  77 +**逻辑:**
  78 +1. 用户录入合同信息
  79 +2. 系统根据以下信息自动生成月租明细:
  80 + - **合同起始日期**(F_ContractStartDate)
  81 + - **合同结束日期**(F_ContractEndDate)
  82 + - **交租周期**(F_PaymentCycle,单位:月)
  83 + - **缴租金额**(F_PaymentAmount,每次交租的金额)
  84 +
  85 +**生成规则:**
  86 +- 从合同起始日期开始,每隔 `F_PaymentCycle` 个月生成一条明细
  87 +- 应缴月份:每次交租对应的月份(格式:YYYY-MM-01)
  88 +- 应缴日期:应缴月份的第一天(或根据业务规则设定)
  89 +- 应缴金额:等于 `F_PaymentAmount`
  90 +- 生成到合同结束日期为止
  91 +
  92 +**示例:**
  93 +- 合同起始:2025-01-01
  94 +- 合同结束:2025-12-31
  95 +- 交租周期:3个月
  96 +- 缴租金额:3000元
  97 +
  98 +生成的明细:
  99 +- 2025-01-01,应缴金额:3000元(1-3月)
  100 +- 2025-04-01,应缴金额:3000元(4-6月)
  101 +- 2025-07-01,应缴金额:3000元(7-9月)
  102 +- 2025-10-01,应缴金额:3000元(10-12月)
  103 +
  104 +### 2. 计算下次应交时间
  105 +
  106 +**逻辑:**
  107 +- 根据当前时间、提前多少天提醒(F_ReminderDays),计算下次应交时间(F_NextPaymentDate)
  108 +- 查找该合同未缴费的明细中,应缴日期最早的一条
  109 +- 下次应交时间 = 最早应缴日期 - 提前提醒天数
  110 +
  111 +**示例:**
  112 +- 当前时间:2025-01-15
  113 +- 提前提醒天数:7天
  114 +- 最早未缴费明细的应缴日期:2025-02-01
  115 +- 下次应交时间 = 2025-02-01 - 7天 = 2025-01-25
  116 +
  117 +### 3. 删除合同时级联删除明细
  118 +
  119 +**逻辑:**
  120 +- 删除合同时,需要同时删除该合同的所有月租明细
  121 +- 使用事务确保数据一致性
  122 +- 物理删除或逻辑删除(设置 F_IsEffective = 0)
  123 +
  124 +### 4. 更新合同时的处理
  125 +
  126 +**逻辑:**
  127 +- 如果修改了以下字段,需要重新生成月租明细:
  128 + - 合同起始日期
  129 + - 合同结束日期
  130 + - 交租周期
  131 + - 缴租金额
  132 +- 删除旧的明细,重新生成新的明细
  133 +- 如果只是修改其他字段(如备注、附件等),不需要重新生成明细
  134 +
  135 +## 📊 统计功能
  136 +
  137 +### 1. 按月统计每个门店的合同支付明细
  138 +
  139 +**查询逻辑:**
  140 +- 根据月份筛选月租明细表
  141 +- 按门店分组统计
  142 +- 统计字段:
  143 + - 门店ID、门店名称
  144 + - 应缴金额合计
  145 + - 已缴金额合计
  146 + - 未缴金额合计
  147 + - 明细数量
  148 +
  149 +### 2. 成本费用统计
  150 +
  151 +**查询逻辑:**
  152 +- 统计已缴费的明细
  153 +- 按月份、门店分组
  154 +- 用于成本费用核算
  155 +
  156 +## 🔧 功能方法
  157 +
  158 +### 1. 自动生成月租明细方法
  159 +
  160 +**方法名:** `GenerateRentDetailsAsync(string contractId)`
  161 +
  162 +**逻辑:**
  163 +1. 查询合同信息
  164 +2. 计算需要生成的明细数量
  165 +3. 从合同起始日期开始,每隔交租周期生成一条明细
  166 +4. 批量插入明细记录
  167 +
  168 +### 2. 计算下次应交时间方法(匿名方法)
  169 +
  170 +**方法名:** `CalculateNextPaymentDate(string contractId)`
  171 +
  172 +**逻辑:**
  173 +1. 查询合同信息(获取提前提醒天数)
  174 +2. 查询该合同未缴费的明细,按应缴日期升序排列
  175 +3. 取最早的一条明细
  176 +4. 计算:下次应交时间 = 最早应缴日期 - 提前提醒天数
  177 +5. 更新合同的 F_NextPaymentDate 字段
  178 +
  179 +### 3. 删除合同方法
  180 +
  181 +**方法名:** `DeleteContractAsync(string contractId)`
  182 +
  183 +**逻辑:**
  184 +1. 开启事务
  185 +2. 删除该合同的所有月租明细(逻辑删除或物理删除)
  186 +3. 删除合同(逻辑删除或物理删除)
  187 +4. 提交事务
  188 +
  189 +## ⚠️ 注意事项
  190 +
  191 +1. **交租周期说明:**
  192 + - 1 = 每月交租
  193 + - 3 = 每3个月交租(季度)
  194 + - 6 = 每6个月交租(半年)
  195 + - 12 = 每年交租
  196 +
  197 +2. **缴租金额说明:**
  198 + - 缴租金额 = 月租 × 交租周期
  199 + - 例如:月租1000元,交租周期3个月,则缴租金额 = 3000元
  200 +
  201 +3. **应缴日期计算:**
  202 + - 建议:应缴日期 = 应缴月份的第一天
  203 + - 或者:根据合同起始日期推算(如合同起始是每月15日,则应缴日期也是每月15日)
  204 +
  205 +4. **数据一致性:**
  206 + - 删除合同时必须级联删除明细
  207 + - 更新合同时如果影响明细,需要重新生成
  208 +
  209 +5. **性能考虑:**
  210 + - 明细表数据量可能较大,需要建立合适的索引
  211 + - 批量生成明细时使用批量插入
  212 +
  213 +## 📝 待确认问题
  214 +
  215 +1. **应缴日期的计算规则:**
  216 + - 是固定为每月1日,还是根据合同起始日期推算?
  217 + - 例如:合同起始是2025-01-15,交租周期3个月,第一次应缴日期是2025-01-15还是2025-01-01?
  218 +
  219 +2. **删除方式:**
  220 + - 物理删除还是逻辑删除?
  221 + - 建议使用逻辑删除(设置 F_IsEffective = 0),保留历史数据
  222 +
  223 +3. **附件存储方式:**
  224 + - 存储文件路径?
  225 + - 还是存储JSON格式的附件信息(多个附件)?
  226 +
  227 +4. **缴租金额的计算:**
  228 + - 是否自动计算:缴租金额 = 月租 × 交租周期?
  229 + - 还是允许手动输入(可能有不规则的情况)?
  230 +
... ...
库存使用审批流程-前端调用说明.md 0 → 100644
  1 +# 库存使用审批流程 - 前端调用说明
  2 +
  3 +## 📋 目录
  4 +- [业务流程概述](#业务流程概述)
  5 +- [接口列表](#接口列表)
  6 +- [完整流程示例](#完整流程示例)
  7 +- [接口详细说明](#接口详细说明)
  8 +- [注意事项](#注意事项)
  9 +
  10 +## 🔄 业务流程概述
  11 +
  12 +库存使用审批流程包含以下步骤:
  13 +
  14 +1. **创建使用记录并提交审批** - 批量创建库存使用记录,同时创建申请记录并提交审批
  15 +2. **查询申请记录** - 根据批次ID查询申请记录,查看审批状态
  16 +3. **审批申请** - 审批人(财务老师或库管)审批申请(通过/不通过)
  17 +4. **标记已领取** - 审批通过后,标记申请已领取
  18 +5. **查询批次信息** - 查询批次详细信息,包括使用记录和申请记录
  19 +6. **门店领取统计** - 按月份统计每个门店的领取总金额
  20 +
  21 +## 📡 接口列表
  22 +
  23 +| 接口 | 方法 | 路径 | 说明 |
  24 +|------|------|------|------|
  25 +| 批量创建使用记录 | POST | `/api/Extend/LqInventoryUsage/BatchCreate` | 创建使用记录并提交审批(审批人ID和申请门店ID必填) |
  26 +| 查询申请记录 | GET | `/api/Extend/LqInventoryUsage/GetApplicationByBatchId` | 根据批次ID查询申请记录 |
  27 +| 审批申请 | POST | `/api/Extend/LqInventoryUsage/Approve/{applicationId}` | 审批申请(通过/不通过) |
  28 +| 标记已领取 | PUT | `/api/Extend/LqInventoryUsage/MarkReceived/{applicationId}` | 标记申请已领取 |
  29 +| 查询批次信息 | GET | `/api/Extend/LqInventoryUsage/GetBatchInfo` | 查询批次详细信息 |
  30 +| 门店领取统计 | POST | `/api/Extend/LqInventoryUsage/GetStoreReceiveStatistics` | 按月份统计门店领取总金额 |
  31 +
  32 +## 🚀 完整流程示例
  33 +
  34 +### 步骤1:创建使用记录并提交审批
  35 +
  36 +**接口:** `POST /api/Extend/LqInventoryUsage/BatchCreate`
  37 +
  38 +**请求示例:**
  39 +```javascript
  40 +const response = await request({
  41 + url: '/api/Extend/LqInventoryUsage/BatchCreate',
  42 + method: 'POST',
  43 + data: {
  44 + approverId: '13110190690', // 审批人ID(财务老师或库管,必填)
  45 + applicationStoreId: '1649328471923847168', // 申请门店ID(必填)
  46 + remarks: '测试审批流程', // 备注(可选)
  47 + usageItems: [
  48 + {
  49 + productId: '763978766132184325', // 产品ID(必填)
  50 + storeId: '1649328471923847168', // 门店ID(必填)
  51 + usageTime: '2025-01-09T10:00:00', // 使用时间(必填)
  52 + usageQuantity: 10, // 使用数量(必填,必须大于0)
  53 + relatedConsumeId: '' // 关联消耗ID(可选)
  54 + },
  55 + {
  56 + productId: '763979169120912645',
  57 + storeId: '1649328471923847168',
  58 + usageTime: '2025-01-09T10:00:00',
  59 + usageQuantity: 20,
  60 + relatedConsumeId: ''
  61 + }
  62 + ]
  63 + }
  64 +});
  65 +
  66 +// 响应示例
  67 +// {
  68 +// "code": 200,
  69 +// "msg": "操作成功",
  70 +// "data": {
  71 +// "batchId": "768041985045955845", // 批次ID,后续查询需要使用
  72 +// "successCount": 2,
  73 +// "failCount": 0,
  74 +// "successIds": ["768041987361211653", "768041987361211654"],
  75 +// "failItems": []
  76 +// }
  77 +// }
  78 +```
  79 +
  80 +**说明:**
  81 +- 系统会自动计算单价(从产品表获取)和合计金额(单价×数量)
  82 +- 创建使用记录后会自动创建申请记录,状态为"审批中"
  83 +- 保存返回的 `batchId`,后续查询需要使用
  84 +
  85 +### 步骤2:查询申请记录(可选)
  86 +
  87 +**接口:** `GET /api/Extend/LqInventoryUsage/GetApplicationByBatchId`
  88 +
  89 +**请求示例:**
  90 +```javascript
  91 +const response = await request({
  92 + url: '/api/Extend/LqInventoryUsage/GetApplicationByBatchId',
  93 + method: 'GET',
  94 + params: {
  95 + batchId: '768041985045955845' // 批次ID
  96 + }
  97 +});
  98 +
  99 +// 响应示例
  100 +// {
  101 +// "code": 200,
  102 +// "msg": "操作成功",
  103 +// "data": {
  104 +// "success": true,
  105 +// "data": {
  106 +// "id": "768041987952608518", // 申请ID
  107 +// "usageBatchId": "768041985045955845",
  108 +// "applicationUserId": "admin",
  109 +// "applicationUserName": "管理员",
  110 +// "applicationStoreId": "1649328471923847168",
  111 +// "applicationTime": 1765280681000,
  112 +// "approvalStatus": "审批中", // 审批状态:待审批/审批中/已通过/未通过
  113 +// "isReceived": 0, // 是否已领取:0-未领取,1-已领取
  114 +// "receiveTime": null,
  115 +// "receiveUser": null,
  116 +// "remarks": "测试审批流程",
  117 +// "approvalRecords": [] // 审批记录列表
  118 +// }
  119 +// }
  120 +// }
  121 +
  122 +// 注意:如果是旧数据(没有申请记录),返回:
  123 +// {
  124 +// "code": 200,
  125 +// "data": {
  126 +// "success": false,
  127 +// "message": "该批次没有对应的申请记录",
  128 +// "data": null
  129 +// }
  130 +// }
  131 +```
  132 +
  133 +### 步骤3:审批申请
  134 +
  135 +**接口:** `POST /api/Extend/LqInventoryUsage/Approve/{applicationId}`
  136 +
  137 +**请求示例(通过):**
  138 +```javascript
  139 +const response = await request({
  140 + url: '/api/Extend/LqInventoryUsage/Approve/768041987952608518',
  141 + method: 'POST',
  142 + params: {
  143 + result: '通过', // 审批结果:通过/不通过
  144 + opinion: '同意申请' // 审批意见(可选)
  145 + }
  146 +});
  147 +
  148 +// 响应示例
  149 +// {
  150 +// "code": 200,
  151 +// "msg": "操作成功",
  152 +// "data": null
  153 +// }
  154 +```
  155 +
  156 +**请求示例(不通过):**
  157 +```javascript
  158 +const response = await request({
  159 + url: '/api/Extend/LqInventoryUsage/Approve/768041987952608518',
  160 + method: 'POST',
  161 + params: {
  162 + result: '不通过',
  163 + opinion: '库存不足,暂不批准'
  164 + }
  165 +});
  166 +```
  167 +
  168 +**说明:**
  169 +- 只有状态为"审批中"的申请才能进行审批操作
  170 +- 审批通过后,状态变为"已通过"
  171 +- 审批不通过后,状态变为"未通过"
  172 +
  173 +### 步骤4:标记已领取(审批通过后)
  174 +
  175 +**接口:** `PUT /api/Extend/LqInventoryUsage/MarkReceived/{applicationId}`
  176 +
  177 +**请求示例:**
  178 +```javascript
  179 +const response = await request({
  180 + url: '/api/Extend/LqInventoryUsage/MarkReceived/768041987952608518',
  181 + method: 'PUT'
  182 +});
  183 +
  184 +// 响应示例
  185 +// {
  186 +// "code": 200,
  187 +// "msg": "操作成功",
  188 +// "data": null
  189 +// }
  190 +```
  191 +
  192 +**说明:**
  193 +- 只有审批状态为"已通过"的申请才能标记为已领取
  194 +- 标记后会记录领取时间和领取人
  195 +
  196 +### 步骤5:查询批次信息
  197 +
  198 +**接口:** `GET /api/Extend/LqInventoryUsage/GetBatchInfo`
  199 +
  200 +**请求示例:**
  201 +```javascript
  202 +const response = await request({
  203 + url: '/api/Extend/LqInventoryUsage/GetBatchInfo',
  204 + method: 'GET',
  205 + params: {
  206 + batchId: '768041985045955845' // 批次ID
  207 + }
  208 +});
  209 +
  210 +// 响应示例
  211 +// {
  212 +// "code": 200,
  213 +// "msg": "操作成功",
  214 +// "data": {
  215 +// "batchId": "768041985045955845",
  216 +// "createTime": 1765280681000,
  217 +// "createUser": "admin",
  218 +// "createUserName": "管理员",
  219 +// "totalCount": 2,
  220 +// "effectiveCount": 2,
  221 +// "ineffectiveCount": 0,
  222 +// "totalUsageQuantity": 30,
  223 +// "totalUsageAmount": 220.00, // 总使用金额
  224 +// "usageRecords": [
  225 +// {
  226 +// "id": "768041987361211653",
  227 +// "productId": "763978766132184325",
  228 +// "productName": "卸妆油",
  229 +// "productPrice": 10.00,
  230 +// "storeId": "1649328471923847168",
  231 +// "storeName": "绿纤总部",
  232 +// "usageTime": 1736388000000,
  233 +// "usageQuantity": 10,
  234 +// "usageTotalValue": 100.00 // 该条记录的总金额
  235 +// },
  236 +// // ... 更多记录
  237 +// ],
  238 +// "applicationInfo": { // 申请记录信息(如果是旧数据,此字段为null)
  239 +// "id": "768041987952608518",
  240 +// "applicationUserId": "admin",
  241 +// "applicationUserName": "管理员",
  242 +// "applicationStoreId": "1649328471923847168",
  243 +// "applicationTime": 1765280681000,
  244 +// "approvalStatus": "已通过",
  245 +// "isReceived": 1,
  246 +// "receiveTime": 1765280756000,
  247 +// "receiveUser": "admin",
  248 +// "remarks": "测试审批流程"
  249 +// }
  250 +// }
  251 +// }
  252 +```
  253 +
  254 +**说明:**
  255 +- 兼容旧数据:如果该批次没有申请记录(旧数据),`applicationInfo` 字段为 `null`
  256 +- 包含该批次的所有使用记录详情
  257 +- 包含申请记录信息(如果有)
  258 +
  259 +### 步骤6:门店领取统计(可选)
  260 +
  261 +**接口:** `POST /api/Extend/LqInventoryUsage/GetStoreReceiveStatistics`
  262 +
  263 +**请求示例:**
  264 +```javascript
  265 +const response = await request({
  266 + url: '/api/Extend/LqInventoryUsage/GetStoreReceiveStatistics',
  267 + method: 'POST',
  268 + data: {
  269 + year: 2025, // 统计年份(必填)
  270 + month: 11, // 统计月份(必填,1-12)
  271 + storeId: '' // 门店ID(可选,不传则统计所有门店)
  272 + }
  273 +});
  274 +
  275 +// 响应示例
  276 +// {
  277 +// "code": 200,
  278 +// "msg": "操作成功",
  279 +// "data": {
  280 +// "success": true,
  281 +// "data": [
  282 +// {
  283 +// "storeId": "1649328471923847168",
  284 +// "storeName": "绿纤总部",
  285 +// "applicationCount": 5, // 申请数量
  286 +// "totalAmount": 1500.00 // 领取总金额
  287 +// },
  288 +// // ... 更多门店
  289 +// ],
  290 +// "total": 10,
  291 +// "message": "查询成功,共10个门店"
  292 +// }
  293 +// }
  294 +```
  295 +
  296 +**说明:**
  297 +- 只统计审批通过且已领取的申请
  298 +- 按门店分组统计
  299 +- 按总金额降序排列
  300 +
  301 +## 📝 接口详细说明
  302 +
  303 +### 1. 批量创建使用记录
  304 +
  305 +**必填参数:**
  306 +- `approverId`: 审批人ID(财务老师或库管)
  307 +- `applicationStoreId`: 申请门店ID
  308 +- `usageItems`: 使用记录列表(至少一条)
  309 + - `productId`: 产品ID
  310 + - `storeId`: 门店ID
  311 + - `usageTime`: 使用时间
  312 + - `usageQuantity`: 使用数量(必须大于0)
  313 +
  314 +**可选参数:**
  315 +- `batchId`: 批次ID(不传则自动生成)
  316 +- `remarks`: 备注
  317 +
  318 +**自动计算:**
  319 +- `unitPrice`: 单价(从产品表获取)
  320 +- `totalAmount`: 合计金额(单价×数量)
  321 +
  322 +### 2. 查询申请记录
  323 +
  324 +**参数:**
  325 +- `batchId`: 批次ID(必填)
  326 +
  327 +**返回说明:**
  328 +- 如果存在申请记录:返回完整的申请信息
  329 +- 如果不存在申请记录(旧数据):返回 `success=false`,`data=null`
  330 +
  331 +### 3. 审批申请
  332 +
  333 +**参数:**
  334 +- `applicationId`: 申请ID(路径参数)
  335 +- `result`: 审批结果(查询参数,必填,值:通过/不通过)
  336 +- `opinion`: 审批意见(查询参数,可选)
  337 +
  338 +**状态要求:**
  339 +- 只有状态为"审批中"的申请才能进行审批操作
  340 +
  341 +### 4. 标记已领取
  342 +
  343 +**参数:**
  344 +- `applicationId`: 申请ID(路径参数)
  345 +
  346 +**状态要求:**
  347 +- 只有审批状态为"已通过"的申请才能标记为已领取
  348 +
  349 +### 5. 查询批次信息
  350 +
  351 +**参数:**
  352 +- `batchId`: 批次ID(必填)
  353 +
  354 +**兼容性:**
  355 +- 兼容旧数据:如果该批次没有申请记录,`applicationInfo` 字段为 `null`
  356 +
  357 +### 6. 门店领取统计
  358 +
  359 +**参数:**
  360 +- `year`: 统计年份(必填,范围:2020-2100)
  361 +- `month`: 统计月份(必填,范围:1-12)
  362 +- `storeId`: 门店ID(可选)
  363 +
  364 +## ⚠️ 注意事项
  365 +
  366 +1. **审批人ID和申请门店ID必填**
  367 + - 创建使用记录时,`approverId` 和 `applicationStoreId` 必须传入
  368 + - 系统会自动创建申请记录并提交审批
  369 +
  370 +2. **兼容旧数据**
  371 + - 查询申请记录时,如果该批次没有申请记录(旧数据),会返回 `success=false`
  372 + - 查询批次信息时,如果该批次没有申请记录,`applicationInfo` 字段为 `null`
  373 + - 前端需要兼容这两种情况
  374 +
  375 +3. **审批流程**
  376 + - 申请创建后,状态为"审批中"
  377 + - 只有"审批中"状态的申请才能进行审批操作
  378 + - 审批通过后,状态变为"已通过"
  379 + - 审批不通过后,状态变为"未通过"
  380 +
  381 +4. **标记已领取**
  382 + - 只有"已通过"状态的申请才能标记为已领取
  383 + - 标记后会记录领取时间和领取人
  384 +
  385 +5. **单价和合计金额**
  386 + - 系统会自动计算单价(从产品表获取)和合计金额(单价×数量)
  387 + - 前端无需手动计算
  388 +
  389 +6. **URL编码**
  390 + - 审批接口的参数如果包含中文,需要进行URL编码
  391 + - 例如:`result=通过` 需要编码为 `result=%E9%80%9A%E8%BF%87`
  392 +
  393 +## 🔍 状态说明
  394 +
  395 +### 审批状态(approvalStatus)
  396 +- `待审批`: 申请已创建,等待提交审批
  397 +- `审批中`: 申请已提交,等待审批
  398 +- `已通过`: 审批通过
  399 +- `未通过`: 审批不通过
  400 +- `已退回`: 申请被退回
  401 +
  402 +### 领取状态(isReceived)
  403 +- `0`: 未领取
  404 +- `1`: 已领取
  405 +
  406 +## 📊 数据流程
  407 +
  408 +```
  409 +创建使用记录 → 创建申请记录(审批中) → 审批申请 → 标记已领取
  410 + ↓ ↓ ↓ ↓
  411 + 批次ID 申请ID 审批记录 领取记录
  412 +```
  413 +
  414 +## 🎯 前端页面建议
  415 +
  416 +1. **创建使用记录页面**
  417 + - 表单包含:产品选择、门店选择、使用时间、使用数量
  418 + - 必须选择审批人(财务老师或库管)
  419 + - 必须选择申请门店
  420 + - 提交后显示批次ID
  421 +
  422 +2. **申请列表页面**
  423 + - 显示所有申请记录
  424 + - 显示审批状态、是否已领取
  425 + - 支持筛选:审批状态、是否已领取、门店等
  426 +
  427 +3. **审批页面**
  428 + - 显示申请详情
  429 + - 显示使用记录列表
  430 + - 审批操作:通过/不通过,可填写审批意见
  431 +
  432 +4. **批次详情页面**
  433 + - 显示批次基本信息
  434 + - 显示使用记录列表(包含单价和合计金额)
  435 + - 显示申请记录信息(如果有)
  436 + - 兼容旧数据(没有申请记录的情况)
  437 +
  438 +5. **统计页面**
  439 + - 选择年份和月份
  440 + - 显示每个门店的领取统计
  441 + - 显示总金额、申请数量等
  442 +
... ...