Commit d055b5e13f0beb0e006b28973fc7a1975b61e851

Authored by “wangming”
1 parent 2030a58d

fix: update development environment API configuration and enhance reimbursement export functionality

- Updated VUE_APP_BASE_API in the development environment to point to localhost for local testing.
- Added a new endpoint to export approved reimbursement details for the current month, including associated purchase records.
- Enhanced SQL queries for better performance and clarity in the reimbursement statistics service.
- Improved documentation and comments for better understanding of the code logic.
Showing 42 changed files with 6145 additions and 31 deletions
Excel导入格式说明.md 0 → 100644
  1 +# Excel导入格式说明
  2 +
  3 +## 📋 合作成本表导入格式
  4 +
  5 +### 文件要求
  6 +- 文件格式:`.xlsx` 或 `.xls`
  7 +- 第一行为标题行(必须)
  8 +- 从第二行开始为数据行
  9 +
  10 +### Excel列格式
  11 +
  12 +| 列序号 | 列名 | 是否必填 | 数据类型 | 说明 | 示例 |
  13 +|--------|------|---------|---------|------|------|
  14 +| A | 门店ID | ✅ 必填 | 文本 | 门店ID(关联lq_mdxx.F_Id) | `1649328471923847168` |
  15 +| B | 门店名称 | ⚪ 可选 | 文本 | 门店名称(如果为空,系统会根据门店ID自动查询) | `川师店` |
  16 +| C | 年份 | ✅ 必填 | 数字 | 年份(4位数字) | `2025` |
  17 +| D | 月份 | ✅ 必填 | 文本 | 月份(YYYYMM格式,6位) | `202501` |
  18 +| E | 合计金额 | ✅ 必填 | 数字 | 合计金额(支持小数) | `5000.00` |
  19 +| F | 备注说明 | ⚪ 可选 | 文本 | 备注说明 | `1月合作项目成本` |
  20 +
  21 +### Excel示例
  22 +
  23 +```
  24 +门店ID | 门店名称 | 年份 | 月份 | 合计金额 | 备注说明
  25 +---------------------------|---------|------|--------|----------|------------------
  26 +1649328471923847168 | 川师店 | 2025 | 202501 | 5000.00 | 1月合作项目成本
  27 +1649328471923847169 | 春熙店 | 2025 | 202501 | 3000.00 | 1月合作项目成本
  28 +```
  29 +
  30 +### 注意事项
  31 +1. **门店ID**:必须存在系统中,否则导入会失败
  32 +2. **年份**:必须是4位数字,如:2025
  33 +3. **月份**:必须是YYYYMM格式的6位字符串,如:202501(表示2025年1月)
  34 +4. **合计金额**:必须是数字,支持小数,如:5000.00
  35 +5. **重复检查**:系统会检查相同门店、年份、月份是否已存在记录,如果存在会跳过并提示
  36 +6. **空行处理**:如果门店ID和门店名称都为空,该行会被跳过
  37 +
  38 +---
  39 +
  40 +## 📋 店内支出表导入格式
  41 +
  42 +### 文件要求
  43 +- 文件格式:`.xlsx` 或 `.xls`
  44 +- 第一行为标题行(必须)
  45 +- 从第二行开始为数据行
  46 +
  47 +### Excel列格式
  48 +
  49 +| 列序号 | 列名 | 是否必填 | 数据类型 | 说明 | 示例 |
  50 +|--------|------|---------|---------|------|------|
  51 +| A | 门店ID | ✅ 必填 | 文本 | 门店ID(关联lq_mdxx.F_Id) | `1649328471923847168` |
  52 +| B | 门店名称 | ⚪ 可选 | 文本 | 门店名称(如果为空,系统会根据门店ID自动查询) | `川师店` |
  53 +| C | 支出分类ID | ⚪ 可选 | 文本 | 支出分类ID(关联lq_reimbursement_category.F_Id) | `xxx` |
  54 +| D | 支出分类名称 | ⚪ 可选 | 文本 | 支出分类名称 | `办公用品` |
  55 +| E | 支出日期 | ✅ 必填 | 日期 | 支出日期(格式:YYYY-MM-DD) | `2025-01-15` |
  56 +| F | 单价 | ⚪ 可选 | 数字 | 单价(支持小数) | `10.50` |
  57 +| G | 数量 | ⚪ 可选 | 数字 | 数量(整数) | `5` |
  58 +| H | 金额 | ✅ 必填 | 数字 | 金额(支持小数) | `52.50` |
  59 +| I | 备注说明 | ⚪ 可选 | 文本 | 备注说明 | `购买办公用品` |
  60 +| J | 关联报销申请ID | ⚪ 可选 | 文本 | 关联的报销申请ID | `xxx` |
  61 +| K | 关联购买记录ID | ⚪ 可选 | 文本 | 关联的购买记录ID | `xxx` |
  62 +
  63 +### Excel示例
  64 +
  65 +```
  66 +门店ID | 门店名称 | 支出分类ID | 支出分类名称 | 支出日期 | 单价 | 数量 | 金额 | 备注说明 | 关联报销申请ID | 关联购买记录ID
  67 +---------------------------|---------|-----------|------------|-----------|-------|------|--------|-------------|--------------|---------------
  68 +1649328471923847168 | 川师店 | xxx | 办公用品 | 2025-01-15 | 10.50 | 5 | 52.50 | 购买办公用品 | xxx | xxx
  69 +1649328471923847168 | 川师店 | xxx | 水电费 | 2025-01-20 | 0.00 | 0 | 500.00 | 1月水电费 | |
  70 +```
  71 +
  72 +### 注意事项
  73 +1. **门店ID**:必须存在系统中,否则导入会失败
  74 +2. **支出日期**:必须是日期格式,推荐格式:`YYYY-MM-DD`,如:`2025-01-15`
  75 +3. **金额**:必须是数字,支持小数,如:52.50
  76 +4. **单价和数量**:如果为空,默认为0
  77 +5. **空行处理**:如果门店ID和门店名称都为空,该行会被跳过
  78 +
  79 +---
  80 +
  81 +## 🔗 导入接口说明
  82 +
  83 +### 合作成本表导入接口
  84 +```
  85 +POST /api/Extend/LqCooperationCost/Actions/Import
  86 +Content-Type: multipart/form-data
  87 +
  88 +参数:
  89 +- file: Excel文件(必填)
  90 +```
  91 +
  92 +### 店内支出表导入接口
  93 +```
  94 +POST /api/Extend/LqStoreExpense/Actions/Import
  95 +Content-Type: multipart/form-data
  96 +
  97 +参数:
  98 +- file: Excel文件(必填)
  99 +```
  100 +
  101 +### 返回结果
  102 +```json
  103 +{
  104 + "success": true,
  105 + "message": "导入完成:成功2条,失败0条",
  106 + "successCount": 2,
  107 + "failCount": 0,
  108 + "failMessages": []
  109 +}
  110 +```
  111 +
  112 +---
  113 +
  114 +## 📝 测试数据准备
  115 +
  116 +### 合作成本表测试数据(示例)
  117 +
  118 +请创建一个Excel文件,包含以下测试数据:
  119 +
  120 +| 门店ID | 门店名称 | 年份 | 月份 | 合计金额 | 备注说明 |
  121 +|--------|---------|------|------|----------|----------|
  122 +| (请填写实际门店ID) | 测试门店1 | 2025 | 202501 | 5000.00 | 测试数据1 |
  123 +| (请填写实际门店ID) | 测试门店2 | 2025 | 202501 | 3000.00 | 测试数据2 |
  124 +| (请填写实际门店ID) | 测试门店1 | 2025 | 202502 | 6000.00 | 测试数据3 |
  125 +
  126 +### 店内支出表测试数据(示例)
  127 +
  128 +请创建一个Excel文件,包含以下测试数据:
  129 +
  130 +| 门店ID | 门店名称 | 支出分类ID | 支出分类名称 | 支出日期 | 单价 | 数量 | 金额 | 备注说明 | 关联报销申请ID | 关联购买记录ID |
  131 +|--------|---------|-----------|------------|---------|------|------|------|----------|--------------|--------------|
  132 +| (请填写实际门店ID) | 测试门店1 | | 办公用品 | 2025-01-15 | 10.50 | 5 | 52.50 | 测试支出1 | | |
  133 +| (请填写实际门店ID) | 测试门店1 | | 水电费 | 2025-01-20 | 0 | 0 | 500.00 | 测试支出2 | | |
  134 +| (请填写实际门店ID) | 测试门店2 | | 交通费 | 2025-01-25 | 50.00 | 2 | 100.00 | 测试支出3 | | |
  135 +
  136 +---
  137 +
  138 +## ⚠️ 重要提示
  139 +
  140 +1. **门店ID获取**:可以通过门店列表接口获取实际的门店ID
  141 +2. **日期格式**:支出日期必须使用标准日期格式,推荐使用 `YYYY-MM-DD`
  142 +3. **月份格式**:合作成本表的月份必须是YYYYMM格式(6位字符串),如:202501
  143 +4. **金额精度**:所有金额字段支持2位小数
  144 +5. **重复数据**:合作成本表会检查相同门店、年份、月份是否已存在,如果存在会跳过
  145 +6. **错误处理**:导入过程中如果有错误,会在返回结果中列出所有错误信息
  146 +
... ...
antis-ncc-admin/.env.development
... ... @@ -2,7 +2,7 @@
2 2  
3 3 VUE_CLI_BABEL_TRANSPILE_MODULES = true
4 4 # VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com'
5   -VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
6   -# VUE_APP_BASE_API = 'http://localhost:2011'
  5 +# VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
  6 +VUE_APP_BASE_API = 'http://localhost:2011'
7 7 # VUE_APP_BASE_API = 'http://localhost:2011'
8 8 VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket'
... ...
excel/合作成本表.xlsx 0 → 100644
No preview for this file type
excel/店内支出表.xlsx 0 → 100644
No preview for this file type
excel/考勤统计导入模板_11月.xlsx 0 → 100644
No preview for this file type
netcore/src/Application/NCC.API/Files/TemporaryFile/合作成本表.xls 0 → 100644
No preview for this file type
netcore/src/Application/NCC.API/Files/TemporaryFile/店内支出表.xls 0 → 100644
No preview for this file type
netcore/src/Application/NCC.API/Files/TemporaryFile/报销表明细_2025年01月.xls 0 → 100644
No preview for this file type
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqCooperationCost
  4 +{
  5 + /// <summary>
  6 + /// 合作成本表创建输入参数
  7 + /// </summary>
  8 + public class LqCooperationCostCrInput
  9 + {
  10 + /// <summary>
  11 + /// 门店ID(关联lq_mdxx.F_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 + /// 月份(YYYYMM格式)
  27 + /// </summary>
  28 + public string month { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 合计金额
  32 + /// </summary>
  33 + public decimal totalAmount { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 备注说明
  37 + /// </summary>
  38 + public string remarks { get; set; }
  39 + }
  40 +}
  41 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqCooperationCost
  4 +{
  5 + /// <summary>
  6 + /// 合作成本表详情输出参数
  7 + /// </summary>
  8 + public class LqCooperationCostInfoOutput
  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 int year { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 月份(YYYYMM格式)
  32 + /// </summary>
  33 + public string month { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 合计金额
  37 + /// </summary>
  38 + public decimal totalAmount { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 备注说明
  42 + /// </summary>
  43 + public string remarks { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 创建人ID
  47 + /// </summary>
  48 + public string createUser { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 创建时间
  52 + /// </summary>
  53 + public DateTime? createTime { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 更新人ID
  57 + /// </summary>
  58 + public string updateUser { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 更新时间
  62 + /// </summary>
  63 + public DateTime? updateTime { get; set; }
  64 + }
  65 +}
  66 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqCooperationCost
  4 +{
  5 + /// <summary>
  6 + /// 合作成本表列表输出参数
  7 + /// </summary>
  8 + public class LqCooperationCostListOutput
  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 int year { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 月份(YYYYMM格式)
  32 + /// </summary>
  33 + public string month { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 合计金额
  37 + /// </summary>
  38 + public decimal totalAmount { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 备注说明
  42 + /// </summary>
  43 + public string remarks { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 创建人ID
  47 + /// </summary>
  48 + public string createUser { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 创建时间
  52 + /// </summary>
  53 + public DateTime? createTime { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 更新人ID
  57 + /// </summary>
  58 + public string updateUser { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 更新时间
  62 + /// </summary>
  63 + public DateTime? updateTime { get; set; }
  64 + }
  65 +}
  66 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqCooperationCost
  4 +{
  5 + /// <summary>
  6 + /// 合作成本表列表查询输入参数
  7 + /// </summary>
  8 + public class LqCooperationCostListQueryInput : PageInputBase
  9 + {
  10 + /// <summary>
  11 + /// 选择导出数据key
  12 + /// </summary>
  13 + public string selectKey { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 数据类型(0:分页 1:全部)
  17 + /// </summary>
  18 + public int dataType { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID
  22 + /// </summary>
  23 + public string storeId { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 门店名称
  27 + /// </summary>
  28 + public string storeName { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 年份
  32 + /// </summary>
  33 + public int? year { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 月份(YYYYMM格式)
  37 + /// </summary>
  38 + public string month { get; set; }
  39 + }
  40 +}
  41 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqCooperationCost
  4 +{
  5 + /// <summary>
  6 + /// 合作成本表更新输入参数
  7 + /// </summary>
  8 + public class LqCooperationCostUpInput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店ID(关联lq_mdxx.F_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 int year { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 月份(YYYYMM格式)
  32 + /// </summary>
  33 + public string month { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 合计金额
  37 + /// </summary>
  38 + public decimal totalAmount { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 备注说明
  42 + /// </summary>
  43 + public string remarks { get; set; }
  44 + }
  45 +}
  46 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseCrInput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +using NCC.Common.Model;
  4 +
  5 +namespace NCC.Extend.Entitys.Dto.LqStoreExpense
  6 +{
  7 + /// <summary>
  8 + /// 店内支出表创建输入参数
  9 + /// </summary>
  10 + public class LqStoreExpenseCrInput
  11 + {
  12 + /// <summary>
  13 + /// 门店ID(关联lq_mdxx.F_Id)
  14 + /// </summary>
  15 + public string storeId { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 门店名称
  19 + /// </summary>
  20 + public string storeName { get; set; }
  21 +
  22 + /// <summary>
  23 + /// 支出分类ID(关联lq_reimbursement_category.F_Id)
  24 + /// </summary>
  25 + public string expenseCategoryId { get; set; }
  26 +
  27 + /// <summary>
  28 + /// 支出分类名称
  29 + /// </summary>
  30 + public string expenseCategoryName { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 支出日期
  34 + /// </summary>
  35 + public DateTime expenseDate { get; set; }
  36 +
  37 + /// <summary>
  38 + /// 单价
  39 + /// </summary>
  40 + public decimal unitPrice { get; set; }
  41 +
  42 + /// <summary>
  43 + /// 数量
  44 + /// </summary>
  45 + public int quantity { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 金额
  49 + /// </summary>
  50 + public decimal amount { get; set; }
  51 +
  52 + /// <summary>
  53 + /// 备注说明
  54 + /// </summary>
  55 + public string memo { get; set; }
  56 +
  57 + /// <summary>
  58 + /// 附件
  59 + /// </summary>
  60 + public List<FileControlsModel> attachment { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 关联报销申请ID(可选)
  64 + /// </summary>
  65 + public string relatedReimbursementId { get; set; }
  66 +
  67 + /// <summary>
  68 + /// 关联购买记录ID(可选)
  69 + /// </summary>
  70 + public string relatedPurchaseRecordId { get; set; }
  71 + }
  72 +}
  73 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseInfoOutput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +using NCC.Common.Model;
  4 +
  5 +namespace NCC.Extend.Entitys.Dto.LqStoreExpense
  6 +{
  7 + /// <summary>
  8 + /// 店内支出表详情输出参数
  9 + /// </summary>
  10 + public class LqStoreExpenseInfoOutput
  11 + {
  12 + /// <summary>
  13 + /// 主键ID
  14 + /// </summary>
  15 + public string id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 门店ID
  19 + /// </summary>
  20 + public string storeId { get; set; }
  21 +
  22 + /// <summary>
  23 + /// 门店名称
  24 + /// </summary>
  25 + public string storeName { get; set; }
  26 +
  27 + /// <summary>
  28 + /// 支出分类ID
  29 + /// </summary>
  30 + public string expenseCategoryId { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 支出分类名称
  34 + /// </summary>
  35 + public string expenseCategoryName { get; set; }
  36 +
  37 + /// <summary>
  38 + /// 支出日期
  39 + /// </summary>
  40 + public DateTime expenseDate { get; set; }
  41 +
  42 + /// <summary>
  43 + /// 单价
  44 + /// </summary>
  45 + public decimal unitPrice { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 数量
  49 + /// </summary>
  50 + public int quantity { get; set; }
  51 +
  52 + /// <summary>
  53 + /// 金额
  54 + /// </summary>
  55 + public decimal amount { get; set; }
  56 +
  57 + /// <summary>
  58 + /// 备注说明
  59 + /// </summary>
  60 + public string memo { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 附件
  64 + /// </summary>
  65 + public List<FileControlsModel> attachment { get; set; }
  66 +
  67 + /// <summary>
  68 + /// 关联报销申请ID
  69 + /// </summary>
  70 + public string relatedReimbursementId { get; set; }
  71 +
  72 + /// <summary>
  73 + /// 关联购买记录ID
  74 + /// </summary>
  75 + public string relatedPurchaseRecordId { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 创建人ID
  79 + /// </summary>
  80 + public string createUser { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 创建时间
  84 + /// </summary>
  85 + public DateTime? createTime { get; set; }
  86 +
  87 + /// <summary>
  88 + /// 更新人ID
  89 + /// </summary>
  90 + public string updateUser { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 更新时间
  94 + /// </summary>
  95 + public DateTime? updateTime { get; set; }
  96 + }
  97 +}
  98 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseListOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqStoreExpense
  4 +{
  5 + /// <summary>
  6 + /// 店内支出表列表输出参数
  7 + /// </summary>
  8 + public class LqStoreExpenseListOutput
  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 + /// 支出分类ID
  27 + /// </summary>
  28 + public string expenseCategoryId { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 支出分类名称
  32 + /// </summary>
  33 + public string expenseCategoryName { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 支出日期
  37 + /// </summary>
  38 + public DateTime expenseDate { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 单价
  42 + /// </summary>
  43 + public decimal unitPrice { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 数量
  47 + /// </summary>
  48 + public int quantity { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 金额
  52 + /// </summary>
  53 + public decimal amount { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 备注说明
  57 + /// </summary>
  58 + public string memo { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 关联报销申请ID
  62 + /// </summary>
  63 + public string relatedReimbursementId { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 关联购买记录ID
  67 + /// </summary>
  68 + public string relatedPurchaseRecordId { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 创建人ID
  72 + /// </summary>
  73 + public string createUser { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 创建时间
  77 + /// </summary>
  78 + public DateTime? createTime { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 更新人ID
  82 + /// </summary>
  83 + public string updateUser { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 更新时间
  87 + /// </summary>
  88 + public DateTime? updateTime { get; set; }
  89 + }
  90 +}
  91 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqStoreExpense
  4 +{
  5 + /// <summary>
  6 + /// 店内支出表列表查询输入参数
  7 + /// </summary>
  8 + public class LqStoreExpenseListQueryInput : PageInputBase
  9 + {
  10 + /// <summary>
  11 + /// 选择导出数据key
  12 + /// </summary>
  13 + public string selectKey { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 数据类型(0:分页 1:全部)
  17 + /// </summary>
  18 + public int dataType { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID
  22 + /// </summary>
  23 + public string storeId { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 门店名称
  27 + /// </summary>
  28 + public string storeName { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 支出分类ID
  32 + /// </summary>
  33 + public string expenseCategoryId { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 支出日期(开始)
  37 + /// </summary>
  38 + public string expenseDateStart { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 支出日期(结束)
  42 + /// </summary>
  43 + public string expenseDateEnd { get; set; }
  44 + }
  45 +}
  46 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseUpInput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +using NCC.Common.Model;
  4 +
  5 +namespace NCC.Extend.Entitys.Dto.LqStoreExpense
  6 +{
  7 + /// <summary>
  8 + /// 店内支出表更新输入参数
  9 + /// </summary>
  10 + public class LqStoreExpenseUpInput
  11 + {
  12 + /// <summary>
  13 + /// 主键ID
  14 + /// </summary>
  15 + public string id { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 门店ID(关联lq_mdxx.F_Id)
  19 + /// </summary>
  20 + public string storeId { get; set; }
  21 +
  22 + /// <summary>
  23 + /// 门店名称
  24 + /// </summary>
  25 + public string storeName { get; set; }
  26 +
  27 + /// <summary>
  28 + /// 支出分类ID(关联lq_reimbursement_category.F_Id)
  29 + /// </summary>
  30 + public string expenseCategoryId { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 支出分类名称
  34 + /// </summary>
  35 + public string expenseCategoryName { get; set; }
  36 +
  37 + /// <summary>
  38 + /// 支出日期
  39 + /// </summary>
  40 + public DateTime expenseDate { get; set; }
  41 +
  42 + /// <summary>
  43 + /// 单价
  44 + /// </summary>
  45 + public decimal unitPrice { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 数量
  49 + /// </summary>
  50 + public int quantity { get; set; }
  51 +
  52 + /// <summary>
  53 + /// 金额
  54 + /// </summary>
  55 + public decimal amount { get; set; }
  56 +
  57 + /// <summary>
  58 + /// 备注说明
  59 + /// </summary>
  60 + public string memo { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 附件
  64 + /// </summary>
  65 + public List<FileControlsModel> attachment { get; set; }
  66 +
  67 + /// <summary>
  68 + /// 关联报销申请ID(可选)
  69 + /// </summary>
  70 + public string relatedReimbursementId { get; set; }
  71 +
  72 + /// <summary>
  73 + /// 关联购买记录ID(可选)
  74 + /// </summary>
  75 + public string relatedPurchaseRecordId { get; set; }
  76 + }
  77 +}
  78 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreManagerSalary/StoreManagerSalaryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqStoreManagerSalary
  5 +{
  6 + /// <summary>
  7 + /// 店长工资查询参数
  8 + /// </summary>
  9 + public class StoreManagerSalaryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 年份
  13 + /// </summary>
  14 + public int Year { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 月份
  18 + /// </summary>
  19 + public int Month { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 门店ID(可选,用于筛选特定门店)
  23 + /// </summary>
  24 + public string StoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 员工姓名/账号(可选,用于模糊搜索)
  28 + /// </summary>
  29 + public string Keyword { get; set; }
  30 + }
  31 +}
  32 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreManagerSalary/StoreManagerSalaryOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqStoreManagerSalary
  4 +{
  5 + /// <summary>
  6 + /// 店长工资输出
  7 + /// </summary>
  8 + public class StoreManagerSalaryOutput
  9 + {
  10 + /// <summary>
  11 + /// 主键ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 门店名称
  17 + /// </summary>
  18 + public string StoreName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 员工姓名
  22 + /// </summary>
  23 + public string EmployeeName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 岗位
  27 + /// </summary>
  28 + public string Position { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 门店总业绩
  32 + /// </summary>
  33 + public decimal StoreTotalPerformance { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 门店开单业绩
  37 + /// </summary>
  38 + public decimal StoreBillingPerformance { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 门店退卡业绩
  42 + /// </summary>
  43 + public decimal StoreRefundPerformance { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 门店生命线
  47 + /// </summary>
  48 + public decimal StoreLifeline { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 业绩完成率
  52 + /// </summary>
  53 + public decimal PerformanceCompletionRate { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 业绩是否达标
  57 + /// </summary>
  58 + public string PerformanceReached { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 人头是否达标
  62 + /// </summary>
  63 + public string HeadCountReached { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 消耗是否达标
  67 + /// </summary>
  68 + public string ConsumeReached { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 考核扣款金额
  72 + /// </summary>
  73 + public decimal AssessmentDeduction { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 未达标指标数量
  77 + /// </summary>
  78 + public int UnreachedIndicatorCount { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 进店消耗人数
  82 + /// </summary>
  83 + public int HeadCount { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 目标人头数
  87 + /// </summary>
  88 + public decimal TargetHeadCount { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 门店消耗金额
  92 + /// </summary>
  93 + public decimal StoreConsume { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 目标消耗金额
  97 + /// </summary>
  98 + public decimal TargetConsume { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 销售业绩(开单业绩-退款业绩)
  102 + /// </summary>
  103 + public decimal SalesPerformance { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 产品物料(仓库领用金额)
  107 + /// </summary>
  108 + public decimal ProductMaterial { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 合作项目成本
  112 + /// </summary>
  113 + public decimal CooperationCost { get; set; }
  114 +
  115 + /// <summary>
  116 + /// 店内支出
  117 + /// </summary>
  118 + public decimal StoreExpense { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 洗毛巾费用
  122 + /// </summary>
  123 + public decimal LaundryCost { get; set; }
  124 +
  125 + /// <summary>
  126 + /// 毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾)
  127 + /// </summary>
  128 + public decimal GrossProfit { get; set; }
  129 +
  130 + /// <summary>
  131 + /// 提成比例
  132 + /// </summary>
  133 + public decimal CommissionRate { get; set; }
  134 +
  135 + /// <summary>
  136 + /// 提成金额(基于毛利)
  137 + /// </summary>
  138 + public decimal CommissionAmount { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 底薪金额(固定4000元)
  142 + /// </summary>
  143 + public decimal BaseSalary { get; set; }
  144 +
  145 + /// <summary>
  146 + /// 旗舰店负奖励(800元)
  147 + /// </summary>
  148 + public decimal FlagshipStoreDeduction { get; set; }
  149 +
  150 + /// <summary>
  151 + /// 实际底薪(底薪-考核扣款-旗舰店负奖励)
  152 + /// </summary>
  153 + public decimal ActualBaseSalary { get; set; }
  154 +
  155 + /// <summary>
  156 + /// 在店天数
  157 + /// </summary>
  158 + public int WorkingDays { get; set; }
  159 +
  160 + /// <summary>
  161 + /// 请假天数
  162 + /// </summary>
  163 + public int LeaveDays { get; set; }
  164 +
  165 + /// <summary>
  166 + /// 应发工资(实际底薪+提成)
  167 + /// </summary>
  168 + public decimal GrossSalary { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 实发工资(应发工资-扣款合计+补贴合计+奖金)
  172 + /// </summary>
  173 + public decimal ActualSalary { get; set; }
  174 +
  175 + /// <summary>
  176 + /// 扣款合计
  177 + /// </summary>
  178 + public decimal TotalDeduction { get; set; }
  179 +
  180 + /// <summary>
  181 + /// 补贴合计
  182 + /// </summary>
  183 + public decimal TotalSubsidy { get; set; }
  184 +
  185 + /// <summary>
  186 + /// 发奖金
  187 + /// </summary>
  188 + public decimal Bonus { get; set; }
  189 +
  190 + /// <summary>
  191 + /// 当月是否发放
  192 + /// </summary>
  193 + public string MonthlyPaymentStatus { get; set; }
  194 +
  195 + /// <summary>
  196 + /// 支付金额
  197 + /// </summary>
  198 + public decimal PaidAmount { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 待支付金额
  202 + /// </summary>
  203 + public decimal PendingAmount { get; set; }
  204 +
  205 + /// <summary>
  206 + /// 是否锁定(0未锁定,1已锁定)
  207 + /// </summary>
  208 + public int IsLocked { get; set; }
  209 +
  210 + /// <summary>
  211 + /// 门店类型
  212 + /// </summary>
  213 + public int? StoreType { get; set; }
  214 +
  215 + /// <summary>
  216 + /// 门店类别
  217 + /// </summary>
  218 + public int? StoreCategory { get; set; }
  219 +
  220 + /// <summary>
  221 + /// 是否新店
  222 + /// </summary>
  223 + public string IsNewStore { get; set; }
  224 +
  225 + /// <summary>
  226 + /// 新店保护阶段
  227 + /// </summary>
  228 + public int NewStoreProtectionStage { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 更新时间
  232 + /// </summary>
  233 + public DateTime UpdateTime { get; set; }
  234 + }
  235 +}
  236 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_cooperation_cost
  6 +{
  7 + /// <summary>
  8 + /// 合作成本表
  9 + /// </summary>
  10 + [SugarTable("lq_cooperation_cost")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqCooperationCostEntity
  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_Year")]
  36 + public int Year { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 月份(YYYYMM格式)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_Month")]
  42 + public string Month { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 合计金额
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_TotalAmount", DecimalDigits = 2)]
  48 + public decimal TotalAmount { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 备注说明
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_Remarks")]
  54 + public string Remarks { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 是否有效(1:有效 0:无效)
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_IsEffective")]
  60 + public int IsEffective { get; set; } = 1;
  61 +
  62 + /// <summary>
  63 + /// 创建人ID
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_CreateUser")]
  66 + public string CreateUser { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 创建时间
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_CreateTime")]
  72 + public DateTime? CreateTime { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 更新人ID
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_UpdateUser")]
  78 + public string UpdateUser { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 更新时间
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_UpdateTime")]
  84 + public DateTime? UpdateTime { get; set; }
  85 + }
  86 +}
  87 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs
... ... @@ -128,3 +128,5 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_application
128 128 }
129 129  
130 130  
  131 +
  132 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs
... ... @@ -56,3 +56,5 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_application_node
56 56 }
57 57  
58 58  
  59 +
  60 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs
... ... @@ -74,3 +74,5 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_approval_record
74 74 }
75 75  
76 76  
  77 +
  78 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_store_expense/LqStoreExpenseEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_store_expense
  6 +{
  7 + /// <summary>
  8 + /// 店内支出表
  9 + /// </summary>
  10 + [SugarTable("lq_store_expense")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqStoreExpenseEntity
  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 + /// 支出分类ID(关联lq_reimbursement_category.F_Id)
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "F_ExpenseCategoryId")]
  36 + public string ExpenseCategoryId { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 支出分类名称
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_ExpenseCategoryName")]
  42 + public string ExpenseCategoryName { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 支出日期
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_ExpenseDate")]
  48 + public DateTime ExpenseDate { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 单价
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_UnitPrice", DecimalDigits = 2)]
  54 + public decimal UnitPrice { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 数量
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_Quantity")]
  60 + public int Quantity { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 金额
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_Amount", DecimalDigits = 2)]
  66 + public decimal Amount { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 备注说明
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_Memo")]
  72 + public string Memo { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 附件(JSON格式)
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_Attachment", ColumnDataType = "TEXT")]
  78 + public string Attachment { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 关联报销申请ID(可选)
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_RelatedReimbursementId")]
  84 + public string RelatedReimbursementId { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 关联购买记录ID(可选)
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_RelatedPurchaseRecordId")]
  90 + public string RelatedPurchaseRecordId { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 是否有效(1:有效 0:无效)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_IsEffective")]
  96 + public int IsEffective { get; set; } = 1;
  97 +
  98 + /// <summary>
  99 + /// 创建人ID
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_CreateUser")]
  102 + public string CreateUser { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 创建时间
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_CreateTime")]
  108 + public DateTime? CreateTime { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 更新人ID
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_UpdateUser")]
  114 + public string UpdateUser { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 更新时间
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_UpdateTime")]
  120 + public DateTime? UpdateTime { get; set; }
  121 + }
  122 +}
  123 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_store_manager_salary_statistics/LqStoreManagerSalaryStatisticsEntity.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Const;
  3 +using SqlSugar;
  4 +
  5 +namespace NCC.Extend.Entitys.lq_store_manager_salary_statistics
  6 +{
  7 + /// <summary>
  8 + /// 店长工资统计表
  9 + /// </summary>
  10 + [SugarTable("lq_store_manager_salary_statistics")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class LqStoreManagerSalaryStatisticsEntity
  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
  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_Position")]
  36 + public string Position { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 员工ID
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_EmployeeId")]
  42 + public string EmployeeId { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 员工姓名
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "F_EmployeeName")]
  48 + public string EmployeeName { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 统计月份(YYYYMM)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "F_StatisticsMonth")]
  54 + public string StatisticsMonth { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 门店类型
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "F_StoreType")]
  60 + public int? StoreType { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 门店类别
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "F_StoreCategory")]
  66 + public int? StoreCategory { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 是否新店
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "F_IsNewStore")]
  72 + public string IsNewStore { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 新店保护阶段
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")]
  78 + public int NewStoreProtectionStage { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 门店总业绩
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "F_StoreTotalPerformance")]
  84 + public decimal StoreTotalPerformance { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 门店开单业绩
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "F_StoreBillingPerformance")]
  90 + public decimal StoreBillingPerformance { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 门店退卡业绩
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "F_StoreRefundPerformance")]
  96 + public decimal StoreRefundPerformance { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 门店生命线
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "F_StoreLifeline")]
  102 + public decimal StoreLifeline { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 业绩完成率
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_PerformanceCompletionRate")]
  108 + public decimal PerformanceCompletionRate { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 业绩是否达标
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "F_PerformanceReached")]
  114 + public string PerformanceReached { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 进店消耗人数
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_HeadCount")]
  120 + public int HeadCount { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 目标人头数
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_TargetHeadCount")]
  126 + public decimal TargetHeadCount { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 人头是否达标
  130 + /// </summary>
  131 + [SugarColumn(ColumnName = "F_HeadCountReached")]
  132 + public string HeadCountReached { get; set; }
  133 +
  134 + /// <summary>
  135 + /// 门店消耗金额
  136 + /// </summary>
  137 + [SugarColumn(ColumnName = "F_StoreConsume")]
  138 + public decimal StoreConsume { get; set; }
  139 +
  140 + /// <summary>
  141 + /// 目标消耗金额
  142 + /// </summary>
  143 + [SugarColumn(ColumnName = "F_TargetConsume")]
  144 + public decimal TargetConsume { get; set; }
  145 +
  146 + /// <summary>
  147 + /// 消耗是否达标
  148 + /// </summary>
  149 + [SugarColumn(ColumnName = "F_ConsumeReached")]
  150 + public string ConsumeReached { get; set; }
  151 +
  152 + /// <summary>
  153 + /// 未达标指标数量
  154 + /// </summary>
  155 + [SugarColumn(ColumnName = "F_UnreachedIndicatorCount")]
  156 + public int UnreachedIndicatorCount { get; set; }
  157 +
  158 + /// <summary>
  159 + /// 考核扣款金额
  160 + /// </summary>
  161 + [SugarColumn(ColumnName = "F_AssessmentDeduction")]
  162 + public decimal AssessmentDeduction { get; set; }
  163 +
  164 + /// <summary>
  165 + /// 销售业绩(开单业绩-退款业绩)
  166 + /// </summary>
  167 + [SugarColumn(ColumnName = "F_SalesPerformance")]
  168 + public decimal SalesPerformance { get; set; }
  169 +
  170 + /// <summary>
  171 + /// 产品物料(仓库领用金额)
  172 + /// </summary>
  173 + [SugarColumn(ColumnName = "F_ProductMaterial")]
  174 + public decimal ProductMaterial { get; set; }
  175 +
  176 + /// <summary>
  177 + /// 合作项目成本
  178 + /// </summary>
  179 + [SugarColumn(ColumnName = "F_CooperationCost")]
  180 + public decimal CooperationCost { get; set; }
  181 +
  182 + /// <summary>
  183 + /// 店内支出
  184 + /// </summary>
  185 + [SugarColumn(ColumnName = "F_StoreExpense")]
  186 + public decimal StoreExpense { get; set; }
  187 +
  188 + /// <summary>
  189 + /// 洗毛巾费用
  190 + /// </summary>
  191 + [SugarColumn(ColumnName = "F_LaundryCost")]
  192 + public decimal LaundryCost { get; set; }
  193 +
  194 + /// <summary>
  195 + /// 毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾)
  196 + /// </summary>
  197 + [SugarColumn(ColumnName = "F_GrossProfit")]
  198 + public decimal GrossProfit { get; set; }
  199 +
  200 + /// <summary>
  201 + /// 提成比例
  202 + /// </summary>
  203 + [SugarColumn(ColumnName = "F_CommissionRate")]
  204 + public decimal CommissionRate { get; set; }
  205 +
  206 + /// <summary>
  207 + /// 提成金额(基于毛利)
  208 + /// </summary>
  209 + [SugarColumn(ColumnName = "F_CommissionAmount")]
  210 + public decimal CommissionAmount { get; set; }
  211 +
  212 + /// <summary>
  213 + /// 底薪金额(固定4000元)
  214 + /// </summary>
  215 + [SugarColumn(ColumnName = "F_BaseSalary")]
  216 + public decimal BaseSalary { get; set; }
  217 +
  218 + /// <summary>
  219 + /// 旗舰店负奖励(800元)
  220 + /// </summary>
  221 + [SugarColumn(ColumnName = "F_FlagshipStoreDeduction")]
  222 + public decimal FlagshipStoreDeduction { get; set; }
  223 +
  224 + /// <summary>
  225 + /// 实际底薪(底薪-考核扣款-旗舰店负奖励)
  226 + /// </summary>
  227 + [SugarColumn(ColumnName = "F_ActualBaseSalary")]
  228 + public decimal ActualBaseSalary { get; set; }
  229 +
  230 + /// <summary>
  231 + /// 在店天数
  232 + /// </summary>
  233 + [SugarColumn(ColumnName = "F_WorkingDays")]
  234 + public int WorkingDays { get; set; }
  235 +
  236 + /// <summary>
  237 + /// 请假天数
  238 + /// </summary>
  239 + [SugarColumn(ColumnName = "F_LeaveDays")]
  240 + public int LeaveDays { get; set; }
  241 +
  242 + /// <summary>
  243 + /// 应发工资(实际底薪+提成)
  244 + /// </summary>
  245 + [SugarColumn(ColumnName = "F_GrossSalary")]
  246 + public decimal GrossSalary { get; set; }
  247 +
  248 + /// <summary>
  249 + /// 实发工资(应发工资-扣款合计+补贴合计+奖金)
  250 + /// </summary>
  251 + [SugarColumn(ColumnName = "F_ActualSalary")]
  252 + public decimal ActualSalary { get; set; }
  253 +
  254 + /// <summary>
  255 + /// 缺卡扣款
  256 + /// </summary>
  257 + [SugarColumn(ColumnName = "F_MissingCard")]
  258 + public decimal MissingCard { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 迟到扣款
  262 + /// </summary>
  263 + [SugarColumn(ColumnName = "F_LateArrival")]
  264 + public decimal LateArrival { get; set; }
  265 +
  266 + /// <summary>
  267 + /// 请假扣款
  268 + /// </summary>
  269 + [SugarColumn(ColumnName = "F_LeaveDeduction")]
  270 + public decimal LeaveDeduction { get; set; }
  271 +
  272 + /// <summary>
  273 + /// 扣社保
  274 + /// </summary>
  275 + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")]
  276 + public decimal SocialInsuranceDeduction { get; set; }
  277 +
  278 + /// <summary>
  279 + /// 扣除奖励
  280 + /// </summary>
  281 + [SugarColumn(ColumnName = "F_RewardDeduction")]
  282 + public decimal RewardDeduction { get; set; }
  283 +
  284 + /// <summary>
  285 + /// 扣住宿费
  286 + /// </summary>
  287 + [SugarColumn(ColumnName = "F_AccommodationDeduction")]
  288 + public decimal AccommodationDeduction { get; set; }
  289 +
  290 + /// <summary>
  291 + /// 扣学习期费用
  292 + /// </summary>
  293 + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")]
  294 + public decimal StudyPeriodDeduction { get; set; }
  295 +
  296 + /// <summary>
  297 + /// 扣工作服费用
  298 + /// </summary>
  299 + [SugarColumn(ColumnName = "F_WorkClothesDeduction")]
  300 + public decimal WorkClothesDeduction { get; set; }
  301 +
  302 + /// <summary>
  303 + /// 扣款合计
  304 + /// </summary>
  305 + [SugarColumn(ColumnName = "F_TotalDeduction")]
  306 + public decimal TotalDeduction { get; set; }
  307 +
  308 + /// <summary>
  309 + /// 当月培训补贴
  310 + /// </summary>
  311 + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")]
  312 + public decimal MonthlyTrainingSubsidy { get; set; }
  313 +
  314 + /// <summary>
  315 + /// 当月交通补贴
  316 + /// </summary>
  317 + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")]
  318 + public decimal MonthlyTransportSubsidy { get; set; }
  319 +
  320 + /// <summary>
  321 + /// 上月培训补贴
  322 + /// </summary>
  323 + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")]
  324 + public decimal LastMonthTrainingSubsidy { get; set; }
  325 +
  326 + /// <summary>
  327 + /// 上月交通补贴
  328 + /// </summary>
  329 + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")]
  330 + public decimal LastMonthTransportSubsidy { get; set; }
  331 +
  332 + /// <summary>
  333 + /// 补贴合计
  334 + /// </summary>
  335 + [SugarColumn(ColumnName = "F_TotalSubsidy")]
  336 + public decimal TotalSubsidy { get; set; }
  337 +
  338 + /// <summary>
  339 + /// 发奖金
  340 + /// </summary>
  341 + [SugarColumn(ColumnName = "F_Bonus")]
  342 + public decimal Bonus { get; set; }
  343 +
  344 + /// <summary>
  345 + /// 退手机押金
  346 + /// </summary>
  347 + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")]
  348 + public decimal ReturnPhoneDeposit { get; set; }
  349 +
  350 + /// <summary>
  351 + /// 退住宿押金
  352 + /// </summary>
  353 + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")]
  354 + public decimal ReturnAccommodationDeposit { get; set; }
  355 +
  356 + /// <summary>
  357 + /// 当月是否发放
  358 + /// </summary>
  359 + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")]
  360 + public string MonthlyPaymentStatus { get; set; }
  361 +
  362 + /// <summary>
  363 + /// 支付金额
  364 + /// </summary>
  365 + [SugarColumn(ColumnName = "F_PaidAmount")]
  366 + public decimal PaidAmount { get; set; }
  367 +
  368 + /// <summary>
  369 + /// 待支付金额
  370 + /// </summary>
  371 + [SugarColumn(ColumnName = "F_PendingAmount")]
  372 + public decimal PendingAmount { get; set; }
  373 +
  374 + /// <summary>
  375 + /// 补发上月
  376 + /// </summary>
  377 + [SugarColumn(ColumnName = "F_LastMonthSupplement")]
  378 + public decimal LastMonthSupplement { get; set; }
  379 +
  380 + /// <summary>
  381 + /// 当月支付总额
  382 + /// </summary>
  383 + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")]
  384 + public decimal MonthlyTotalPayment { get; set; }
  385 +
  386 + /// <summary>
  387 + /// 是否锁定(0未锁定,1已锁定)
  388 + /// </summary>
  389 + [SugarColumn(ColumnName = "F_IsLocked")]
  390 + public int IsLocked { get; set; }
  391 +
  392 + /// <summary>
  393 + /// 创建时间
  394 + /// </summary>
  395 + [SugarColumn(ColumnName = "F_CreateTime")]
  396 + public DateTime CreateTime { get; set; }
  397 +
  398 + /// <summary>
  399 + /// 更新时间
  400 + /// </summary>
  401 + [SugarColumn(ColumnName = "F_UpdateTime")]
  402 + public DateTime UpdateTime { get; set; }
  403 +
  404 + /// <summary>
  405 + /// 创建人
  406 + /// </summary>
  407 + [SugarColumn(ColumnName = "F_CreateUser")]
  408 + public string CreateUser { get; set; }
  409 +
  410 + /// <summary>
  411 + /// 更新人
  412 + /// </summary>
  413 + [SugarColumn(ColumnName = "F_UpdateUser")]
  414 + public string UpdateUser { get; set; }
  415 + }
  416 +}
  417 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ILqCooperationCostService.cs 0 → 100644
  1 +namespace NCC.Extend.Interfaces.LqCooperationCost
  2 +{
  3 + public interface ILqCooperationCostService
  4 + {
  5 + }
  6 +}
  7 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ILqStoreExpenseService.cs 0 → 100644
  1 +namespace NCC.Extend.Interfaces.LqStoreExpense
  2 +{
  3 + public interface ILqStoreExpenseService
  4 + {
  5 + }
  6 +}
  7 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs 0 → 100644
  1 +using NCC.Common.Core.Manager;
  2 +using NCC.Common.Enum;
  3 +using NCC.Common.Extension;
  4 +using NCC.Common.Filter;
  5 +using NCC.Dependency;
  6 +using NCC.DynamicApiController;
  7 +using NCC.FriendlyException;
  8 +using NCC.Extend.Interfaces.LqCooperationCost;
  9 +using Mapster;
  10 +using Microsoft.AspNetCore.Mvc;
  11 +using SqlSugar;
  12 +using System;
  13 +using System.Collections.Generic;
  14 +using System.Linq;
  15 +using System.Threading.Tasks;
  16 +using NCC.Extend.Entitys.lq_cooperation_cost;
  17 +using NCC.Extend.Entitys.Dto.LqCooperationCost;
  18 +using NCC.Extend.Entitys.lq_mdxx;
  19 +using Yitter.IdGenerator;
  20 +using NCC.Common.Helper;
  21 +using NCC.Common.Model.NPOI;
  22 +using NCC.Common.Configuration;
  23 +using NCC.DataEncryption;
  24 +using NCC.ClayObject;
  25 +using NCC.Common.Const;
  26 +using NCC.Extend.Entitys.Enum;
  27 +using Microsoft.AspNetCore.Http;
  28 +using System.IO;
  29 +using System.Data;
  30 +
  31 +namespace NCC.Extend.LqCooperationCost
  32 +{
  33 + /// <summary>
  34 + /// 合作成本表服务
  35 + /// </summary>
  36 + [ApiDescriptionSettings(Tag = "Extend", Name = "LqCooperationCost", Order = 200)]
  37 + [Route("api/Extend/[controller]")]
  38 + public class LqCooperationCostService : ILqCooperationCostService, IDynamicApiController, ITransient
  39 + {
  40 + private readonly ISqlSugarRepository<LqCooperationCostEntity> _repository;
  41 + private readonly SqlSugarScope _db;
  42 + private readonly IUserManager _userManager;
  43 +
  44 + /// <summary>
  45 + /// 初始化一个<see cref="LqCooperationCostService"/>类型的新实例
  46 + /// </summary>
  47 + public LqCooperationCostService(
  48 + ISqlSugarRepository<LqCooperationCostEntity> repository,
  49 + IUserManager userManager)
  50 + {
  51 + _repository = repository;
  52 + _db = _repository.Context;
  53 + _userManager = userManager;
  54 + }
  55 +
  56 + /// <summary>
  57 + /// 获取合作成本表详情
  58 + /// </summary>
  59 + /// <param name="id">主键ID</param>
  60 + /// <returns></returns>
  61 + [HttpGet("{id}")]
  62 + public async Task<dynamic> GetInfo(string id)
  63 + {
  64 + var entity = await _db.Queryable<LqCooperationCostEntity>()
  65 + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  66 + .FirstAsync();
  67 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  68 + var output = entity.Adapt<LqCooperationCostInfoOutput>();
  69 + return output;
  70 + }
  71 +
  72 + /// <summary>
  73 + /// 获取合作成本表列表
  74 + /// </summary>
  75 + /// <param name="input">请求参数</param>
  76 + /// <returns></returns>
  77 + [HttpGet("")]
  78 + public async Task<dynamic> GetList([FromQuery] LqCooperationCostListQueryInput input)
  79 + {
  80 + var sidx = input.sidx ?? "CreateTime";
  81 + var sortType = input.sort?.ToLower() == "asc" ? OrderByType.Asc : OrderByType.Desc;
  82 + var query = _db.Queryable<LqCooperationCostEntity>()
  83 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  84 + .WhereIF(!string.IsNullOrEmpty(input.storeId), x => x.StoreId == input.storeId)
  85 + .WhereIF(!string.IsNullOrEmpty(input.storeName), x => x.StoreName.Contains(input.storeName))
  86 + .WhereIF(input.year.HasValue, x => x.Year == input.year.Value)
  87 + .WhereIF(!string.IsNullOrEmpty(input.month), x => x.Month == input.month);
  88 +
  89 + // 处理排序
  90 + switch (sidx.ToLower())
  91 + {
  92 + case "id":
  93 + query = query.OrderBy(x => x.Id, sortType);
  94 + break;
  95 + case "createtime":
  96 + query = query.OrderBy(x => x.CreateTime, sortType);
  97 + break;
  98 + case "storename":
  99 + query = query.OrderBy(x => x.StoreName, sortType);
  100 + break;
  101 + case "year":
  102 + query = query.OrderBy(x => x.Year, sortType);
  103 + break;
  104 + case "month":
  105 + query = query.OrderBy(x => x.Month, sortType);
  106 + break;
  107 + default:
  108 + query = query.OrderBy(x => x.CreateTime, OrderByType.Desc);
  109 + break;
  110 + }
  111 +
  112 + var total = await query.CountAsync();
  113 + var list = await query.ToPageListAsync(input.currentPage, input.pageSize);
  114 +
  115 + var result = list.Select(x => new LqCooperationCostListOutput
  116 + {
  117 + id = x.Id,
  118 + storeId = x.StoreId,
  119 + storeName = x.StoreName,
  120 + year = x.Year,
  121 + month = x.Month,
  122 + totalAmount = x.TotalAmount,
  123 + remarks = x.Remarks,
  124 + createUser = x.CreateUser,
  125 + createTime = x.CreateTime,
  126 + updateUser = x.UpdateUser,
  127 + updateTime = x.UpdateTime
  128 + }).ToList();
  129 +
  130 + return PageResult<LqCooperationCostListOutput>.SqlSugarPageResult(
  131 + new SqlSugarPagedList<LqCooperationCostListOutput>
  132 + {
  133 + list = result,
  134 + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total }
  135 + });
  136 + }
  137 +
  138 + /// <summary>
  139 + /// 获取合作成本表无分页列表
  140 + /// </summary>
  141 + /// <param name="input">请求参数</param>
  142 + /// <returns></returns>
  143 + [HttpGet("Actions/GetNoPagingList")]
  144 + public async Task<List<LqCooperationCostListOutput>> GetNoPagingList([FromQuery] LqCooperationCostListQueryInput input)
  145 + {
  146 + var sidx = input.sidx ?? "CreateTime";
  147 + var sortType = input.sort?.ToLower() == "asc" ? OrderByType.Asc : OrderByType.Desc;
  148 + var query = _db.Queryable<LqCooperationCostEntity>()
  149 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  150 + .WhereIF(!string.IsNullOrEmpty(input.storeId), x => x.StoreId == input.storeId)
  151 + .WhereIF(!string.IsNullOrEmpty(input.storeName), x => x.StoreName.Contains(input.storeName))
  152 + .WhereIF(input.year.HasValue, x => x.Year == input.year.Value)
  153 + .WhereIF(!string.IsNullOrEmpty(input.month), x => x.Month == input.month);
  154 +
  155 + // 处理排序
  156 + switch (sidx.ToLower())
  157 + {
  158 + case "id":
  159 + query = query.OrderBy(x => x.Id, sortType);
  160 + break;
  161 + case "createtime":
  162 + query = query.OrderBy(x => x.CreateTime, sortType);
  163 + break;
  164 + case "storename":
  165 + query = query.OrderBy(x => x.StoreName, sortType);
  166 + break;
  167 + case "year":
  168 + query = query.OrderBy(x => x.Year, sortType);
  169 + break;
  170 + case "month":
  171 + query = query.OrderBy(x => x.Month, sortType);
  172 + break;
  173 + default:
  174 + query = query.OrderBy(x => x.CreateTime, OrderByType.Desc);
  175 + break;
  176 + }
  177 +
  178 + var list = await query
  179 + .Select(x => new LqCooperationCostListOutput
  180 + {
  181 + id = x.Id,
  182 + storeId = x.StoreId,
  183 + storeName = x.StoreName,
  184 + year = x.Year,
  185 + month = x.Month,
  186 + totalAmount = x.TotalAmount,
  187 + remarks = x.Remarks,
  188 + createUser = x.CreateUser,
  189 + createTime = x.CreateTime,
  190 + updateUser = x.UpdateUser,
  191 + updateTime = x.UpdateTime
  192 + })
  193 + .ToListAsync();
  194 + return list;
  195 + }
  196 +
  197 + /// <summary>
  198 + /// 创建合作成本表
  199 + /// </summary>
  200 + /// <param name="input">参数</param>
  201 + /// <returns></returns>
  202 + [HttpPost("")]
  203 + public async Task Create([FromBody] LqCooperationCostCrInput input)
  204 + {
  205 + var userInfo = await _userManager.GetUserInfo();
  206 + var entity = input.Adapt<LqCooperationCostEntity>();
  207 + entity.Id = YitIdHelper.NextId().ToString();
  208 + entity.IsEffective = StatusEnum.有效.GetHashCode();
  209 + entity.CreateUser = _userManager.UserId;
  210 + entity.CreateTime = DateTime.Now;
  211 +
  212 + // 如果未提供门店名称,根据门店ID查询
  213 + if (string.IsNullOrEmpty(entity.StoreName) && !string.IsNullOrEmpty(entity.StoreId))
  214 + {
  215 + var store = await _db.Queryable<LqMdxxEntity>()
  216 + .Where(x => x.Id == entity.StoreId)
  217 + .Select(x => x.Dm)
  218 + .FirstAsync();
  219 + entity.StoreName = store;
  220 + }
  221 +
  222 + var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
  223 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  224 + }
  225 +
  226 + /// <summary>
  227 + /// 更新合作成本表
  228 + /// </summary>
  229 + /// <param name="id">主键</param>
  230 + /// <param name="input">参数</param>
  231 + /// <returns></returns>
  232 + [HttpPut("{id}")]
  233 + public async Task Update(string id, [FromBody] LqCooperationCostUpInput input)
  234 + {
  235 + var entity = await _db.Queryable<LqCooperationCostEntity>()
  236 + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  237 + .FirstAsync();
  238 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  239 +
  240 + entity.StoreId = input.storeId;
  241 + entity.StoreName = input.storeName;
  242 + entity.Year = input.year;
  243 + entity.Month = input.month;
  244 + entity.TotalAmount = input.totalAmount;
  245 + entity.Remarks = input.remarks;
  246 + entity.UpdateUser = _userManager.UserId;
  247 + entity.UpdateTime = DateTime.Now;
  248 +
  249 + // 如果未提供门店名称,根据门店ID查询
  250 + if (string.IsNullOrEmpty(entity.StoreName) && !string.IsNullOrEmpty(entity.StoreId))
  251 + {
  252 + var store = await _db.Queryable<LqMdxxEntity>()
  253 + .Where(x => x.Id == entity.StoreId)
  254 + .Select(x => x.Dm)
  255 + .FirstAsync();
  256 + entity.StoreName = store;
  257 + }
  258 +
  259 + var isOk = await _db.Updateable(entity).ExecuteCommandAsync();
  260 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001);
  261 + }
  262 +
  263 + /// <summary>
  264 + /// 删除合作成本表
  265 + /// </summary>
  266 + /// <param name="id">主键</param>
  267 + /// <returns></returns>
  268 + [HttpDelete("{id}")]
  269 + public async Task Delete(string id)
  270 + {
  271 + var entity = await _db.Queryable<LqCooperationCostEntity>()
  272 + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  273 + .FirstAsync();
  274 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  275 +
  276 + // 逻辑删除
  277 + entity.IsEffective = StatusEnum.无效.GetHashCode();
  278 + entity.UpdateUser = _userManager.UserId;
  279 + entity.UpdateTime = DateTime.Now;
  280 +
  281 + var isOk = await _db.Updateable(entity).ExecuteCommandAsync();
  282 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1002);
  283 + }
  284 +
  285 + /// <summary>
  286 + /// 导出合作成本表
  287 + /// </summary>
  288 + /// <param name="input">请求参数</param>
  289 + /// <returns></returns>
  290 + [HttpGet("Actions/Export")]
  291 + public async Task<dynamic> Export([FromQuery] LqCooperationCostListQueryInput input)
  292 + {
  293 + var userInfo = await _userManager.GetUserInfo();
  294 + var exportData = new List<LqCooperationCostListOutput>();
  295 + if (input.dataType == 0)
  296 + {
  297 + var data = Clay.Object(await this.GetList(input));
  298 + exportData = data.Solidify<PageResult<LqCooperationCostListOutput>>().list;
  299 + }
  300 + else
  301 + {
  302 + exportData = await this.GetNoPagingList(input);
  303 + }
  304 + List<ParamsModel> paramList = "[{\"value\":\"门店ID\",\"field\":\"storeId\"},{\"value\":\"门店名称\",\"field\":\"storeName\"},{\"value\":\"年份\",\"field\":\"year\"},{\"value\":\"月份\",\"field\":\"month\"},{\"value\":\"合计金额\",\"field\":\"totalAmount\"},{\"value\":\"备注说明\",\"field\":\"remarks\"},{\"value\":\"创建人\",\"field\":\"createUser\"},{\"value\":\"创建时间\",\"field\":\"createTime\"},]".ToList<ParamsModel>();
  305 + ExcelConfig excelconfig = new ExcelConfig();
  306 + excelconfig.FileName = "合作成本表.xls";
  307 + excelconfig.HeadFont = "微软雅黑";
  308 + excelconfig.HeadPoint = 10;
  309 + excelconfig.IsAllSizeColumn = true;
  310 + excelconfig.ColumnModel = new List<ExcelColumnModel>();
  311 + List<string> selectKeyList = !string.IsNullOrEmpty(input.selectKey) ? input.selectKey.Split(',').ToList() : paramList.Select(p => p.field).ToList();
  312 + foreach (var item in selectKeyList)
  313 + {
  314 + var isExist = paramList.Find(p => p.field == item);
  315 + if (isExist != null)
  316 + {
  317 + excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = isExist.field, ExcelColumn = isExist.value });
  318 + }
  319 + }
  320 + var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName;
  321 + ExcelExportHelper<LqCooperationCostListOutput>.Export(exportData, excelconfig, addPath);
  322 + var fileName = _userManager.UserId + "|" + addPath + "|xls";
  323 + var output = new
  324 + {
  325 + name = excelconfig.FileName,
  326 + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC")
  327 + };
  328 + return output;
  329 + }
  330 +
  331 + /// <summary>
  332 + /// 导入合作成本数据
  333 + /// </summary>
  334 + /// <remarks>
  335 + /// 从Excel文件导入合作成本数据
  336 + ///
  337 + /// Excel格式要求:
  338 + /// 第一行为标题行:门店ID、门店名称、年份、月份、合计金额、备注说明
  339 + /// 从第二行开始为数据行
  340 + ///
  341 + /// 示例请求:
  342 + /// POST /api/Extend/LqCooperationCost/Actions/Import
  343 + /// Content-Type: multipart/form-data
  344 + /// </remarks>
  345 + /// <param name="file">Excel文件</param>
  346 + /// <returns>导入结果</returns>
  347 + /// <response code="200">导入成功</response>
  348 + /// <response code="400">文件格式错误或数据验证失败</response>
  349 + [HttpPost("Actions/Import")]
  350 + public async Task<dynamic> Import(IFormFile file)
  351 + {
  352 + try
  353 + {
  354 + if (file == null || file.Length == 0)
  355 + {
  356 + throw NCCException.Oh("请选择要上传的Excel文件");
  357 + }
  358 +
  359 + // 检查文件格式
  360 + var allowedExtensions = new[] { ".xlsx", ".xls" };
  361 + var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();
  362 + if (!allowedExtensions.Contains(fileExtension))
  363 + {
  364 + throw NCCException.Oh("只支持.xlsx和.xls格式的Excel文件");
  365 + }
  366 +
  367 + var successCount = 0;
  368 + var failCount = 0;
  369 + var failMessages = new List<string>();
  370 +
  371 + // 保存临时文件
  372 + var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName));
  373 + try
  374 + {
  375 + using (var stream = new FileStream(tempFilePath, FileMode.Create))
  376 + {
  377 + await file.CopyToAsync(stream);
  378 + }
  379 +
  380 + // 使用ExcelImportHelper读取Excel文件(第一行为标题行)
  381 + var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0);
  382 +
  383 + if (dataTable.Rows.Count <= 1)
  384 + {
  385 + throw NCCException.Oh("Excel文件中没有数据行(至少需要标题行和一行数据)");
  386 + }
  387 +
  388 + // 从第1行开始读取数据(跳过标题行)
  389 + for (int i = 1; i < dataTable.Rows.Count; i++)
  390 + {
  391 + try
  392 + {
  393 + var row = dataTable.Rows[i];
  394 + var storeId = row[0]?.ToString()?.Trim();
  395 + var storeName = row[1]?.ToString()?.Trim();
  396 + var yearText = row[2]?.ToString()?.Trim();
  397 + var monthText = row[3]?.ToString()?.Trim();
  398 + var totalAmountText = row[4]?.ToString()?.Trim();
  399 + var remarks = row[5]?.ToString()?.Trim();
  400 +
  401 + // 跳过空行
  402 + if (string.IsNullOrEmpty(storeId) && string.IsNullOrEmpty(storeName))
  403 + {
  404 + continue;
  405 + }
  406 +
  407 + // 验证必填字段
  408 + if (string.IsNullOrEmpty(storeId))
  409 + {
  410 + failMessages.Add($"第{i + 1}行:门店ID不能为空");
  411 + failCount++;
  412 + continue;
  413 + }
  414 +
  415 + if (string.IsNullOrEmpty(yearText) || !int.TryParse(yearText, out int year))
  416 + {
  417 + failMessages.Add($"第{i + 1}行:年份格式错误(应为数字)");
  418 + failCount++;
  419 + continue;
  420 + }
  421 +
  422 + if (string.IsNullOrEmpty(monthText) || monthText.Length != 6)
  423 + {
  424 + failMessages.Add($"第{i + 1}行:月份格式错误(应为YYYYMM格式,如:202501)");
  425 + failCount++;
  426 + continue;
  427 + }
  428 +
  429 + if (string.IsNullOrEmpty(totalAmountText) || !decimal.TryParse(totalAmountText, out decimal totalAmount))
  430 + {
  431 + failMessages.Add($"第{i + 1}行:合计金额格式错误(应为数字)");
  432 + failCount++;
  433 + continue;
  434 + }
  435 +
  436 + // 如果未提供门店名称,根据门店ID查询
  437 + if (string.IsNullOrEmpty(storeName))
  438 + {
  439 + var store = await _db.Queryable<LqMdxxEntity>()
  440 + .Where(x => x.Id == storeId)
  441 + .Select(x => x.Dm)
  442 + .FirstAsync();
  443 + storeName = store ?? "";
  444 + }
  445 +
  446 + // 检查是否已存在相同门店、年份、月份的记录
  447 + var exists = await _db.Queryable<LqCooperationCostEntity>()
  448 + .Where(x => x.StoreId == storeId && x.Year == year && x.Month == monthText && x.IsEffective == StatusEnum.有效.GetHashCode())
  449 + .AnyAsync();
  450 +
  451 + if (exists)
  452 + {
  453 + failMessages.Add($"第{i + 1}行:该门店{year}年{monthText}月的记录已存在");
  454 + failCount++;
  455 + continue;
  456 + }
  457 +
  458 + // 创建记录
  459 + var entity = new LqCooperationCostEntity
  460 + {
  461 + Id = YitIdHelper.NextId().ToString(),
  462 + StoreId = storeId,
  463 + StoreName = storeName,
  464 + Year = year,
  465 + Month = monthText,
  466 + TotalAmount = totalAmount,
  467 + Remarks = remarks,
  468 + IsEffective = StatusEnum.有效.GetHashCode(),
  469 + CreateUser = _userManager.UserId,
  470 + CreateTime = DateTime.Now
  471 + };
  472 +
  473 + var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
  474 + if (isOk > 0)
  475 + {
  476 + successCount++;
  477 + }
  478 + else
  479 + {
  480 + failMessages.Add($"第{i + 1}行:保存失败");
  481 + failCount++;
  482 + }
  483 + }
  484 + catch (Exception ex)
  485 + {
  486 + failMessages.Add($"第{i + 1}行:{ex.Message}");
  487 + failCount++;
  488 + }
  489 + }
  490 + }
  491 + finally
  492 + {
  493 + // 清理临时文件
  494 + if (File.Exists(tempFilePath))
  495 + {
  496 + File.Delete(tempFilePath);
  497 + }
  498 + }
  499 +
  500 + return new
  501 + {
  502 + success = true,
  503 + message = $"导入完成:成功{successCount}条,失败{failCount}条",
  504 + successCount = successCount,
  505 + failCount = failCount,
  506 + failMessages = failMessages
  507 + };
  508 + }
  509 + catch (Exception ex)
  510 + {
  511 + throw NCCException.Oh($"导入失败:{ex.Message}");
  512 + }
  513 + }
  514 + }
  515 +}
  516 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
... ... @@ -1470,5 +1470,233 @@ namespace NCC.Extend.LqReimbursementApplication
1470 1470  
1471 1471 return new List<object>();
1472 1472 }
  1473 +
  1474 + /// <summary>
  1475 + /// 导出本月已审核通过的报销表明细
  1476 + /// </summary>
  1477 + /// <remarks>
  1478 + /// 导出本月已审核通过的报销申请及其关联的购买记录明细
  1479 + /// 用于线下整理后导入到店内支出表
  1480 + ///
  1481 + /// 示例请求:
  1482 + /// GET /api/Extend/LqReimbursementApplication/Actions/ExportApprovedDetails?year=2025&month=01
  1483 + /// </remarks>
  1484 + /// <param name="year">年份(可选,默认当前年份)</param>
  1485 + /// <param name="month">月份(可选,默认当前月份,格式:01-12)</param>
  1486 + /// <returns>导出文件信息</returns>
  1487 + /// <response code="200">导出成功</response>
  1488 + /// <response code="500">服务器错误</response>
  1489 + [HttpGet("Actions/ExportApprovedDetails")]
  1490 + public async Task<dynamic> ExportApprovedDetails([FromQuery] int? year = null, [FromQuery] string month = null)
  1491 + {
  1492 + try
  1493 + {
  1494 + var userInfo = await _userManager.GetUserInfo();
  1495 + var now = DateTime.Now;
  1496 + var queryYear = year ?? now.Year;
  1497 + var queryMonth = !string.IsNullOrEmpty(month) ? month : now.ToString("MM");
  1498 +
  1499 + // 构建月份字符串(YYYYMM格式)
  1500 + var monthStr = $"{queryYear}{queryMonth}";
  1501 +
  1502 + // 计算月份的开始和结束日期
  1503 + var startDate = new DateTime(queryYear, int.Parse(queryMonth), 1);
  1504 + var endDate = startDate.AddMonths(1).AddDays(-1);
  1505 +
  1506 + // 查询本月已审核通过的报销申请
  1507 + var applications = await _db.Queryable<LqReimbursementApplicationEntity>()
  1508 + .Where(x => (x.ApprovalStatus ?? x.ApproveStatus) == "已通过")
  1509 + .Where(x => x.ApplicationTime.HasValue &&
  1510 + x.ApplicationTime.Value.Year == queryYear &&
  1511 + x.ApplicationTime.Value.Month == int.Parse(queryMonth))
  1512 + .ToListAsync();
  1513 +
  1514 + // 获取所有关联的购买记录
  1515 + var applicationIds = applications.Select(x => x.Id).ToList();
  1516 + var purchaseRecords = new List<LqPurchaseRecordsEntity>();
  1517 + if (applicationIds.Any())
  1518 + {
  1519 + purchaseRecords = await _db.Queryable<LqPurchaseRecordsEntity>()
  1520 + .Where(x => applicationIds.Contains(x.ApplicationId))
  1521 + .OrderBy(x => x.ApplicationId)
  1522 + .OrderBy(x => x.CreateTime)
  1523 + .ToListAsync();
  1524 + }
  1525 +
  1526 + // 获取门店信息
  1527 + var storeIds = applications.Where(x => !string.IsNullOrEmpty(x.ApplicationStoreId))
  1528 + .Select(x => x.ApplicationStoreId)
  1529 + .Distinct()
  1530 + .ToList();
  1531 + var stores = new Dictionary<string, string>();
  1532 + if (storeIds.Any())
  1533 + {
  1534 + var storeList = await _db.Queryable<LqMdxxEntity>()
  1535 + .Where(x => storeIds.Contains(x.Id))
  1536 + .Select(x => new { x.Id, x.Dm })
  1537 + .ToListAsync();
  1538 + stores = storeList.ToDictionary(x => x.Id, x => x.Dm ?? "");
  1539 + }
  1540 +
  1541 + // 组装导出数据(包含报销申请和购买记录明细)
  1542 + var exportData = new List<ReimbursementDetailExportOutput>();
  1543 + foreach (var app in applications)
  1544 + {
  1545 + var appPurchaseRecords = purchaseRecords.Where(x => x.ApplicationId == app.Id).ToList();
  1546 +
  1547 + if (appPurchaseRecords.Any())
  1548 + {
  1549 + // 每个购买记录作为一行
  1550 + foreach (var pr in appPurchaseRecords)
  1551 + {
  1552 + exportData.Add(new ReimbursementDetailExportOutput
  1553 + {
  1554 + applicationId = app.Id,
  1555 + applicationUserName = app.ApplicationUserName,
  1556 + applicationStoreId = app.ApplicationStoreId,
  1557 + applicationStoreName = !string.IsNullOrEmpty(app.ApplicationStoreId) && stores.ContainsKey(app.ApplicationStoreId)
  1558 + ? stores[app.ApplicationStoreId]
  1559 + : "",
  1560 + applicationTime = app.ApplicationTime,
  1561 + applicationAmount = !string.IsNullOrEmpty(app.Amount) ? decimal.Parse(app.Amount) : 0m,
  1562 + purchaseRecordId = pr.Id,
  1563 + reimbursementCategoryId = pr.ReimbursementCategoryId,
  1564 + reimbursementCategoryName = pr.ReimbursementCategoryName,
  1565 + unitPrice = pr.UnitPrice,
  1566 + quantity = pr.Quantity ?? 0,
  1567 + amount = pr.Amount,
  1568 + memo = pr.Memo,
  1569 + purchaseTime = pr.PurchaseTime
  1570 + });
  1571 + }
  1572 + }
  1573 + else
  1574 + {
  1575 + // 如果没有购买记录,至少导出报销申请基本信息
  1576 + exportData.Add(new ReimbursementDetailExportOutput
  1577 + {
  1578 + applicationId = app.Id,
  1579 + applicationUserName = app.ApplicationUserName,
  1580 + applicationStoreId = app.ApplicationStoreId,
  1581 + applicationStoreName = !string.IsNullOrEmpty(app.ApplicationStoreId) && stores.ContainsKey(app.ApplicationStoreId)
  1582 + ? stores[app.ApplicationStoreId]
  1583 + : "",
  1584 + applicationTime = app.ApplicationTime,
  1585 + applicationAmount = !string.IsNullOrEmpty(app.Amount) ? decimal.Parse(app.Amount) : 0m,
  1586 + purchaseRecordId = "",
  1587 + reimbursementCategoryId = "",
  1588 + reimbursementCategoryName = "",
  1589 + unitPrice = 0m,
  1590 + quantity = 0,
  1591 + amount = 0m,
  1592 + memo = "",
  1593 + purchaseTime = null
  1594 + });
  1595 + }
  1596 + }
  1597 +
  1598 + // 导出Excel
  1599 + List<ParamsModel> paramList = "[{\"value\":\"报销申请ID\",\"field\":\"applicationId\"},{\"value\":\"申请人姓名\",\"field\":\"applicationUserName\"},{\"value\":\"门店ID\",\"field\":\"applicationStoreId\"},{\"value\":\"门店名称\",\"field\":\"applicationStoreName\"},{\"value\":\"申请时间\",\"field\":\"applicationTime\"},{\"value\":\"申请金额\",\"field\":\"applicationAmount\"},{\"value\":\"购买记录ID\",\"field\":\"purchaseRecordId\"},{\"value\":\"支出分类ID\",\"field\":\"reimbursementCategoryId\"},{\"value\":\"支出分类名称\",\"field\":\"reimbursementCategoryName\"},{\"value\":\"单价\",\"field\":\"unitPrice\"},{\"value\":\"数量\",\"field\":\"quantity\"},{\"value\":\"金额\",\"field\":\"amount\"},{\"value\":\"备注说明\",\"field\":\"memo\"},{\"value\":\"购买时间\",\"field\":\"purchaseTime\"},]".ToList<ParamsModel>();
  1600 + ExcelConfig excelconfig = new ExcelConfig();
  1601 + excelconfig.FileName = $"报销表明细_{queryYear}年{queryMonth}月.xls";
  1602 + excelconfig.HeadFont = "微软雅黑";
  1603 + excelconfig.HeadPoint = 10;
  1604 + excelconfig.IsAllSizeColumn = true;
  1605 + excelconfig.ColumnModel = new List<ExcelColumnModel>();
  1606 + foreach (var param in paramList)
  1607 + {
  1608 + excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = param.field, ExcelColumn = param.value });
  1609 + }
  1610 + var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName;
  1611 + ExcelExportHelper<ReimbursementDetailExportOutput>.Export(exportData, excelconfig, addPath);
  1612 + var fileName = _userManager.UserId + "|" + addPath + "|xls";
  1613 + var output = new
  1614 + {
  1615 + name = excelconfig.FileName,
  1616 + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC")
  1617 + };
  1618 + return output;
  1619 + }
  1620 + catch (Exception ex)
  1621 + {
  1622 + throw NCCException.Oh($"导出失败:{ex.Message}");
  1623 + }
  1624 + }
  1625 + }
  1626 +
  1627 + /// <summary>
  1628 + /// 报销表明细导出输出
  1629 + /// </summary>
  1630 + public class ReimbursementDetailExportOutput
  1631 + {
  1632 + /// <summary>
  1633 + /// 报销申请ID
  1634 + /// </summary>
  1635 + public string applicationId { get; set; }
  1636 +
  1637 + /// <summary>
  1638 + /// 申请人姓名
  1639 + /// </summary>
  1640 + public string applicationUserName { get; set; }
  1641 +
  1642 + /// <summary>
  1643 + /// 门店ID
  1644 + /// </summary>
  1645 + public string applicationStoreId { get; set; }
  1646 +
  1647 + /// <summary>
  1648 + /// 门店名称
  1649 + /// </summary>
  1650 + public string applicationStoreName { get; set; }
  1651 +
  1652 + /// <summary>
  1653 + /// 申请时间
  1654 + /// </summary>
  1655 + public DateTime? applicationTime { get; set; }
  1656 +
  1657 + /// <summary>
  1658 + /// 申请金额
  1659 + /// </summary>
  1660 + public decimal applicationAmount { get; set; }
  1661 +
  1662 + /// <summary>
  1663 + /// 购买记录ID
  1664 + /// </summary>
  1665 + public string purchaseRecordId { get; set; }
  1666 +
  1667 + /// <summary>
  1668 + /// 支出分类ID
  1669 + /// </summary>
  1670 + public string reimbursementCategoryId { get; set; }
  1671 +
  1672 + /// <summary>
  1673 + /// 支出分类名称
  1674 + /// </summary>
  1675 + public string reimbursementCategoryName { get; set; }
  1676 +
  1677 + /// <summary>
  1678 + /// 单价
  1679 + /// </summary>
  1680 + public decimal unitPrice { get; set; }
  1681 +
  1682 + /// <summary>
  1683 + /// 数量
  1684 + /// </summary>
  1685 + public int quantity { get; set; }
  1686 +
  1687 + /// <summary>
  1688 + /// 金额
  1689 + /// </summary>
  1690 + public decimal amount { get; set; }
  1691 +
  1692 + /// <summary>
  1693 + /// 备注说明
  1694 + /// </summary>
  1695 + public string memo { get; set; }
  1696 +
  1697 + /// <summary>
  1698 + /// 购买时间
  1699 + /// </summary>
  1700 + public DateTime? purchaseTime { get; set; }
1473 1701 }
1474 1702 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
... ... @@ -4070,12 +4070,14 @@ namespace NCC.Extend.LqStatistics
4070 4070 var whereClause = whereConditions.Any() ? "WHERE " + string.Join(" AND ", whereConditions) : "";
4071 4071  
4072 4072 // 使用子查询优化性能,避免复杂的JOIN和GROUP BY
  4073 + // 注意:邀约、预约、消耗、开单表存储的是会员ID(F_MemberId),不是线索池ID(F_Id)
  4074 + // 需要通过线索池的F_MemberId去关联这些表
4073 4075 var sql = $@"
4074 4076 SELECT
4075 4077 tk.F_Id as LeadCustomerId,
4076 4078 tk.F_CustomerName as CustomerName,
4077 4079 tk.F_ExpansionTime as ExpansionTime,
4078   - -- 是否邀约:通过拓客编号关联
  4080 + -- 是否邀约:通过会员ID关联(邀约表的yykh字段存储的是会员ID)
4079 4081 CASE WHEN yaoy_stats.has_invite = 1 THEN '是' ELSE '否' END as HasInvite,
4080 4082 -- 是否预约:通过邀约ID关联(只统计通过邀约产生的预约)
4081 4083 CASE WHEN yy_stats.has_appointment = 1 THEN '是' ELSE '否' END as HasAppointment,
... ... @@ -4096,37 +4098,41 @@ namespace NCC.Extend.LqStatistics
4096 4098 -- 实际开单记录数(不管是否通过预约产生)
4097 4099 COALESCE(kd_actual.count, 0) as ActualBillingCount
4098 4100 FROM lq_tkjlb tk
4099   - -- 邀约统计子查询
  4101 + -- 邀约统计子查询(通过会员ID关联:邀约表的yykh字段存储的是会员ID)
4100 4102 LEFT JOIN (
4101 4103 SELECT
4102   - yaoy.tkbh as tk_id,
  4104 + yaoy.yykh as member_id,
4103 4105 1 as has_invite
4104 4106 FROM lq_yaoyjl yaoy
4105   - GROUP BY yaoy.tkbh
4106   - ) yaoy_stats ON yaoy_stats.tk_id = tk.F_Id
  4107 + WHERE yaoy.yykh IS NOT NULL
  4108 + GROUP BY yaoy.yykh
  4109 + ) yaoy_stats ON yaoy_stats.member_id = tk.F_MemberId
4107 4110 -- 预约统计子查询(只统计通过邀约产生的预约)
  4111 + -- 通过会员ID关联:线索池 -> 邀约(通过会员ID) -> 预约(通过邀约ID,且会员ID匹配)
4108 4112 LEFT JOIN (
4109 4113 SELECT
4110 4114 tk_inner.F_MemberId as member_id,
4111 4115 1 as has_appointment,
4112 4116 MAX(yy.F_NoDealRemark) as no_billing_reason
4113 4117 FROM lq_tkjlb tk_inner
4114   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id
4115   - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id
  4118 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
  4119 + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
4116 4120 GROUP BY tk_inner.F_MemberId
4117 4121 ) yy_stats ON yy_stats.member_id = tk.F_MemberId
4118 4122 -- 消耗统计子查询(只统计通过预约产生的耗卡)
  4123 + -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 消耗(通过预约ID,且会员ID匹配)
4119 4124 LEFT JOIN (
4120 4125 SELECT
4121 4126 tk_inner.F_MemberId as member_id,
4122 4127 1 as has_consume
4123 4128 FROM lq_tkjlb tk_inner
4124   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id
4125   - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id
4126   - INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1
  4129 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
  4130 + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
  4131 + INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.hy = tk_inner.F_MemberId AND xh.F_IsEffective = 1
4127 4132 GROUP BY tk_inner.F_MemberId
4128 4133 ) xh_stats ON xh_stats.member_id = tk.F_MemberId
4129 4134 -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项)
  4135 + -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配)
4130 4136 LEFT JOIN (
4131 4137 SELECT
4132 4138 tk_inner.F_MemberId as member_id,
... ... @@ -4134,9 +4140,9 @@ namespace NCC.Extend.LqStatistics
4134 4140 SUM(kd.zdyj) as billing_amount,
4135 4141 GROUP_CONCAT(DISTINCT kdpx.pxmc SEPARATOR '、') as billing_items
4136 4142 FROM lq_tkjlb tk_inner
4137   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id
4138   - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id
4139   - INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.F_IsEffective = 1
  4143 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
  4144 + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
  4145 + INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.kdhy = tk_inner.F_MemberId AND kd.F_IsEffective = 1
4140 4146 LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1
4141 4147 GROUP BY tk_inner.F_MemberId
4142 4148 ) kd_stats ON kd_stats.member_id = tk.F_MemberId
... ... @@ -4186,40 +4192,69 @@ namespace NCC.Extend.LqStatistics
4186 4192 var result = await _db.Ado.SqlQueryAsync<LeadCustomerStatisticsListOutput>(sql, parameters);
4187 4193  
4188 4194 // 生成问题分析说明
  4195 + // 完整链路1:邀约 -> 预约 -> 开单
  4196 + // 完整链路2:邀约 -> 预约 -> 消耗
4189 4197 foreach (var item in result)
4190 4198 {
4191 4199 var analysisList = new List<string>();
  4200 + var completeChains = new List<string>();
  4201 +
  4202 + // 判断是否形成完整链路
  4203 + if (item.HasInvite == "是" && item.HasAppointment == "是")
  4204 + {
  4205 + if (item.HasBilling == "是")
  4206 + {
  4207 + completeChains.Add("邀约->预约->开单");
  4208 + }
  4209 + if (item.HasConsume == "是")
  4210 + {
  4211 + completeChains.Add("邀约->预约->消耗");
  4212 + }
  4213 + }
4192 4214  
  4215 + // 如果有完整链路,先说明链路完整
  4216 + if (completeChains.Count > 0)
  4217 + {
  4218 + analysisList.Add($"✓ 链路完整:{string.Join("、", completeChains)}");
  4219 + }
  4220 +
  4221 + // 有预约记录,但未通过邀约产生
4193 4222 if (item.HasInvite == "否" && item.HasAppointment == "否" && item.ActualAppointmentCount > 0)
4194 4223 {
4195 4224 analysisList.Add($"有{item.ActualAppointmentCount}条预约记录,但未通过邀约产生(F_InviteId为null)");
4196 4225 }
4197 4226  
  4227 + // 有消耗记录,但未通过完整链路产生(邀约->预约->消耗)
4198 4228 if (item.HasAppointment == "否" && item.HasConsume == "否" && item.ActualConsumeCount > 0)
4199 4229 {
4200   - analysisList.Add($"有{item.ActualConsumeCount}条消耗记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)");
  4230 + analysisList.Add($"有{item.ActualConsumeCount}条消耗记录,但未通过完整链路产生(邀约->预约->消耗)");
4201 4231 }
4202 4232  
  4233 + // 有开单记录,但未通过完整链路产生(邀约->预约->开单)
4203 4234 if (item.HasAppointment == "否" && item.HasBilling == "否" && item.ActualBillingCount > 0)
4204 4235 {
4205   - analysisList.Add($"有{item.ActualBillingCount}条开单记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)");
  4236 + analysisList.Add($"有{item.ActualBillingCount}条开单记录,但未通过完整链路产生(邀约->预约->开单)");
4206 4237 }
4207 4238  
  4239 + // 有邀约记录,有预约记录,但预约记录的F_InviteId未关联到邀约记录
4208 4240 if (item.HasInvite == "是" && item.HasAppointment == "否" && item.ActualAppointmentCount > 0)
4209 4241 {
4210 4242 analysisList.Add($"有邀约记录,有{item.ActualAppointmentCount}条预约记录,但预约记录的F_InviteId未关联到邀约记录");
4211 4243 }
4212 4244  
  4245 + // 有预约记录(通过邀约产生),有消耗记录,但消耗记录的F_AppointmentId未关联到预约记录
4213 4246 if (item.HasAppointment == "是" && item.HasConsume == "否" && item.ActualConsumeCount > 0)
4214 4247 {
4215   - analysisList.Add($"有预约记录,有{item.ActualConsumeCount}条消耗记录,但消耗记录的F_AppointmentId未关联到预约记录");
  4248 + analysisList.Add($"有预约记录(通过邀约产生),有{item.ActualConsumeCount}条消耗记录,但消耗记录的F_AppointmentId未关联到预约记录,未形成完整链路(邀约->预约->消耗)");
4216 4249 }
4217 4250  
  4251 + // 有预约记录(通过邀约产生),有开单记录,但开单记录的F_AppointmentId未关联到预约记录
4218 4252 if (item.HasAppointment == "是" && item.HasBilling == "否" && item.ActualBillingCount > 0)
4219 4253 {
4220   - analysisList.Add($"有预约记录,有{item.ActualBillingCount}条开单记录,但开单记录的F_AppointmentId未关联到预约记录");
  4254 + analysisList.Add($"有预约记录(通过邀约产生),有{item.ActualBillingCount}条开单记录,但开单记录的F_AppointmentId未关联到预约记录,未形成完整链路(邀约->预约->开单)");
4221 4255 }
4222 4256  
  4257 + // 生成最终分析说明
4223 4258 if (analysisList.Count == 0)
4224 4259 {
4225 4260 item.Analysis = "数据正常,符合业务链路:拓客 -> 邀约 -> 预约 -> 开单/消耗";
... ... @@ -4394,37 +4429,41 @@ namespace NCC.Extend.LqStatistics
4394 4429 -- 实际开单记录数(不管是否通过预约产生)
4395 4430 COALESCE(kd_actual.count, 0) as ActualBillingCount
4396 4431 FROM lq_tkjlb tk
4397   - -- 邀约统计子查询
  4432 + -- 邀约统计子查询(通过会员ID关联:邀约表的yykh字段存储的是会员ID)
4398 4433 LEFT JOIN (
4399 4434 SELECT
4400   - yaoy.tkbh as tk_id,
  4435 + yaoy.yykh as member_id,
4401 4436 1 as has_invite
4402 4437 FROM lq_yaoyjl yaoy
4403   - GROUP BY yaoy.tkbh
4404   - ) yaoy_stats ON yaoy_stats.tk_id = tk.F_Id
  4438 + WHERE yaoy.yykh IS NOT NULL
  4439 + GROUP BY yaoy.yykh
  4440 + ) yaoy_stats ON yaoy_stats.member_id = tk.F_MemberId
4405 4441 -- 预约统计子查询(只统计通过邀约产生的预约)
  4442 + -- 通过会员ID关联:线索池 -> 邀约(通过会员ID) -> 预约(通过邀约ID,且会员ID匹配)
4406 4443 LEFT JOIN (
4407 4444 SELECT
4408 4445 tk_inner.F_MemberId as member_id,
4409 4446 1 as has_appointment,
4410 4447 MAX(yy.F_NoDealRemark) as no_billing_reason
4411 4448 FROM lq_tkjlb tk_inner
4412   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id
4413   - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id
  4449 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
  4450 + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
4414 4451 GROUP BY tk_inner.F_MemberId
4415 4452 ) yy_stats ON yy_stats.member_id = tk.F_MemberId
4416 4453 -- 消耗统计子查询(只统计通过预约产生的耗卡)
  4454 + -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 消耗(通过预约ID,且会员ID匹配)
4417 4455 LEFT JOIN (
4418 4456 SELECT
4419 4457 tk_inner.F_MemberId as member_id,
4420 4458 1 as has_consume
4421 4459 FROM lq_tkjlb tk_inner
4422   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id
4423   - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id
4424   - INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1
  4460 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
  4461 + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
  4462 + INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.hy = tk_inner.F_MemberId AND xh.F_IsEffective = 1
4425 4463 GROUP BY tk_inner.F_MemberId
4426 4464 ) xh_stats ON xh_stats.member_id = tk.F_MemberId
4427 4465 -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项)
  4466 + -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配)
4428 4467 LEFT JOIN (
4429 4468 SELECT
4430 4469 tk_inner.F_MemberId as member_id,
... ... @@ -4432,9 +4471,9 @@ namespace NCC.Extend.LqStatistics
4432 4471 SUM(kd.zdyj) as billing_amount,
4433 4472 GROUP_CONCAT(DISTINCT kdpx.pxmc SEPARATOR '、') as billing_items
4434 4473 FROM lq_tkjlb tk_inner
4435   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id
4436   - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id
4437   - INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.F_IsEffective = 1
  4474 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
  4475 + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
  4476 + INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.kdhy = tk_inner.F_MemberId AND kd.F_IsEffective = 1
4438 4477 LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1
4439 4478 GROUP BY tk_inner.F_MemberId
4440 4479 ) kd_stats ON kd_stats.member_id = tk.F_MemberId
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStoreExpenseService.cs 0 → 100644
  1 +using NCC.Common.Core.Manager;
  2 +using NCC.Common.Enum;
  3 +using NCC.Common.Extension;
  4 +using NCC.Common.Filter;
  5 +using NCC.Dependency;
  6 +using NCC.DynamicApiController;
  7 +using NCC.FriendlyException;
  8 +using NCC.Extend.Interfaces.LqStoreExpense;
  9 +using Mapster;
  10 +using Microsoft.AspNetCore.Mvc;
  11 +using SqlSugar;
  12 +using System;
  13 +using System.Collections.Generic;
  14 +using System.Linq;
  15 +using System.Threading.Tasks;
  16 +using NCC.Extend.Entitys.lq_store_expense;
  17 +using NCC.Extend.Entitys.Dto.LqStoreExpense;
  18 +using NCC.Extend.Entitys.lq_mdxx;
  19 +using Yitter.IdGenerator;
  20 +using NCC.Common.Helper;
  21 +using NCC.Common.Model.NPOI;
  22 +using NCC.Common.Configuration;
  23 +using NCC.DataEncryption;
  24 +using NCC.ClayObject;
  25 +using NCC.Common.Const;
  26 +using NCC.JsonSerialization;
  27 +using NCC.Extend.Entitys.Enum;
  28 +using Microsoft.AspNetCore.Http;
  29 +using System.IO;
  30 +using System.Data;
  31 +
  32 +namespace NCC.Extend.LqStoreExpense
  33 +{
  34 + /// <summary>
  35 + /// 店内支出表服务
  36 + /// </summary>
  37 + [ApiDescriptionSettings(Tag = "Extend", Name = "LqStoreExpense", Order = 200)]
  38 + [Route("api/Extend/[controller]")]
  39 + public class LqStoreExpenseService : ILqStoreExpenseService, IDynamicApiController, ITransient
  40 + {
  41 + private readonly ISqlSugarRepository<LqStoreExpenseEntity> _repository;
  42 + private readonly SqlSugarScope _db;
  43 + private readonly IUserManager _userManager;
  44 +
  45 + /// <summary>
  46 + /// 初始化一个<see cref="LqStoreExpenseService"/>类型的新实例
  47 + /// </summary>
  48 + public LqStoreExpenseService(
  49 + ISqlSugarRepository<LqStoreExpenseEntity> repository,
  50 + IUserManager userManager)
  51 + {
  52 + _repository = repository;
  53 + _db = _repository.Context;
  54 + _userManager = userManager;
  55 + }
  56 +
  57 + /// <summary>
  58 + /// 获取店内支出表详情
  59 + /// </summary>
  60 + /// <param name="id">主键ID</param>
  61 + /// <returns></returns>
  62 + [HttpGet("{id}")]
  63 + public async Task<dynamic> GetInfo(string id)
  64 + {
  65 + var entity = await _db.Queryable<LqStoreExpenseEntity>()
  66 + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  67 + .FirstAsync();
  68 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  69 + var output = entity.Adapt<LqStoreExpenseInfoOutput>();
  70 + if (!string.IsNullOrEmpty(entity.Attachment))
  71 + {
  72 + output.attachment = entity.Attachment.ToObject<List<NCC.Common.Model.FileControlsModel>>();
  73 + }
  74 + return output;
  75 + }
  76 +
  77 + /// <summary>
  78 + /// 获取店内支出表列表
  79 + /// </summary>
  80 + /// <param name="input">请求参数</param>
  81 + /// <returns></returns>
  82 + [HttpGet("")]
  83 + public async Task<dynamic> GetList([FromQuery] LqStoreExpenseListQueryInput input)
  84 + {
  85 + var sidx = input.sidx ?? "ExpenseDate";
  86 + var sortType = input.sort?.ToLower() == "asc" ? OrderByType.Asc : OrderByType.Desc;
  87 + List<string> queryExpenseDate = input.expenseDateStart != null && input.expenseDateEnd != null
  88 + ? new List<string> { input.expenseDateStart, input.expenseDateEnd }
  89 + : null;
  90 + DateTime? startExpenseDate = queryExpenseDate != null ? Ext.GetDateTime(queryExpenseDate.First()) : null;
  91 + DateTime? endExpenseDate = queryExpenseDate != null ? Ext.GetDateTime(queryExpenseDate.Last()) : null;
  92 +
  93 + var query = _db.Queryable<LqStoreExpenseEntity>()
  94 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  95 + .WhereIF(!string.IsNullOrEmpty(input.storeId), x => x.StoreId == input.storeId)
  96 + .WhereIF(!string.IsNullOrEmpty(input.storeName), x => x.StoreName.Contains(input.storeName))
  97 + .WhereIF(!string.IsNullOrEmpty(input.expenseCategoryId), x => x.ExpenseCategoryId == input.expenseCategoryId)
  98 + .WhereIF(queryExpenseDate != null, x => x.ExpenseDate >= new DateTime(startExpenseDate.ToDate().Year, startExpenseDate.ToDate().Month, startExpenseDate.ToDate().Day, 0, 0, 0))
  99 + .WhereIF(queryExpenseDate != null, x => x.ExpenseDate <= new DateTime(endExpenseDate.ToDate().Year, endExpenseDate.ToDate().Month, endExpenseDate.ToDate().Day, 23, 59, 59));
  100 +
  101 + // 处理排序
  102 + switch (sidx.ToLower())
  103 + {
  104 + case "id":
  105 + query = query.OrderBy(x => x.Id, sortType);
  106 + break;
  107 + case "expensedate":
  108 + query = query.OrderBy(x => x.ExpenseDate, sortType);
  109 + break;
  110 + case "storename":
  111 + query = query.OrderBy(x => x.StoreName, sortType);
  112 + break;
  113 + case "amount":
  114 + query = query.OrderBy(x => x.Amount, sortType);
  115 + break;
  116 + case "createtime":
  117 + query = query.OrderBy(x => x.CreateTime, sortType);
  118 + break;
  119 + default:
  120 + query = query.OrderBy(x => x.ExpenseDate, OrderByType.Desc);
  121 + break;
  122 + }
  123 +
  124 + var total = await query.CountAsync();
  125 + var list = await query.ToPageListAsync(input.currentPage, input.pageSize);
  126 +
  127 + var result = list.Select(x => new LqStoreExpenseListOutput
  128 + {
  129 + id = x.Id,
  130 + storeId = x.StoreId,
  131 + storeName = x.StoreName,
  132 + expenseCategoryId = x.ExpenseCategoryId,
  133 + expenseCategoryName = x.ExpenseCategoryName,
  134 + expenseDate = x.ExpenseDate,
  135 + unitPrice = x.UnitPrice,
  136 + quantity = x.Quantity,
  137 + amount = x.Amount,
  138 + memo = x.Memo,
  139 + relatedReimbursementId = x.RelatedReimbursementId,
  140 + relatedPurchaseRecordId = x.RelatedPurchaseRecordId,
  141 + createUser = x.CreateUser,
  142 + createTime = x.CreateTime,
  143 + updateUser = x.UpdateUser,
  144 + updateTime = x.UpdateTime
  145 + }).ToList();
  146 +
  147 + return PageResult<LqStoreExpenseListOutput>.SqlSugarPageResult(
  148 + new SqlSugarPagedList<LqStoreExpenseListOutput>
  149 + {
  150 + list = result,
  151 + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total }
  152 + });
  153 + }
  154 +
  155 + /// <summary>
  156 + /// 获取店内支出表无分页列表
  157 + /// </summary>
  158 + /// <param name="input">请求参数</param>
  159 + /// <returns></returns>
  160 + [HttpGet("Actions/GetNoPagingList")]
  161 + public async Task<List<LqStoreExpenseListOutput>> GetNoPagingList([FromQuery] LqStoreExpenseListQueryInput input)
  162 + {
  163 + var sidx = input.sidx ?? "ExpenseDate";
  164 + var sortType = input.sort?.ToLower() == "asc" ? OrderByType.Asc : OrderByType.Desc;
  165 + List<string> queryExpenseDate = input.expenseDateStart != null && input.expenseDateEnd != null
  166 + ? new List<string> { input.expenseDateStart, input.expenseDateEnd }
  167 + : null;
  168 + DateTime? startExpenseDate = queryExpenseDate != null ? Ext.GetDateTime(queryExpenseDate.First()) : null;
  169 + DateTime? endExpenseDate = queryExpenseDate != null ? Ext.GetDateTime(queryExpenseDate.Last()) : null;
  170 +
  171 + var query = _db.Queryable<LqStoreExpenseEntity>()
  172 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  173 + .WhereIF(!string.IsNullOrEmpty(input.storeId), x => x.StoreId == input.storeId)
  174 + .WhereIF(!string.IsNullOrEmpty(input.storeName), x => x.StoreName.Contains(input.storeName))
  175 + .WhereIF(!string.IsNullOrEmpty(input.expenseCategoryId), x => x.ExpenseCategoryId == input.expenseCategoryId)
  176 + .WhereIF(queryExpenseDate != null, x => x.ExpenseDate >= new DateTime(startExpenseDate.ToDate().Year, startExpenseDate.ToDate().Month, startExpenseDate.ToDate().Day, 0, 0, 0))
  177 + .WhereIF(queryExpenseDate != null, x => x.ExpenseDate <= new DateTime(endExpenseDate.ToDate().Year, endExpenseDate.ToDate().Month, endExpenseDate.ToDate().Day, 23, 59, 59));
  178 +
  179 + // 处理排序
  180 + switch (sidx.ToLower())
  181 + {
  182 + case "id":
  183 + query = query.OrderBy(x => x.Id, sortType);
  184 + break;
  185 + case "expensedate":
  186 + query = query.OrderBy(x => x.ExpenseDate, sortType);
  187 + break;
  188 + case "storename":
  189 + query = query.OrderBy(x => x.StoreName, sortType);
  190 + break;
  191 + case "amount":
  192 + query = query.OrderBy(x => x.Amount, sortType);
  193 + break;
  194 + case "createtime":
  195 + query = query.OrderBy(x => x.CreateTime, sortType);
  196 + break;
  197 + default:
  198 + query = query.OrderBy(x => x.ExpenseDate, OrderByType.Desc);
  199 + break;
  200 + }
  201 +
  202 + var list = await query
  203 + .Select(x => new LqStoreExpenseListOutput
  204 + {
  205 + id = x.Id,
  206 + storeId = x.StoreId,
  207 + storeName = x.StoreName,
  208 + expenseCategoryId = x.ExpenseCategoryId,
  209 + expenseCategoryName = x.ExpenseCategoryName,
  210 + expenseDate = x.ExpenseDate,
  211 + unitPrice = x.UnitPrice,
  212 + quantity = x.Quantity,
  213 + amount = x.Amount,
  214 + memo = x.Memo,
  215 + relatedReimbursementId = x.RelatedReimbursementId,
  216 + relatedPurchaseRecordId = x.RelatedPurchaseRecordId,
  217 + createUser = x.CreateUser,
  218 + createTime = x.CreateTime,
  219 + updateUser = x.UpdateUser,
  220 + updateTime = x.UpdateTime
  221 + })
  222 + .ToListAsync();
  223 + return list;
  224 + }
  225 +
  226 + /// <summary>
  227 + /// 创建店内支出表
  228 + /// </summary>
  229 + /// <param name="input">参数</param>
  230 + /// <returns></returns>
  231 + [HttpPost("")]
  232 + public async Task Create([FromBody] LqStoreExpenseCrInput input)
  233 + {
  234 + var userInfo = await _userManager.GetUserInfo();
  235 + var entity = input.Adapt<LqStoreExpenseEntity>();
  236 + entity.Id = YitIdHelper.NextId().ToString();
  237 + entity.IsEffective = StatusEnum.有效.GetHashCode();
  238 + entity.CreateUser = _userManager.UserId;
  239 + entity.CreateTime = DateTime.Now;
  240 +
  241 + if (input.attachment != null && input.attachment.Any())
  242 + {
  243 + entity.Attachment = input.attachment.ToJson();
  244 + }
  245 +
  246 + // 如果未提供门店名称,根据门店ID查询
  247 + if (string.IsNullOrEmpty(entity.StoreName) && !string.IsNullOrEmpty(entity.StoreId))
  248 + {
  249 + var store = await _db.Queryable<LqMdxxEntity>()
  250 + .Where(x => x.Id == entity.StoreId)
  251 + .Select(x => x.Dm)
  252 + .FirstAsync();
  253 + entity.StoreName = store;
  254 + }
  255 +
  256 + var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
  257 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
  258 + }
  259 +
  260 + /// <summary>
  261 + /// 更新店内支出表
  262 + /// </summary>
  263 + /// <param name="id">主键</param>
  264 + /// <param name="input">参数</param>
  265 + /// <returns></returns>
  266 + [HttpPut("{id}")]
  267 + public async Task Update(string id, [FromBody] LqStoreExpenseUpInput input)
  268 + {
  269 + var entity = await _db.Queryable<LqStoreExpenseEntity>()
  270 + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  271 + .FirstAsync();
  272 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  273 +
  274 + entity.StoreId = input.storeId;
  275 + entity.StoreName = input.storeName;
  276 + entity.ExpenseCategoryId = input.expenseCategoryId;
  277 + entity.ExpenseCategoryName = input.expenseCategoryName;
  278 + entity.ExpenseDate = input.expenseDate;
  279 + entity.UnitPrice = input.unitPrice;
  280 + entity.Quantity = input.quantity;
  281 + entity.Amount = input.amount;
  282 + entity.Memo = input.memo;
  283 + entity.RelatedReimbursementId = input.relatedReimbursementId;
  284 + entity.RelatedPurchaseRecordId = input.relatedPurchaseRecordId;
  285 + entity.UpdateUser = _userManager.UserId;
  286 + entity.UpdateTime = DateTime.Now;
  287 +
  288 + if (input.attachment != null && input.attachment.Any())
  289 + {
  290 + entity.Attachment = input.attachment.ToJson();
  291 + }
  292 +
  293 + // 如果未提供门店名称,根据门店ID查询
  294 + if (string.IsNullOrEmpty(entity.StoreName) && !string.IsNullOrEmpty(entity.StoreId))
  295 + {
  296 + var store = await _db.Queryable<LqMdxxEntity>()
  297 + .Where(x => x.Id == entity.StoreId)
  298 + .Select(x => x.Dm)
  299 + .FirstAsync();
  300 + entity.StoreName = store;
  301 + }
  302 +
  303 + var isOk = await _db.Updateable(entity).ExecuteCommandAsync();
  304 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001);
  305 + }
  306 +
  307 + /// <summary>
  308 + /// 删除店内支出表
  309 + /// </summary>
  310 + /// <param name="id">主键</param>
  311 + /// <returns></returns>
  312 + [HttpDelete("{id}")]
  313 + public async Task Delete(string id)
  314 + {
  315 + var entity = await _db.Queryable<LqStoreExpenseEntity>()
  316 + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode())
  317 + .FirstAsync();
  318 + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  319 +
  320 + // 逻辑删除
  321 + entity.IsEffective = StatusEnum.无效.GetHashCode();
  322 + entity.UpdateUser = _userManager.UserId;
  323 + entity.UpdateTime = DateTime.Now;
  324 +
  325 + var isOk = await _db.Updateable(entity).ExecuteCommandAsync();
  326 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1002);
  327 + }
  328 +
  329 + /// <summary>
  330 + /// 导出店内支出表
  331 + /// </summary>
  332 + /// <param name="input">请求参数</param>
  333 + /// <returns></returns>
  334 + [HttpGet("Actions/Export")]
  335 + public async Task<dynamic> Export([FromQuery] LqStoreExpenseListQueryInput input)
  336 + {
  337 + var userInfo = await _userManager.GetUserInfo();
  338 + var exportData = new List<LqStoreExpenseListOutput>();
  339 + if (input.dataType == 0)
  340 + {
  341 + var data = Clay.Object(await this.GetList(input));
  342 + exportData = data.Solidify<PageResult<LqStoreExpenseListOutput>>().list;
  343 + }
  344 + else
  345 + {
  346 + exportData = await this.GetNoPagingList(input);
  347 + }
  348 + List<ParamsModel> paramList = "[{\"value\":\"门店ID\",\"field\":\"storeId\"},{\"value\":\"门店名称\",\"field\":\"storeName\"},{\"value\":\"支出分类ID\",\"field\":\"expenseCategoryId\"},{\"value\":\"支出分类名称\",\"field\":\"expenseCategoryName\"},{\"value\":\"支出日期\",\"field\":\"expenseDate\"},{\"value\":\"单价\",\"field\":\"unitPrice\"},{\"value\":\"数量\",\"field\":\"quantity\"},{\"value\":\"金额\",\"field\":\"amount\"},{\"value\":\"备注说明\",\"field\":\"memo\"},{\"value\":\"关联报销申请ID\",\"field\":\"relatedReimbursementId\"},{\"value\":\"关联购买记录ID\",\"field\":\"relatedPurchaseRecordId\"},{\"value\":\"创建时间\",\"field\":\"createTime\"},]".ToList<ParamsModel>();
  349 + ExcelConfig excelconfig = new ExcelConfig();
  350 + excelconfig.FileName = "店内支出表.xls";
  351 + excelconfig.HeadFont = "微软雅黑";
  352 + excelconfig.HeadPoint = 10;
  353 + excelconfig.IsAllSizeColumn = true;
  354 + excelconfig.ColumnModel = new List<ExcelColumnModel>();
  355 + List<string> selectKeyList = !string.IsNullOrEmpty(input.selectKey) ? input.selectKey.Split(',').ToList() : paramList.Select(p => p.field).ToList();
  356 + foreach (var item in selectKeyList)
  357 + {
  358 + var isExist = paramList.Find(p => p.field == item);
  359 + if (isExist != null)
  360 + {
  361 + excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = isExist.field, ExcelColumn = isExist.value });
  362 + }
  363 + }
  364 + var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName;
  365 + ExcelExportHelper<LqStoreExpenseListOutput>.Export(exportData, excelconfig, addPath);
  366 + var fileName = _userManager.UserId + "|" + addPath + "|xls";
  367 + var output = new
  368 + {
  369 + name = excelconfig.FileName,
  370 + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC")
  371 + };
  372 + return output;
  373 + }
  374 +
  375 + /// <summary>
  376 + /// 导入店内支出数据
  377 + /// </summary>
  378 + /// <remarks>
  379 + /// 从Excel文件导入店内支出数据
  380 + ///
  381 + /// Excel格式要求:
  382 + /// 第一行为标题行:门店ID、门店名称、支出分类ID、支出分类名称、支出日期、单价、数量、金额、备注说明、关联报销申请ID、关联购买记录ID
  383 + /// 从第二行开始为数据行
  384 + ///
  385 + /// 示例请求:
  386 + /// POST /api/Extend/LqStoreExpense/Actions/Import
  387 + /// Content-Type: multipart/form-data
  388 + /// </remarks>
  389 + /// <param name="file">Excel文件</param>
  390 + /// <returns>导入结果</returns>
  391 + /// <response code="200">导入成功</response>
  392 + /// <response code="400">文件格式错误或数据验证失败</response>
  393 + [HttpPost("Actions/Import")]
  394 + public async Task<dynamic> Import(IFormFile file)
  395 + {
  396 + try
  397 + {
  398 + if (file == null || file.Length == 0)
  399 + {
  400 + throw NCCException.Oh("请选择要上传的Excel文件");
  401 + }
  402 +
  403 + // 检查文件格式
  404 + var allowedExtensions = new[] { ".xlsx", ".xls" };
  405 + var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();
  406 + if (!allowedExtensions.Contains(fileExtension))
  407 + {
  408 + throw NCCException.Oh("只支持.xlsx和.xls格式的Excel文件");
  409 + }
  410 +
  411 + var successCount = 0;
  412 + var failCount = 0;
  413 + var failMessages = new List<string>();
  414 +
  415 + // 保存临时文件
  416 + var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName));
  417 + try
  418 + {
  419 + using (var stream = new FileStream(tempFilePath, FileMode.Create))
  420 + {
  421 + await file.CopyToAsync(stream);
  422 + }
  423 +
  424 + // 使用ExcelImportHelper读取Excel文件(第一行为标题行)
  425 + var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0);
  426 +
  427 + if (dataTable.Rows.Count <= 1)
  428 + {
  429 + throw NCCException.Oh("Excel文件中没有数据行(至少需要标题行和一行数据)");
  430 + }
  431 +
  432 + // 从第1行开始读取数据(跳过标题行)
  433 + for (int i = 1; i < dataTable.Rows.Count; i++)
  434 + {
  435 + try
  436 + {
  437 + var row = dataTable.Rows[i];
  438 + var storeId = row[0]?.ToString()?.Trim();
  439 + var storeName = row[1]?.ToString()?.Trim();
  440 + var expenseCategoryId = row[2]?.ToString()?.Trim();
  441 + var expenseCategoryName = row[3]?.ToString()?.Trim();
  442 + var expenseDateText = row[4]?.ToString()?.Trim();
  443 + var unitPriceText = row[5]?.ToString()?.Trim();
  444 + var quantityText = row[6]?.ToString()?.Trim();
  445 + var amountText = row[7]?.ToString()?.Trim();
  446 + var memo = row[8]?.ToString()?.Trim();
  447 + var relatedReimbursementId = row[9]?.ToString()?.Trim();
  448 + var relatedPurchaseRecordId = row[10]?.ToString()?.Trim();
  449 +
  450 + // 跳过空行
  451 + if (string.IsNullOrEmpty(storeId) && string.IsNullOrEmpty(storeName))
  452 + {
  453 + continue;
  454 + }
  455 +
  456 + // 验证必填字段
  457 + if (string.IsNullOrEmpty(storeId))
  458 + {
  459 + failMessages.Add($"第{i + 1}行:门店ID不能为空");
  460 + failCount++;
  461 + continue;
  462 + }
  463 +
  464 + if (string.IsNullOrEmpty(expenseDateText) || !DateTime.TryParse(expenseDateText, out DateTime expenseDate))
  465 + {
  466 + failMessages.Add($"第{i + 1}行:支出日期格式错误(应为日期格式,如:2025-01-15)");
  467 + failCount++;
  468 + continue;
  469 + }
  470 +
  471 + if (string.IsNullOrEmpty(amountText) || !decimal.TryParse(amountText, out decimal amount))
  472 + {
  473 + failMessages.Add($"第{i + 1}行:金额格式错误(应为数字)");
  474 + failCount++;
  475 + continue;
  476 + }
  477 +
  478 + // 解析可选字段
  479 + decimal unitPrice = 0m;
  480 + if (!string.IsNullOrEmpty(unitPriceText) && decimal.TryParse(unitPriceText, out decimal up))
  481 + {
  482 + unitPrice = up;
  483 + }
  484 +
  485 + int quantity = 0;
  486 + if (!string.IsNullOrEmpty(quantityText) && int.TryParse(quantityText, out int qty))
  487 + {
  488 + quantity = qty;
  489 + }
  490 +
  491 + // 如果未提供门店名称,根据门店ID查询
  492 + if (string.IsNullOrEmpty(storeName))
  493 + {
  494 + var store = await _db.Queryable<LqMdxxEntity>()
  495 + .Where(x => x.Id == storeId)
  496 + .Select(x => x.Dm)
  497 + .FirstAsync();
  498 + storeName = store ?? "";
  499 + }
  500 +
  501 + // 创建记录
  502 + var entity = new LqStoreExpenseEntity
  503 + {
  504 + Id = YitIdHelper.NextId().ToString(),
  505 + StoreId = storeId,
  506 + StoreName = storeName,
  507 + ExpenseCategoryId = expenseCategoryId,
  508 + ExpenseCategoryName = expenseCategoryName,
  509 + ExpenseDate = expenseDate,
  510 + UnitPrice = unitPrice,
  511 + Quantity = quantity,
  512 + Amount = amount,
  513 + Memo = memo,
  514 + RelatedReimbursementId = relatedReimbursementId,
  515 + RelatedPurchaseRecordId = relatedPurchaseRecordId,
  516 + IsEffective = StatusEnum.有效.GetHashCode(),
  517 + CreateUser = _userManager.UserId,
  518 + CreateTime = DateTime.Now
  519 + };
  520 +
  521 + var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
  522 + if (isOk > 0)
  523 + {
  524 + successCount++;
  525 + }
  526 + else
  527 + {
  528 + failMessages.Add($"第{i + 1}行:保存失败");
  529 + failCount++;
  530 + }
  531 + }
  532 + catch (Exception ex)
  533 + {
  534 + failMessages.Add($"第{i + 1}行:{ex.Message}");
  535 + failCount++;
  536 + }
  537 + }
  538 + }
  539 + finally
  540 + {
  541 + // 清理临时文件
  542 + if (File.Exists(tempFilePath))
  543 + {
  544 + File.Delete(tempFilePath);
  545 + }
  546 + }
  547 +
  548 + return new
  549 + {
  550 + success = true,
  551 + message = $"导入完成:成功{successCount}条,失败{failCount}条",
  552 + successCount = successCount,
  553 + failCount = failCount,
  554 + failMessages = failMessages
  555 + };
  556 + }
  557 + catch (Exception ex)
  558 + {
  559 + throw NCCException.Oh($"导入失败:{ex.Message}");
  560 + }
  561 + }
  562 + }
  563 +}
  564 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs 0 → 100644
  1 +using Microsoft.AspNetCore.Authorization;
  2 +using Microsoft.AspNetCore.Mvc;
  3 +using NCC.Common.Filter;
  4 +using NCC.Common.Helper;
  5 +using NCC.Dependency;
  6 +using NCC.DynamicApiController;
  7 +using NCC.Extend.Entitys.Dto.LqStoreManagerSalary;
  8 +using NCC.Extend.Entitys.Enum;
  9 +using NCC.Extend.Entitys.lq_attendance_summary;
  10 +using NCC.Extend.Entitys.lq_cooperation_cost;
  11 +using NCC.Extend.Entitys.lq_hytk_hytk;
  12 +using NCC.Extend.Entitys.lq_inventory_usage;
  13 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  14 +using NCC.Extend.Entitys.lq_laundry_flow;
  15 +using NCC.Extend.Entitys.lq_md_target;
  16 +using NCC.Extend.Entitys.lq_md_xdbhsj;
  17 +using NCC.Extend.Entitys.lq_mdxx;
  18 +using NCC.Extend.Entitys.lq_store_expense;
  19 +using NCC.Extend.Entitys.lq_store_manager_salary_statistics;
  20 +using NCC.Extend.Entitys.lq_xh_hyhk;
  21 +using NCC.Extend.Entitys.lq_xh_jksyj;
  22 +using NCC.System.Entitys.Permission;
  23 +using SqlSugar;
  24 +using System;
  25 +using System.Collections.Generic;
  26 +using System.Linq;
  27 +using System.Threading.Tasks;
  28 +using Yitter.IdGenerator;
  29 +
  30 +namespace NCC.Extend
  31 +{
  32 + /// <summary>
  33 + /// 店长薪酬服务
  34 + /// </summary>
  35 + [ApiDescriptionSettings(Tag = "店长薪酬服务", Name = "LqStoreManagerSalary", Order = 303)]
  36 + [Route("api/Extend/[controller]")]
  37 + public class LqStoreManagerSalaryService : IDynamicApiController, ITransient
  38 + {
  39 + private readonly ISqlSugarClient _db;
  40 +
  41 + /// <summary>
  42 + /// 初始化一个<see cref="LqStoreManagerSalaryService"/>类型的新实例
  43 + /// </summary>
  44 + public LqStoreManagerSalaryService(ISqlSugarClient db)
  45 + {
  46 + _db = db;
  47 + }
  48 +
  49 + /// <summary>
  50 + /// 获取店长工资列表
  51 + /// </summary>
  52 + /// <param name="input">查询参数</param>
  53 + /// <returns>店长工资分页列表</returns>
  54 + [HttpGet("store-manager")]
  55 + public async Task<dynamic> GetStoreManagerSalaryList([FromQuery] StoreManagerSalaryInput input)
  56 + {
  57 + var monthStr = $"{input.Year}{input.Month:D2}";
  58 +
  59 + // 1. 检查当月是否已生成工资数据
  60 + var exists = await _db.Queryable<LqStoreManagerSalaryStatisticsEntity>()
  61 + .AnyAsync(x => x.StatisticsMonth == monthStr);
  62 +
  63 + // 2. 如果没有数据,则进行计算
  64 + if (!exists)
  65 + {
  66 + await CalculateStoreManagerSalary(input.Year, input.Month);
  67 + }
  68 +
  69 + // 3. 查询数据
  70 + var query = _db.Queryable<LqStoreManagerSalaryStatisticsEntity>()
  71 + .Where(x => x.StatisticsMonth == monthStr);
  72 +
  73 + if (!string.IsNullOrEmpty(input.StoreId))
  74 + {
  75 + query = query.Where(x => x.StoreId == input.StoreId);
  76 + }
  77 +
  78 + if (!string.IsNullOrEmpty(input.Keyword))
  79 + {
  80 + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword));
  81 + }
  82 +
  83 + var list = await query.Select(x => new StoreManagerSalaryOutput
  84 + {
  85 + Id = x.Id,
  86 + StoreName = x.StoreName,
  87 + EmployeeName = x.EmployeeName,
  88 + Position = x.Position,
  89 + StoreTotalPerformance = x.StoreTotalPerformance,
  90 + StoreBillingPerformance = x.StoreBillingPerformance,
  91 + StoreRefundPerformance = x.StoreRefundPerformance,
  92 + StoreLifeline = x.StoreLifeline,
  93 + PerformanceCompletionRate = x.PerformanceCompletionRate,
  94 + PerformanceReached = x.PerformanceReached,
  95 + HeadCountReached = x.HeadCountReached,
  96 + ConsumeReached = x.ConsumeReached,
  97 + AssessmentDeduction = x.AssessmentDeduction,
  98 + UnreachedIndicatorCount = x.UnreachedIndicatorCount,
  99 + HeadCount = x.HeadCount,
  100 + TargetHeadCount = x.TargetHeadCount,
  101 + StoreConsume = x.StoreConsume,
  102 + TargetConsume = x.TargetConsume,
  103 + SalesPerformance = x.SalesPerformance,
  104 + ProductMaterial = x.ProductMaterial,
  105 + CooperationCost = x.CooperationCost,
  106 + StoreExpense = x.StoreExpense,
  107 + LaundryCost = x.LaundryCost,
  108 + GrossProfit = x.GrossProfit,
  109 + CommissionRate = x.CommissionRate,
  110 + CommissionAmount = x.CommissionAmount,
  111 + BaseSalary = x.BaseSalary,
  112 + FlagshipStoreDeduction = x.FlagshipStoreDeduction,
  113 + ActualBaseSalary = x.ActualBaseSalary,
  114 + WorkingDays = x.WorkingDays,
  115 + LeaveDays = x.LeaveDays,
  116 + GrossSalary = x.GrossSalary,
  117 + ActualSalary = x.ActualSalary,
  118 + TotalDeduction = x.TotalDeduction,
  119 + TotalSubsidy = x.TotalSubsidy,
  120 + Bonus = x.Bonus,
  121 + MonthlyPaymentStatus = x.MonthlyPaymentStatus,
  122 + PaidAmount = x.PaidAmount,
  123 + PendingAmount = x.PendingAmount,
  124 + IsLocked = x.IsLocked,
  125 + StoreType = x.StoreType,
  126 + StoreCategory = x.StoreCategory,
  127 + IsNewStore = x.IsNewStore,
  128 + NewStoreProtectionStage = x.NewStoreProtectionStage,
  129 + UpdateTime = x.UpdateTime
  130 + })
  131 + .ToPagedListAsync(input.currentPage, input.pageSize);
  132 +
  133 + return PageResult<StoreManagerSalaryOutput>.SqlSugarPageResult(list);
  134 + }
  135 +
  136 + /// <summary>
  137 + /// 计算店长工资
  138 + /// </summary>
  139 + /// <param name="year">年份</param>
  140 + /// <param name="month">月份</param>
  141 + /// <returns></returns>
  142 + [HttpPost("calculate/store-manager")]
  143 + public async Task CalculateStoreManagerSalary(int year, int month)
  144 + {
  145 + var startDate = new DateTime(year, month, 1);
  146 + var endDate = startDate.AddMonths(1).AddDays(-1);
  147 + var monthStr = $"{year}{month:D2}";
  148 +
  149 + // 1. 获取基础数据
  150 +
  151 + // 1.1 获取店长员工列表(从BASE_USER表,岗位为"店长")
  152 + var storeManagerUserList = await _db.Queryable<UserEntity>()
  153 + .Where(x => x.Gw == "店长" && x.DeleteMark == null && x.EnabledMark == 1)
  154 + .Select(x => new { x.Id, x.RealName, x.Mdid, x.Gw })
  155 + .ToListAsync();
  156 +
  157 + if (!storeManagerUserList.Any())
  158 + {
  159 + // 如果没有店长员工,直接返回
  160 + return;
  161 + }
  162 +
  163 + // 1.2 门店信息 (lq_mdxx)
  164 + var storeList = await _db.Queryable<LqMdxxEntity>().ToListAsync();
  165 + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x);
  166 +
  167 + // 1.3 门店目标信息 (lq_md_target)
  168 + var storeTargets = await _db.Queryable<LqMdTargetEntity>()
  169 + .Where(x => x.Month == monthStr)
  170 + .ToListAsync();
  171 + var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId))
  172 + .ToDictionary(x => x.StoreId, x => x);
  173 +
  174 + // 1.4 门店新店保护信息 (lq_md_xdbhsj)
  175 + var newStoreProtectionList = await _db.Queryable<LqMdXdbhsjEntity>()
  176 + .Where(x => x.Sfqy == 1)
  177 + .ToListAsync();
  178 + var newStoreProtectionDict = newStoreProtectionList
  179 + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate)
  180 + .GroupBy(x => x.Mdid)
  181 + .ToDictionary(g => g.Key, g => g.First());
  182 +
  183 + // 1.5 门店总业绩计算 (开单实付 - 退卡金额)
  184 + // 开单实付
  185 + var storeBillingList = await _db.Queryable<LqKdKdjlbEntity>()
  186 + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1)
  187 + .Select(x => new { x.Djmd, x.Sfyj })
  188 + .ToListAsync();
  189 + var storeBillingDict = storeBillingList
  190 + .Where(x => !string.IsNullOrEmpty(x.Djmd))
  191 + .GroupBy(x => x.Djmd)
  192 + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj));
  193 +
  194 + // 退卡金额(使用F_ActualRefundAmount,如果没有则使用tkje)
  195 + var storeRefundList = await _db.Queryable<LqHytkHytkEntity>()
  196 + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
  197 + .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje })
  198 + .ToListAsync();
  199 + var storeRefundDict = storeRefundList
  200 + .Where(x => !string.IsNullOrEmpty(x.Md))
  201 + .GroupBy(x => x.Md)
  202 + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0));
  203 +
  204 + // 1.6 门店消耗金额统计(按门店统计当月总消耗)
  205 + var storeConsumeSql = $@"
  206 + SELECT
  207 + hyhk.md as StoreId,
  208 + COALESCE(SUM(jksyj.jksyj), 0) as ConsumeAmount
  209 + FROM lq_xh_jksyj jksyj
  210 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
  211 + WHERE jksyj.F_IsEffective = 1
  212 + AND hyhk.F_IsEffective = 1
  213 + AND hyhk.hksj >= @startDate
  214 + AND hyhk.hksj <= @endDate
  215 + GROUP BY hyhk.md";
  216 +
  217 + var storeConsumeData = await _db.Ado.SqlQueryAsync<dynamic>(storeConsumeSql, new { startDate, endDate = endDate.AddDays(1) });
  218 + var storeConsumeDict = storeConsumeData
  219 + .Where(x => x.StoreId != null)
  220 + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ConsumeAmount ?? 0));
  221 +
  222 + // 1.7 进店消耗人数统计(有消费金额的,按门店按月去重客户数)
  223 + var headcountSql = $@"
  224 + SELECT
  225 + hyhk.md as StoreId,
  226 + COUNT(DISTINCT hyhk.hy) as HeadCount
  227 + FROM lq_xh_hyhk hyhk
  228 + WHERE hyhk.F_IsEffective = 1
  229 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @monthStr
  230 + AND EXISTS (
  231 + SELECT 1
  232 + FROM lq_xh_jksyj jksyj
  233 + WHERE jksyj.glkdbh = hyhk.F_Id
  234 + AND jksyj.F_IsEffective = 1
  235 + AND jksyj.jksyj > 0
  236 + )
  237 + GROUP BY hyhk.md";
  238 +
  239 + var headcountData = await _db.Ado.SqlQueryAsync<dynamic>(headcountSql, new { monthStr });
  240 + var headcountDict = headcountData
  241 + .Where(x => x.StoreId != null)
  242 + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount));
  243 +
  244 + // 1.8 考勤数据 (lq_attendance_summary)
  245 + var attendanceList = await _db.Queryable<LqAttendanceSummaryEntity>()
  246 + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
  247 + .ToListAsync();
  248 + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x);
  249 +
  250 + // 1.9 产品物料统计(仓库领用金额,注意11月特殊规则)
  251 + var queryMonth = monthStr;
  252 + if (month == 11)
  253 + {
  254 + // 11月工资算10月数据
  255 + queryMonth = $"{year}10";
  256 + }
  257 + var productMaterialSql = $@"
  258 + SELECT
  259 + F_StoreId as StoreId,
  260 + COALESCE(SUM(F_TotalAmount), 0) as MaterialAmount
  261 + FROM lq_inventory_usage
  262 + WHERE F_IsEffective = 1
  263 + AND DATE_FORMAT(F_UsageTime, '%Y%m') = @queryMonth
  264 + GROUP BY F_StoreId";
  265 +
  266 + var productMaterialData = await _db.Ado.SqlQueryAsync<dynamic>(productMaterialSql, new { queryMonth });
  267 + var productMaterialDict = productMaterialData
  268 + .Where(x => x.StoreId != null)
  269 + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.MaterialAmount ?? 0));
  270 +
  271 + // 1.10 合作项目成本统计
  272 + var cooperationCostList = await _db.Queryable<LqCooperationCostEntity>()
  273 + .Where(x => x.Year == year && x.Month == monthStr && x.IsEffective == StatusEnum.有效.GetHashCode())
  274 + .Select(x => new { x.StoreId, x.TotalAmount })
  275 + .ToListAsync();
  276 + var cooperationCostDict = cooperationCostList
  277 + .Where(x => !string.IsNullOrEmpty(x.StoreId))
  278 + .GroupBy(x => x.StoreId)
  279 + .ToDictionary(g => g.Key, g => g.Sum(x => x.TotalAmount));
  280 +
  281 + // 1.11 店内支出统计
  282 + var storeExpenseSql = $@"
  283 + SELECT
  284 + F_StoreId as StoreId,
  285 + COALESCE(SUM(F_Amount), 0) as ExpenseAmount
  286 + FROM lq_store_expense
  287 + WHERE F_IsEffective = 1
  288 + AND DATE_FORMAT(F_ExpenseDate, '%Y%m') = @monthStr
  289 + GROUP BY F_StoreId";
  290 +
  291 + var storeExpenseData = await _db.Ado.SqlQueryAsync<dynamic>(storeExpenseSql, new { monthStr });
  292 + var storeExpenseDict = storeExpenseData
  293 + .Where(x => x.StoreId != null)
  294 + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ExpenseAmount ?? 0));
  295 +
  296 + // 1.12 洗毛巾费用统计(只统计送出的记录,F_FlowType = 0)
  297 + var laundryCostSql = $@"
  298 + SELECT
  299 + F_StoreId as StoreId,
  300 + COALESCE(SUM(F_TotalPrice), 0) as LaundryAmount
  301 + FROM lq_laundry_flow
  302 + WHERE F_IsEffective = 1
  303 + AND F_FlowType = 0
  304 + AND DATE_FORMAT(F_CreateTime, '%Y%m') = @monthStr
  305 + GROUP BY F_StoreId";
  306 +
  307 + var laundryCostData = await _db.Ado.SqlQueryAsync<dynamic>(laundryCostSql, new { monthStr });
  308 + var laundryCostDict = laundryCostData
  309 + .Where(x => x.StoreId != null)
  310 + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.LaundryAmount ?? 0));
  311 +
  312 + // 2. 计算每个店长的工资
  313 + var storeManagerSalaryList = new List<LqStoreManagerSalaryStatisticsEntity>();
  314 +
  315 + foreach (var storeManagerUser in storeManagerUserList)
  316 + {
  317 + var salary = new LqStoreManagerSalaryStatisticsEntity
  318 + {
  319 + Id = YitIdHelper.NextId().ToString(),
  320 + EmployeeId = storeManagerUser.Id,
  321 + EmployeeName = storeManagerUser.RealName,
  322 + StatisticsMonth = monthStr,
  323 + Position = "店长",
  324 + CreateTime = DateTime.Now,
  325 + UpdateTime = DateTime.Now,
  326 + IsLocked = 0,
  327 + MonthlyPaymentStatus = "未发放"
  328 + };
  329 +
  330 + // 2.1 填充门店信息
  331 + string storeId = storeManagerUser.Mdid;
  332 + if (string.IsNullOrEmpty(storeId))
  333 + {
  334 + // 如果用户没有门店ID,跳过
  335 + continue;
  336 + }
  337 +
  338 + salary.StoreId = storeId;
  339 +
  340 + if (storeDict.ContainsKey(storeId))
  341 + {
  342 + var store = storeDict[storeId];
  343 + salary.StoreName = store.Dm;
  344 + salary.StoreType = store.StoreType;
  345 + salary.StoreCategory = store.StoreCategory;
  346 + }
  347 + else
  348 + {
  349 + // 如果门店不存在,跳过
  350 + continue;
  351 + }
  352 +
  353 + // 2.2 填充新店保护信息
  354 + bool isNewStore = false;
  355 + if (newStoreProtectionDict.ContainsKey(storeId))
  356 + {
  357 + var protection = newStoreProtectionDict[storeId];
  358 + salary.IsNewStore = "是";
  359 + salary.NewStoreProtectionStage = protection.Stage;
  360 + isNewStore = true;
  361 + }
  362 + else
  363 + {
  364 + salary.IsNewStore = "否";
  365 + salary.NewStoreProtectionStage = 0;
  366 + }
  367 +
  368 + // 2.2.1 数据校验:只有新店才需要校验门店分类和门店类型
  369 + if (isNewStore)
  370 + {
  371 + if (!salary.StoreCategory.HasValue)
  372 + {
  373 + // 新店如果没有设置门店分类,默认设置为B类门店
  374 + salary.StoreCategory = (int)StoreCategoryEnum.B类门店;
  375 + }
  376 + if (!salary.StoreType.HasValue)
  377 + {
  378 + // 新店如果没有设置门店类型,默认设置为200平门店
  379 + salary.StoreType = (int)StoreTypeEnum.门店200平;
  380 + }
  381 + }
  382 +
  383 + // 2.3 获取门店目标信息(门店生命线、目标人头、目标消耗)
  384 + if (!storeTargetDict.ContainsKey(storeId))
  385 + {
  386 + throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算店长工资");
  387 + }
  388 +
  389 + var storeTarget = storeTargetDict[storeId];
  390 + salary.StoreLifeline = storeTarget.StoreLifeline;
  391 + salary.TargetHeadCount = storeTarget.StoreHeadcountTarget;
  392 + salary.TargetConsume = storeTarget.StoreConsumeTarget;
  393 +
  394 + // 数据校验:门店生命线、目标人头必须设置
  395 + if (salary.StoreLifeline <= 0)
  396 + {
  397 + throw new Exception($"门店【{salary.StoreName ?? storeId}】的门店生命线未设置,无法计算店长工资");
  398 + }
  399 + if (salary.TargetHeadCount <= 0)
  400 + {
  401 + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标人头数未设置,无法计算店长工资");
  402 + }
  403 + if (!isNewStore && salary.TargetConsume <= 0)
  404 + {
  405 + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标消耗未设置,无法计算店长工资(老店需要考核消耗)");
  406 + }
  407 +
  408 + // 2.4 计算门店业绩
  409 + decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0;
  410 + decimal refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0;
  411 + salary.StoreBillingPerformance = billing;
  412 + salary.StoreRefundPerformance = refund;
  413 + salary.StoreTotalPerformance = billing - refund;
  414 +
  415 + // 计算业绩完成率
  416 + if (salary.StoreLifeline > 0)
  417 + {
  418 + salary.PerformanceCompletionRate = salary.StoreTotalPerformance / salary.StoreLifeline;
  419 + }
  420 + else
  421 + {
  422 + salary.PerformanceCompletionRate = 0;
  423 + }
  424 +
  425 + // 2.5 统计门店消耗金额
  426 + salary.StoreConsume = storeConsumeDict.ContainsKey(storeId) ? storeConsumeDict[storeId] : 0;
  427 +
  428 + // 2.6 统计进店消耗人数
  429 + salary.HeadCount = headcountDict.ContainsKey(storeId) ? headcountDict[storeId] : 0;
  430 +
  431 + // 2.7 计算考核指标(业绩、人头、消耗是否达标)
  432 + bool performanceReached = salary.StoreTotalPerformance >= salary.StoreLifeline;
  433 + bool headCountReached = salary.HeadCount >= salary.TargetHeadCount;
  434 + bool consumeReached = salary.StoreConsume >= salary.TargetConsume;
  435 +
  436 + salary.PerformanceReached = performanceReached ? "是" : "否";
  437 + salary.HeadCountReached = headCountReached ? "是" : "否";
  438 + salary.ConsumeReached = consumeReached ? "是" : "否";
  439 +
  440 + // 计算未达标指标数量
  441 + int unreachedCount = 0;
  442 + if (!performanceReached) unreachedCount++;
  443 + if (!headCountReached) unreachedCount++;
  444 + // 新店不考核消耗
  445 + if (!isNewStore && !consumeReached) unreachedCount++;
  446 +
  447 + salary.UnreachedIndicatorCount = unreachedCount;
  448 +
  449 + // 2.8 计算底薪
  450 + salary.BaseSalary = 4000m; // 固定底薪4000元
  451 +
  452 + // 考核扣款:老店每个指标500元,新店每个指标800元
  453 + if (isNewStore)
  454 + {
  455 + salary.AssessmentDeduction = unreachedCount * 800m;
  456 + }
  457 + else
  458 + {
  459 + salary.AssessmentDeduction = unreachedCount * 500m;
  460 + }
  461 +
  462 + // 旗舰店负奖励(800元)
  463 + bool isFlagshipStore = salary.StoreType.HasValue && salary.StoreType.Value == (int)StoreTypeEnum.旗舰店;
  464 + salary.FlagshipStoreDeduction = isFlagshipStore ? 800m : 0m;
  465 +
  466 + // 实际底薪 = 底薪 - 考核扣款 - 旗舰店负奖励
  467 + salary.ActualBaseSalary = salary.BaseSalary - salary.AssessmentDeduction - salary.FlagshipStoreDeduction;
  468 +
  469 + // 2.9 计算毛利
  470 + // 销售业绩 = 开单业绩 - 退款业绩
  471 + salary.SalesPerformance = salary.StoreTotalPerformance;
  472 +
  473 + // 产品物料(注意11月特殊规则已在查询时处理)
  474 + salary.ProductMaterial = productMaterialDict.ContainsKey(storeId) ? productMaterialDict[storeId] : 0;
  475 +
  476 + // 合作项目成本
  477 + salary.CooperationCost = cooperationCostDict.ContainsKey(storeId) ? cooperationCostDict[storeId] : 0;
  478 +
  479 + // 店内支出
  480 + salary.StoreExpense = storeExpenseDict.ContainsKey(storeId) ? storeExpenseDict[storeId] : 0;
  481 +
  482 + // 洗毛巾费用
  483 + salary.LaundryCost = laundryCostDict.ContainsKey(storeId) ? laundryCostDict[storeId] : 0;
  484 +
  485 + // 毛利 = 销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾
  486 + salary.GrossProfit = salary.SalesPerformance - salary.ProductMaterial - salary.CooperationCost - salary.StoreExpense - salary.LaundryCost;
  487 +
  488 + // 2.10 计算提成(基于毛利)
  489 + CalculateCommission(salary, isNewStore, performanceReached);
  490 +
  491 + // 2.11 考勤数据
  492 + if (attendanceDict.ContainsKey(storeManagerUser.Id))
  493 + {
  494 + var attendance = attendanceDict[storeManagerUser.Id];
  495 + salary.WorkingDays = (int)attendance.WorkDays;
  496 + salary.LeaveDays = (int)attendance.LeaveDays;
  497 + }
  498 + else
  499 + {
  500 + salary.WorkingDays = 0;
  501 + salary.LeaveDays = 0;
  502 + }
  503 +
  504 + // 2.12 计算应发工资
  505 + salary.GrossSalary = salary.ActualBaseSalary + salary.CommissionAmount;
  506 +
  507 + // 2.13 初始化扣款、补贴、奖金字段(默认值为0)
  508 + salary.MissingCard = 0;
  509 + salary.LateArrival = 0;
  510 + salary.LeaveDeduction = 0;
  511 + salary.SocialInsuranceDeduction = 0;
  512 + salary.RewardDeduction = 0;
  513 + salary.AccommodationDeduction = 0;
  514 + salary.StudyPeriodDeduction = 0;
  515 + salary.WorkClothesDeduction = 0;
  516 + salary.TotalDeduction = 0;
  517 +
  518 + salary.MonthlyTrainingSubsidy = 0;
  519 + salary.MonthlyTransportSubsidy = 0;
  520 + salary.LastMonthTrainingSubsidy = 0;
  521 + salary.LastMonthTransportSubsidy = 0;
  522 + salary.TotalSubsidy = 0;
  523 +
  524 + salary.Bonus = 0;
  525 + salary.ReturnPhoneDeposit = 0;
  526 + salary.ReturnAccommodationDeposit = 0;
  527 +
  528 + // 2.14 计算实发工资
  529 + salary.ActualSalary = salary.GrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus;
  530 +
  531 + // 2.15 初始化支付相关字段
  532 + salary.PaidAmount = 0;
  533 + salary.PendingAmount = salary.ActualSalary;
  534 + salary.LastMonthSupplement = 0;
  535 + salary.MonthlyTotalPayment = 0;
  536 +
  537 + storeManagerSalaryList.Add(salary);
  538 + }
  539 +
  540 + // 3. 保存数据
  541 + if (storeManagerSalaryList.Any())
  542 + {
  543 + // 先删除当月旧数据 (防止重复)
  544 + await _db.Deleteable<LqStoreManagerSalaryStatisticsEntity>()
  545 + .Where(x => x.StatisticsMonth == monthStr)
  546 + .ExecuteCommandAsync();
  547 +
  548 + await _db.Insertable(storeManagerSalaryList).ExecuteCommandAsync();
  549 + }
  550 + }
  551 +
  552 + /// <summary>
  553 + /// 计算提成(基于毛利)
  554 + /// </summary>
  555 + /// <param name="salary">工资实体</param>
  556 + /// <param name="isNewStore">是否新店</param>
  557 + /// <param name="performanceReached">业绩是否达标</param>
  558 + private void CalculateCommission(LqStoreManagerSalaryStatisticsEntity salary, bool isNewStore, bool performanceReached)
  559 + {
  560 + decimal grossProfit = salary.GrossProfit;
  561 +
  562 + // 确定提成比例
  563 + decimal commissionRate;
  564 +
  565 + if (isNewStore)
  566 + {
  567 + // 新店:根据门店分类和门店类型确定提成比例
  568 + int? storeCategory = salary.StoreCategory;
  569 + int? storeType = salary.StoreType;
  570 +
  571 + if (!storeCategory.HasValue)
  572 + {
  573 + throw new Exception($"新店【{salary.StoreName}】的门店分类未设置,无法计算提成");
  574 + }
  575 + if (!storeType.HasValue)
  576 + {
  577 + throw new Exception($"新店【{salary.StoreName}】的门店类型未设置,无法计算提成");
  578 + }
  579 +
  580 + // 新店根据门店分类和门店类型确定提成比例
  581 + // 这里需要根据实际业务规则确定新店的提成比例
  582 + // 暂时使用统一的提成比例,后续可以根据业务规则调整
  583 + if (performanceReached)
  584 + {
  585 + commissionRate = 0.035m; // 3.5%(业绩达标)
  586 + }
  587 + else
  588 + {
  589 + commissionRate = 0.03m; // 3%(业绩未达标)
  590 + }
  591 + }
  592 + else
  593 + {
  594 + // 老店:不区分门店分类和门店类型,使用统一比例
  595 + if (performanceReached)
  596 + {
  597 + commissionRate = 0.04m; // 4%(业绩达标)
  598 + }
  599 + else
  600 + {
  601 + commissionRate = 0.035m; // 3.5%(业绩未达标)
  602 + }
  603 + }
  604 +
  605 + salary.CommissionRate = commissionRate;
  606 + salary.CommissionAmount = grossProfit * commissionRate;
  607 + }
  608 + }
  609 +}
  610 +
... ...
sql/创建合作成本表和店内支出表.sql 0 → 100644
  1 +-- ============================================
  2 +-- 创建合作成本表和店内支出表
  3 +-- ============================================
  4 +-- 说明:用于店长工资计算中的毛利计算
  5 +-- 执行时间:2025年
  6 +-- ============================================
  7 +
  8 +-- ============================================
  9 +-- 1. 创建合作成本表 (lq_cooperation_cost)
  10 +-- ============================================
  11 +CREATE TABLE IF NOT EXISTS `lq_cooperation_cost` (
  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) NULL COMMENT '门店名称',
  15 + `F_Year` INT NOT NULL COMMENT '年份',
  16 + `F_Month` VARCHAR(6) NOT NULL COMMENT '月份(YYYYMM格式)',
  17 + `F_TotalAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合计金额',
  18 + `F_Remarks` VARCHAR(1000) NULL COMMENT '备注说明',
  19 + `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)',
  20 + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID',
  21 + `F_CreateTime` DATETIME NULL COMMENT '创建时间',
  22 + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID',
  23 + `F_UpdateTime` DATETIME NULL COMMENT '更新时间',
  24 + PRIMARY KEY (`F_Id`),
  25 + KEY `idx_store_month` (`F_StoreId`, `F_Year`, `F_Month`),
  26 + KEY `idx_month` (`F_Year`, `F_Month`),
  27 + KEY `idx_store_id` (`F_StoreId`)
  28 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合作成本表';
  29 +
  30 +-- ============================================
  31 +-- 2. 创建店内支出表 (lq_store_expense)
  32 +-- ============================================
  33 +CREATE TABLE IF NOT EXISTS `lq_store_expense` (
  34 + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID',
  35 + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)',
  36 + `F_StoreName` VARCHAR(200) NULL COMMENT '门店名称',
  37 + `F_ExpenseCategoryId` VARCHAR(50) NULL COMMENT '支出分类ID(关联lq_reimbursement_category.F_Id)',
  38 + `F_ExpenseCategoryName` VARCHAR(200) NULL COMMENT '支出分类名称',
  39 + `F_ExpenseDate` DATETIME NOT NULL COMMENT '支出日期',
  40 + `F_UnitPrice` DECIMAL(18,2) DEFAULT 0.00 COMMENT '单价',
  41 + `F_Quantity` INT DEFAULT 0 COMMENT '数量',
  42 + `F_Amount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '金额',
  43 + `F_Memo` VARCHAR(1000) NULL COMMENT '备注说明',
  44 + `F_Attachment` TEXT NULL COMMENT '附件(JSON格式)',
  45 + `F_RelatedReimbursementId` VARCHAR(50) NULL COMMENT '关联报销申请ID(可选)',
  46 + `F_RelatedPurchaseRecordId` VARCHAR(50) NULL COMMENT '关联购买记录ID(可选)',
  47 + `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)',
  48 + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID',
  49 + `F_CreateTime` DATETIME NULL COMMENT '创建时间',
  50 + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID',
  51 + `F_UpdateTime` DATETIME NULL COMMENT '更新时间',
  52 + PRIMARY KEY (`F_Id`),
  53 + KEY `idx_store_date` (`F_StoreId`, `F_ExpenseDate`),
  54 + KEY `idx_expense_date` (`F_ExpenseDate`),
  55 + KEY `idx_category` (`F_ExpenseCategoryId`),
  56 + KEY `idx_store_id` (`F_StoreId`),
  57 + KEY `idx_related_reimbursement` (`F_RelatedReimbursementId`),
  58 + KEY `idx_related_purchase` (`F_RelatedPurchaseRecordId`)
  59 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店内支出表';
  60 +
... ...
sql/创建库存使用申请审批流程表.sql
... ... @@ -100,3 +100,5 @@ SET u.`F_UnitPrice` = p.`F_Price`,
100 100 WHERE u.`F_UnitPrice` = 0 OR u.`F_UnitPrice` IS NULL;
101 101  
102 102  
  103 +
  104 +
... ...
sql/创建店长工资统计表.sql 0 → 100644
  1 +-- 创建店长工资统计表
  2 +-- 表名:lq_store_manager_salary_statistics
  3 +
  4 +CREATE TABLE IF NOT EXISTS `lq_store_manager_salary_statistics` (
  5 + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID',
  6 +
  7 + -- 基础信息
  8 + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)',
  9 + `F_StoreName` VARCHAR(200) NULL COMMENT '门店名称',
  10 + `F_Position` VARCHAR(50) NULL COMMENT '核算岗位(店长)',
  11 + `F_EmployeeId` VARCHAR(50) NOT NULL COMMENT '员工ID(关联BASE_USER.F_Id)',
  12 + `F_EmployeeName` VARCHAR(100) NULL COMMENT '员工姓名',
  13 + `F_StatisticsMonth` VARCHAR(6) NOT NULL COMMENT '统计月份(YYYYMM格式)',
  14 +
  15 + -- 门店信息
  16 + `F_StoreType` INT NULL COMMENT '门店类型(判断旗舰店)',
  17 + `F_StoreCategory` INT NULL COMMENT '门店类别(A/B/C类)',
  18 + `F_IsNewStore` VARCHAR(10) NULL COMMENT '是否新店(是/否)',
  19 + `F_NewStoreProtectionStage` INT DEFAULT 0 COMMENT '新店保护阶段',
  20 +
  21 + -- 业绩相关
  22 + `F_StoreTotalPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店总业绩(开单业绩-退卡业绩)',
  23 + `F_StoreBillingPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店开单业绩',
  24 + `F_StoreRefundPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店退卡业绩',
  25 + `F_StoreLifeline` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店生命线',
  26 + `F_PerformanceCompletionRate` DECIMAL(18,4) DEFAULT 0.0000 COMMENT '业绩完成率',
  27 + `F_PerformanceReached` VARCHAR(10) NULL COMMENT '业绩是否达标(是/否)',
  28 +
  29 + -- 考核相关
  30 + `F_HeadCount` INT DEFAULT 0 COMMENT '进店消耗人数',
  31 + `F_TargetHeadCount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '目标人头数',
  32 + `F_HeadCountReached` VARCHAR(10) NULL COMMENT '人头是否达标(是/否)',
  33 + `F_StoreConsume` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店消耗金额',
  34 + `F_TargetConsume` DECIMAL(18,2) DEFAULT 0.00 COMMENT '目标消耗金额',
  35 + `F_ConsumeReached` VARCHAR(10) NULL COMMENT '消耗是否达标(是/否)',
  36 + `F_UnreachedIndicatorCount` INT DEFAULT 0 COMMENT '未达标指标数量',
  37 + `F_AssessmentDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '考核扣款金额',
  38 +
  39 + -- 毛利相关
  40 + `F_SalesPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '销售业绩(开单业绩-退款业绩)',
  41 + `F_ProductMaterial` DECIMAL(18,2) DEFAULT 0.00 COMMENT '产品物料(仓库领用金额)',
  42 + `F_CooperationCost` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合作项目成本',
  43 + `F_StoreExpense` DECIMAL(18,2) DEFAULT 0.00 COMMENT '店内支出',
  44 + `F_LaundryCost` DECIMAL(18,2) DEFAULT 0.00 COMMENT '洗毛巾费用',
  45 + `F_GrossProfit` DECIMAL(18,2) DEFAULT 0.00 COMMENT '毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾)',
  46 +
  47 + -- 提成相关
  48 + `F_CommissionRate` DECIMAL(18,4) DEFAULT 0.0000 COMMENT '提成比例',
  49 + `F_CommissionAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '提成金额(基于毛利)',
  50 +
  51 + -- 底薪相关
  52 + `F_BaseSalary` DECIMAL(18,2) DEFAULT 4000.00 COMMENT '底薪金额(固定4000元)',
  53 + `F_FlagshipStoreDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '旗舰店负奖励(800元)',
  54 + `F_ActualBaseSalary` DECIMAL(18,2) DEFAULT 0.00 COMMENT '实际底薪(底薪-考核扣款-旗舰店负奖励)',
  55 +
  56 + -- 考勤相关
  57 + `F_WorkingDays` INT DEFAULT 0 COMMENT '在店天数',
  58 + `F_LeaveDays` INT DEFAULT 0 COMMENT '请假天数',
  59 +
  60 + -- 工资相关
  61 + `F_GrossSalary` DECIMAL(18,2) DEFAULT 0.00 COMMENT '应发工资(实际底薪+提成)',
  62 + `F_ActualSalary` DECIMAL(18,2) DEFAULT 0.00 COMMENT '实发工资(应发工资-扣款合计+补贴合计+奖金)',
  63 +
  64 + -- 扣款相关
  65 + `F_MissingCard` DECIMAL(18,2) DEFAULT 0.00 COMMENT '缺卡扣款',
  66 + `F_LateArrival` DECIMAL(18,2) DEFAULT 0.00 COMMENT '迟到扣款',
  67 + `F_LeaveDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '请假扣款',
  68 + `F_SocialInsuranceDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣社保',
  69 + `F_RewardDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣除奖励',
  70 + `F_AccommodationDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣住宿费',
  71 + `F_StudyPeriodDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣学习期费用',
  72 + `F_WorkClothesDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣工作服费用',
  73 + `F_TotalDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣款合计',
  74 +
  75 + -- 补贴相关
  76 + `F_MonthlyTrainingSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '当月培训补贴',
  77 + `F_MonthlyTransportSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '当月交通补贴',
  78 + `F_LastMonthTrainingSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '上月培训补贴',
  79 + `F_LastMonthTransportSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '上月交通补贴',
  80 + `F_TotalSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '补贴合计',
  81 +
  82 + -- 奖金相关
  83 + `F_Bonus` DECIMAL(18,2) DEFAULT 0.00 COMMENT '发奖金',
  84 + `F_ReturnPhoneDeposit` DECIMAL(18,2) DEFAULT 0.00 COMMENT '退手机押金',
  85 + `F_ReturnAccommodationDeposit` DECIMAL(18,2) DEFAULT 0.00 COMMENT '退住宿押金',
  86 +
  87 + -- 支付相关
  88 + `F_MonthlyPaymentStatus` VARCHAR(50) NULL COMMENT '当月是否发放',
  89 + `F_PaidAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '支付金额',
  90 + `F_PendingAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '待支付金额',
  91 + `F_LastMonthSupplement` DECIMAL(18,2) DEFAULT 0.00 COMMENT '补发上月',
  92 + `F_MonthlyTotalPayment` DECIMAL(18,2) DEFAULT 0.00 COMMENT '当月支付总额',
  93 +
  94 + -- 其他
  95 + `F_IsLocked` INT DEFAULT 0 COMMENT '是否锁定(0未锁定,1已锁定)',
  96 + `F_CreateTime` DATETIME NULL COMMENT '创建时间',
  97 + `F_UpdateTime` DATETIME NULL COMMENT '更新时间',
  98 + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人',
  99 + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人',
  100 +
  101 + PRIMARY KEY (`F_Id`),
  102 + KEY `idx_store_month` (`F_StoreId`, `F_StatisticsMonth`),
  103 + KEY `idx_employee_month` (`F_EmployeeId`, `F_StatisticsMonth`),
  104 + KEY `idx_statistics_month` (`F_StatisticsMonth`),
  105 + KEY `idx_store_id` (`F_StoreId`),
  106 + KEY `idx_employee_id` (`F_EmployeeId`)
  107 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店长工资统计表';
  108 +
... ...
合作成本和店内支出-前端调用说明.md 0 → 100644
  1 +# 合作成本和店内支出 - 前端调用说明
  2 +
  3 +## 📋 目录
  4 +- [业务流程概述](#业务流程概述)
  5 +- [合作成本接口列表](#合作成本接口列表)
  6 +- [店内支出接口列表](#店内支出接口列表)
  7 +- [完整流程示例](#完整流程示例)
  8 +- [接口详细说明](#接口详细说明)
  9 +- [数据验证说明](#数据验证说明)
  10 +- [注意事项](#注意事项)
  11 +
  12 +## 🔄 业务流程概述
  13 +
  14 +### 合作成本管理
  15 +合作成本用于记录门店每月的合作项目成本,用于店长工资计算中的毛利计算。
  16 +
  17 +**核心功能:**
  18 +1. **创建合作成本** - 录入门店某年某月的合作成本金额
  19 +2. **查询合作成本** - 支持列表查询和详情查询,可按门店、年份、月份筛选
  20 +3. **更新合作成本** - 修改已创建的合作成本记录
  21 +4. **删除合作成本** - 删除合作成本记录(逻辑删除)
  22 +5. **导入合作成本** - 通过Excel批量导入合作成本数据
  23 +6. **导出合作成本** - 导出合作成本数据为Excel文件
  24 +
  25 +### 店内支出管理
  26 +店内支出用于记录门店每月的各项支出,用于店长工资计算中的毛利计算。
  27 +
  28 +**核心功能:**
  29 +1. **创建店内支出** - 录入门店的支出明细(支持分类、单价、数量、金额等)
  30 +2. **查询店内支出** - 支持列表查询和详情查询,可按门店、分类、日期范围筛选
  31 +3. **更新店内支出** - 修改已创建的店内支出记录
  32 +4. **删除店内支出** - 删除店内支出记录(逻辑删除)
  33 +5. **导入店内支出** - 通过Excel批量导入店内支出数据
  34 +6. **导出店内支出** - 导出店内支出数据为Excel文件
  35 +
  36 +## 📡 合作成本接口列表
  37 +
  38 +| 接口 | 方法 | 路径 | 说明 |
  39 +|------|------|------|------|
  40 +| 获取合作成本详情 | GET | `/api/Extend/LqCooperationCost/{id}` | 根据ID获取合作成本详情 |
  41 +| 获取合作成本列表 | GET | `/api/Extend/LqCooperationCost` | 分页查询合作成本列表,支持多条件筛选 |
  42 +| 获取全部数据(不分页) | GET | `/api/Extend/LqCooperationCost/Actions/GetNoPagingList` | 获取全部数据,用于导出 |
  43 +| 创建合作成本 | POST | `/api/Extend/LqCooperationCost` | 创建新的合作成本记录 |
  44 +| 更新合作成本 | PUT | `/api/Extend/LqCooperationCost/{id}` | 更新合作成本记录 |
  45 +| 删除合作成本 | DELETE | `/api/Extend/LqCooperationCost/{id}` | 删除合作成本记录(逻辑删除) |
  46 +| 导出合作成本 | GET | `/api/Extend/LqCooperationCost/Actions/Export` | 导出合作成本数据为Excel |
  47 +| 导入合作成本 | POST | `/api/Extend/LqCooperationCost/Actions/Import` | 通过Excel导入合作成本数据 |
  48 +
  49 +## 📡 店内支出接口列表
  50 +
  51 +| 接口 | 方法 | 路径 | 说明 |
  52 +|------|------|------|------|
  53 +| 获取店内支出详情 | GET | `/api/Extend/LqStoreExpense/{id}` | 根据ID获取店内支出详情 |
  54 +| 获取店内支出列表 | GET | `/api/Extend/LqStoreExpense` | 分页查询店内支出列表,支持多条件筛选 |
  55 +| 获取全部数据(不分页) | GET | `/api/Extend/LqStoreExpense/Actions/GetNoPagingList` | 获取全部数据,用于导出 |
  56 +| 创建店内支出 | POST | `/api/Extend/LqStoreExpense` | 创建新的店内支出记录 |
  57 +| 更新店内支出 | PUT | `/api/Extend/LqStoreExpense/{id}` | 更新店内支出记录 |
  58 +| 删除店内支出 | DELETE | `/api/Extend/LqStoreExpense/{id}` | 删除店内支出记录(逻辑删除) |
  59 +| 导出店内支出 | GET | `/api/Extend/LqStoreExpense/Actions/Export` | 导出店内支出数据为Excel |
  60 +| 导入店内支出 | POST | `/api/Extend/LqStoreExpense/Actions/Import` | 通过Excel导入店内支出数据 |
  61 +
  62 +## 🚀 完整流程示例
  63 +
  64 +### 合作成本管理流程
  65 +
  66 +#### 步骤1:创建合作成本
  67 +
  68 +**接口:** `POST /api/Extend/LqCooperationCost`
  69 +
  70 +**请求示例:**
  71 +```javascript
  72 +const response = await request({
  73 + url: '/api/Extend/LqCooperationCost',
  74 + method: 'POST',
  75 + data: {
  76 + storeId: '1649328471923847172', // 门店ID(必填)
  77 + storeName: '绿纤华润店', // 门店名称(可选,不传则自动查询)
  78 + year: 2025, // 年份(必填)
  79 + month: '202511', // 月份(必填,格式:YYYYMM,如202511表示2025年11月)
  80 + totalAmount: 3000.00, // 合计金额(必填)
  81 + remarks: '测试数据-合作成本' // 备注说明(可选)
  82 + }
  83 +});
  84 +
  85 +// 响应示例
  86 +// {
  87 +// "code": 200,
  88 +// "msg": "操作成功",
  89 +// "data": null
  90 +// }
  91 +```
  92 +
  93 +**说明:**
  94 +- 如果未提供门店名称,系统会根据门店ID自动查询并填充
  95 +- 月份格式必须为YYYYMM(如:202511表示2025年11月)
  96 +- 同一门店、同一年份、同一月份只能有一条有效记录(如果已存在会报错)
  97 +
  98 +#### 步骤2:查询合作成本列表
  99 +
  100 +**接口:** `GET /api/Extend/LqCooperationCost`
  101 +
  102 +**请求示例:**
  103 +```javascript
  104 +const response = await request({
  105 + url: '/api/Extend/LqCooperationCost',
  106 + method: 'GET',
  107 + params: {
  108 + currentPage: 1, // 当前页码(必填)
  109 + pageSize: 10, // 每页数量(必填)
  110 + storeId: '1649328471923847172', // 门店ID(可选)
  111 + storeName: '绿纤华润店', // 门店名称(可选,模糊查询)
  112 + year: 2025, // 年份(可选)
  113 + month: '202511', // 月份(可选,格式:YYYYMM)
  114 + sidx: 'CreateTime', // 排序字段(可选,默认:CreateTime)
  115 + sort: 'desc' // 排序方式(可选,asc/desc,默认:desc)
  116 + }
  117 +});
  118 +
  119 +// 响应示例
  120 +// {
  121 +// "code": 200,
  122 +// "msg": "操作成功",
  123 +// "data": {
  124 +// "list": [
  125 +// {
  126 +// "id": "768041985045955845",
  127 +// "storeId": "1649328471923847172",
  128 +// "storeName": "绿纤华润店",
  129 +// "year": 2025,
  130 +// "month": "202511",
  131 +// "totalAmount": 3000.00,
  132 +// "remarks": "测试数据-合作成本",
  133 +// "createUser": "admin",
  134 +// "createTime": "2025-01-10T10:00:00",
  135 +// "updateUser": "admin",
  136 +// "updateTime": "2025-01-10T10:00:00"
  137 +// }
  138 +// ],
  139 +// "pagination": {
  140 +// "total": 1,
  141 +// "pageSize": 10,
  142 +// "currentPage": 1
  143 +// }
  144 +// }
  145 +// }
  146 +```
  147 +
  148 +#### 步骤3:更新合作成本
  149 +
  150 +**接口:** `PUT /api/Extend/LqCooperationCost/{id}`
  151 +
  152 +**请求示例:**
  153 +```javascript
  154 +const response = await request({
  155 + url: `/api/Extend/LqCooperationCost/${id}`,
  156 + method: 'PUT',
  157 + data: {
  158 + storeId: '1649328471923847172',
  159 + storeName: '绿纤华润店',
  160 + year: 2025,
  161 + month: '202511',
  162 + totalAmount: 3500.00, // 修改金额
  163 + remarks: '更新后的备注'
  164 + }
  165 +});
  166 +```
  167 +
  168 +#### 步骤4:删除合作成本
  169 +
  170 +**接口:** `DELETE /api/Extend/LqCooperationCost/{id}`
  171 +
  172 +**请求示例:**
  173 +```javascript
  174 +const response = await request({
  175 + url: `/api/Extend/LqCooperationCost/${id}`,
  176 + method: 'DELETE'
  177 +});
  178 +```
  179 +
  180 +#### 步骤5:导出合作成本
  181 +
  182 +**接口:** `GET /api/Extend/LqCooperationCost/Actions/Export`
  183 +
  184 +**请求示例:**
  185 +```javascript
  186 +const response = await request({
  187 + url: '/api/Extend/LqCooperationCost/Actions/Export',
  188 + method: 'GET',
  189 + params: {
  190 + storeId: '1649328471923847172', // 可选
  191 + year: 2025, // 可选
  192 + month: '202511' // 可选
  193 + },
  194 + responseType: 'blob' // 重要:设置响应类型为blob
  195 +});
  196 +
  197 +// 下载文件
  198 +const blob = new Blob([response.data]);
  199 +const url = window.URL.createObjectURL(blob);
  200 +const link = document.createElement('a');
  201 +link.href = url;
  202 +link.setAttribute('download', `合作成本表_${new Date().getTime()}.xlsx`);
  203 +document.body.appendChild(link);
  204 +link.click();
  205 +document.body.removeChild(link);
  206 +```
  207 +
  208 +#### 步骤6:导入合作成本
  209 +
  210 +**接口:** `POST /api/Extend/LqCooperationCost/Actions/Import`
  211 +
  212 +**请求示例:**
  213 +```javascript
  214 +const formData = new FormData();
  215 +formData.append('file', file); // file是File对象
  216 +
  217 +const response = await request({
  218 + url: '/api/Extend/LqCooperationCost/Actions/Import',
  219 + method: 'POST',
  220 + data: formData,
  221 + headers: {
  222 + 'Content-Type': 'multipart/form-data'
  223 + }
  224 +});
  225 +
  226 +// 响应示例
  227 +// {
  228 +// "code": 200,
  229 +// "msg": "导入成功",
  230 +// "data": {
  231 +// "successCount": 10,
  232 +// "failCount": 2,
  233 +// "failMessages": [
  234 +// "第3行:门店ID不存在",
  235 +// "第5行:该门店2025年11月的记录已存在"
  236 +// ]
  237 +// }
  238 +// }
  239 +```
  240 +
  241 +**Excel格式要求:**
  242 +- 第一行为表头:门店ID、门店名称、年份、月份、合计金额、备注
  243 +- 门店ID和门店名称至少填写一个
  244 +- 月份格式:YYYYMM(如:202511)
  245 +- 合计金额必须为数字
  246 +
  247 +### 店内支出管理流程
  248 +
  249 +#### 步骤1:创建店内支出
  250 +
  251 +**接口:** `POST /api/Extend/LqStoreExpense`
  252 +
  253 +**请求示例:**
  254 +```javascript
  255 +const response = await request({
  256 + url: '/api/Extend/LqStoreExpense',
  257 + method: 'POST',
  258 + data: {
  259 + storeId: '1649328471923847172', // 门店ID(必填)
  260 + storeName: '绿纤华润店', // 门店名称(可选,不传则自动查询)
  261 + expenseCategoryId: '11156146041171400', // 支出分类ID(可选)
  262 + expenseCategoryName: '日常支出', // 支出分类名称(可选)
  263 + expenseDate: '2025-11-15T00:00:00', // 支出日期(必填)
  264 + unitPrice: 2000.00, // 单价(可选)
  265 + quantity: 1, // 数量(可选)
  266 + amount: 2000.00, // 金额(必填)
  267 + memo: '测试数据-店内支出', // 备注说明(可选)
  268 + attachment: [], // 附件(可选,FileControlsModel数组)
  269 + relatedReimbursementId: '', // 关联报销申请ID(可选)
  270 + relatedPurchaseRecordId: '' // 关联购买记录ID(可选)
  271 + }
  272 +});
  273 +
  274 +// 响应示例
  275 +// {
  276 +// "code": 200,
  277 +// "msg": "操作成功",
  278 +// "data": null
  279 +// }
  280 +```
  281 +
  282 +**说明:**
  283 +- 如果未提供门店名称,系统会根据门店ID自动查询并填充
  284 +- 金额字段为必填,单价和数量为可选(如果提供了单价和数量,系统会验证:金额 = 单价 × 数量)
  285 +- 支持附件上传(FileControlsModel格式)
  286 +
  287 +#### 步骤2:查询店内支出列表
  288 +
  289 +**接口:** `GET /api/Extend/LqStoreExpense`
  290 +
  291 +**请求示例:**
  292 +```javascript
  293 +const response = await request({
  294 + url: '/api/Extend/LqStoreExpense',
  295 + method: 'GET',
  296 + params: {
  297 + currentPage: 1, // 当前页码(必填)
  298 + pageSize: 10, // 每页数量(必填)
  299 + storeId: '1649328471923847172', // 门店ID(可选)
  300 + storeName: '绿纤华润店', // 门店名称(可选,模糊查询)
  301 + expenseCategoryId: '11156146041171400', // 支出分类ID(可选)
  302 + expenseDateStart: '2025-11-01', // 支出日期开始(可选)
  303 + expenseDateEnd: '2025-11-30', // 支出日期结束(可选)
  304 + sidx: 'ExpenseDate', // 排序字段(可选,默认:ExpenseDate)
  305 + sort: 'desc' // 排序方式(可选,asc/desc,默认:desc)
  306 + }
  307 +});
  308 +
  309 +// 响应示例
  310 +// {
  311 +// "code": 200,
  312 +// "msg": "操作成功",
  313 +// "data": {
  314 +// "list": [
  315 +// {
  316 +// "id": "768041985045955845",
  317 +// "storeId": "1649328471923847172",
  318 +// "storeName": "绿纤华润店",
  319 +// "expenseCategoryId": "11156146041171400",
  320 +// "expenseCategoryName": "日常支出",
  321 +// "expenseDate": "2025-11-15T00:00:00",
  322 +// "unitPrice": 2000.00,
  323 +// "quantity": 1,
  324 +// "amount": 2000.00,
  325 +// "memo": "测试数据-店内支出",
  326 +// "attachment": [],
  327 +// "relatedReimbursementId": "",
  328 +// "relatedPurchaseRecordId": "",
  329 +// "createUser": "admin",
  330 +// "createTime": "2025-01-10T10:00:00",
  331 +// "updateUser": "admin",
  332 +// "updateTime": "2025-01-10T10:00:00"
  333 +// }
  334 +// ],
  335 +// "pagination": {
  336 +// "total": 1,
  337 +// "pageSize": 10,
  338 +// "currentPage": 1
  339 +// }
  340 +// }
  341 +// }
  342 +```
  343 +
  344 +#### 步骤3:更新店内支出
  345 +
  346 +**接口:** `PUT /api/Extend/LqStoreExpense/{id}`
  347 +
  348 +**请求示例:**
  349 +```javascript
  350 +const response = await request({
  351 + url: `/api/Extend/LqStoreExpense/${id}`,
  352 + method: 'PUT',
  353 + data: {
  354 + storeId: '1649328471923847172',
  355 + storeName: '绿纤华润店',
  356 + expenseCategoryId: '11156146041171400',
  357 + expenseCategoryName: '日常支出',
  358 + expenseDate: '2025-11-15T00:00:00',
  359 + unitPrice: 2500.00, // 修改单价
  360 + quantity: 1,
  361 + amount: 2500.00, // 修改金额
  362 + memo: '更新后的备注',
  363 + attachment: []
  364 + }
  365 +});
  366 +```
  367 +
  368 +#### 步骤4:删除店内支出
  369 +
  370 +**接口:** `DELETE /api/Extend/LqStoreExpense/{id}`
  371 +
  372 +**请求示例:**
  373 +```javascript
  374 +const response = await request({
  375 + url: `/api/Extend/LqStoreExpense/${id}`,
  376 + method: 'DELETE'
  377 +});
  378 +```
  379 +
  380 +#### 步骤5:导出店内支出
  381 +
  382 +**接口:** `GET /api/Extend/LqStoreExpense/Actions/Export`
  383 +
  384 +**请求示例:**
  385 +```javascript
  386 +const response = await request({
  387 + url: '/api/Extend/LqStoreExpense/Actions/Export',
  388 + method: 'GET',
  389 + params: {
  390 + storeId: '1649328471923847172', // 可选
  391 + expenseCategoryId: '11156146041171400', // 可选
  392 + expenseDateStart: '2025-11-01', // 可选
  393 + expenseDateEnd: '2025-11-30' // 可选
  394 + },
  395 + responseType: 'blob' // 重要:设置响应类型为blob
  396 +});
  397 +
  398 +// 下载文件(同合作成本导出)
  399 +```
  400 +
  401 +#### 步骤6:导入店内支出
  402 +
  403 +**接口:** `POST /api/Extend/LqStoreExpense/Actions/Import`
  404 +
  405 +**请求示例:**
  406 +```javascript
  407 +const formData = new FormData();
  408 +formData.append('file', file); // file是File对象
  409 +
  410 +const response = await request({
  411 + url: '/api/Extend/LqStoreExpense/Actions/Import',
  412 + method: 'POST',
  413 + data: formData,
  414 + headers: {
  415 + 'Content-Type': 'multipart/form-data'
  416 + }
  417 +});
  418 +
  419 +// 响应示例(同合作成本导入)
  420 +```
  421 +
  422 +**Excel格式要求:**
  423 +- 第一行为表头:门店ID、门店名称、支出分类ID、支出分类名称、支出日期、单价、数量、金额、备注
  424 +- 门店ID和门店名称至少填写一个
  425 +- 支出日期格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss
  426 +- 金额必须为数字
  427 +
  428 +## 📝 接口详细说明
  429 +
  430 +### 合作成本接口
  431 +
  432 +#### 1. 获取合作成本详情
  433 +
  434 +**接口:** `GET /api/Extend/LqCooperationCost/{id}`
  435 +
  436 +**路径参数:**
  437 +- `id`:合作成本记录ID(必填)
  438 +
  439 +**响应示例:**
  440 +```json
  441 +{
  442 + "code": 200,
  443 + "msg": "操作成功",
  444 + "data": {
  445 + "id": "768041985045955845",
  446 + "storeId": "1649328471923847172",
  447 + "storeName": "绿纤华润店",
  448 + "year": 2025,
  449 + "month": "202511",
  450 + "totalAmount": 3000.00,
  451 + "remarks": "测试数据-合作成本",
  452 + "createUser": "admin",
  453 + "createTime": "2025-01-10T10:00:00",
  454 + "updateUser": "admin",
  455 + "updateTime": "2025-01-10T10:00:00"
  456 + }
  457 +}
  458 +```
  459 +
  460 +#### 2. 获取合作成本列表
  461 +
  462 +**接口:** `GET /api/Extend/LqCooperationCost`
  463 +
  464 +**查询参数:**
  465 +- `currentPage`:当前页码(必填,默认:1)
  466 +- `pageSize`:每页数量(必填,默认:10)
  467 +- `storeId`:门店ID(可选)
  468 +- `storeName`:门店名称(可选,模糊查询)
  469 +- `year`:年份(可选)
  470 +- `month`:月份(可选,格式:YYYYMM)
  471 +- `sidx`:排序字段(可选,默认:CreateTime)
  472 +- `sort`:排序方式(可选,asc/desc,默认:desc)
  473 +
  474 +**响应格式:** 同列表查询标准格式
  475 +
  476 +#### 3. 创建合作成本
  477 +
  478 +**接口:** `POST /api/Extend/LqCooperationCost`
  479 +
  480 +**请求体:**
  481 +```json
  482 +{
  483 + "storeId": "1649328471923847172", // 门店ID(必填)
  484 + "storeName": "绿纤华润店", // 门店名称(可选)
  485 + "year": 2025, // 年份(必填)
  486 + "month": "202511", // 月份(必填,格式:YYYYMM)
  487 + "totalAmount": 3000.00, // 合计金额(必填)
  488 + "remarks": "备注说明" // 备注(可选)
  489 +}
  490 +```
  491 +
  492 +**验证规则:**
  493 +- 门店ID必填
  494 +- 年份必填,必须大于0
  495 +- 月份必填,格式必须为YYYYMM(6位数字)
  496 +- 合计金额必填,必须大于等于0
  497 +- 同一门店、同一年份、同一月份只能有一条有效记录
  498 +
  499 +#### 4. 更新合作成本
  500 +
  501 +**接口:** `PUT /api/Extend/LqCooperationCost/{id}`
  502 +
  503 +**路径参数:**
  504 +- `id`:合作成本记录ID(必填)
  505 +
  506 +**请求体:** 同创建接口
  507 +
  508 +#### 5. 删除合作成本
  509 +
  510 +**接口:** `DELETE /api/Extend/LqCooperationCost/{id}`
  511 +
  512 +**路径参数:**
  513 +- `id`:合作成本记录ID(必填)
  514 +
  515 +**说明:** 执行逻辑删除,不会真正删除数据
  516 +
  517 +### 店内支出接口
  518 +
  519 +#### 1. 获取店内支出详情
  520 +
  521 +**接口:** `GET /api/Extend/LqStoreExpense/{id}`
  522 +
  523 +**路径参数:**
  524 +- `id`:店内支出记录ID(必填)
  525 +
  526 +**响应示例:**
  527 +```json
  528 +{
  529 + "code": 200,
  530 + "msg": "操作成功",
  531 + "data": {
  532 + "id": "768041985045955845",
  533 + "storeId": "1649328471923847172",
  534 + "storeName": "绿纤华润店",
  535 + "expenseCategoryId": "11156146041171400",
  536 + "expenseCategoryName": "日常支出",
  537 + "expenseDate": "2025-11-15T00:00:00",
  538 + "unitPrice": 2000.00,
  539 + "quantity": 1,
  540 + "amount": 2000.00,
  541 + "memo": "测试数据-店内支出",
  542 + "attachment": [],
  543 + "relatedReimbursementId": "",
  544 + "relatedPurchaseRecordId": "",
  545 + "createUser": "admin",
  546 + "createTime": "2025-01-10T10:00:00",
  547 + "updateUser": "admin",
  548 + "updateTime": "2025-01-10T10:00:00"
  549 + }
  550 +}
  551 +```
  552 +
  553 +#### 2. 获取店内支出列表
  554 +
  555 +**接口:** `GET /api/Extend/LqStoreExpense`
  556 +
  557 +**查询参数:**
  558 +- `currentPage`:当前页码(必填,默认:1)
  559 +- `pageSize`:每页数量(必填,默认:10)
  560 +- `storeId`:门店ID(可选)
  561 +- `storeName`:门店名称(可选,模糊查询)
  562 +- `expenseCategoryId`:支出分类ID(可选)
  563 +- `expenseDateStart`:支出日期开始(可选,格式:YYYY-MM-DD)
  564 +- `expenseDateEnd`:支出日期结束(可选,格式:YYYY-MM-DD)
  565 +- `sidx`:排序字段(可选,默认:ExpenseDate)
  566 +- `sort`:排序方式(可选,asc/desc,默认:desc)
  567 +
  568 +**响应格式:** 同列表查询标准格式
  569 +
  570 +#### 3. 创建店内支出
  571 +
  572 +**接口:** `POST /api/Extend/LqStoreExpense`
  573 +
  574 +**请求体:**
  575 +```json
  576 +{
  577 + "storeId": "1649328471923847172", // 门店ID(必填)
  578 + "storeName": "绿纤华润店", // 门店名称(可选)
  579 + "expenseCategoryId": "11156146041171400", // 支出分类ID(可选)
  580 + "expenseCategoryName": "日常支出", // 支出分类名称(可选)
  581 + "expenseDate": "2025-11-15T00:00:00", // 支出日期(必填)
  582 + "unitPrice": 2000.00, // 单价(可选)
  583 + "quantity": 1, // 数量(可选)
  584 + "amount": 2000.00, // 金额(必填)
  585 + "memo": "备注说明", // 备注(可选)
  586 + "attachment": [], // 附件(可选,FileControlsModel数组)
  587 + "relatedReimbursementId": "", // 关联报销申请ID(可选)
  588 + "relatedPurchaseRecordId": "" // 关联购买记录ID(可选)
  589 +}
  590 +```
  591 +
  592 +**验证规则:**
  593 +- 门店ID必填
  594 +- 支出日期必填
  595 +- 金额必填,必须大于等于0
  596 +- 如果提供了单价和数量,系统会验证:金额 = 单价 × 数量
  597 +
  598 +#### 4. 更新店内支出
  599 +
  600 +**接口:** `PUT /api/Extend/LqStoreExpense/{id}`
  601 +
  602 +**路径参数:**
  603 +- `id`:店内支出记录ID(必填)
  604 +
  605 +**请求体:** 同创建接口
  606 +
  607 +#### 5. 删除店内支出
  608 +
  609 +**接口:** `DELETE /api/Extend/LqStoreExpense/{id}`
  610 +
  611 +**路径参数:**
  612 +- `id`:店内支出记录ID(必填)
  613 +
  614 +**说明:** 执行逻辑删除,不会真正删除数据
  615 +
  616 +## ✅ 数据验证说明
  617 +
  618 +### 合作成本验证规则
  619 +
  620 +1. **门店ID验证**
  621 + - 必填
  622 + - 如果门店ID不存在,会报错
  623 +
  624 +2. **年份验证**
  625 + - 必填
  626 + - 必须为整数,大于0
  627 +
  628 +3. **月份验证**
  629 + - 必填
  630 + - 格式必须为YYYYMM(6位数字,如:202511)
  631 + - 月份必须在1-12之间
  632 +
  633 +4. **合计金额验证**
  634 + - 必填
  635 + - 必须为数字,大于等于0
  636 +
  637 +5. **唯一性验证**
  638 + - 同一门店、同一年份、同一月份只能有一条有效记录
  639 + - 如果已存在,创建时会报错
  640 +
  641 +### 店内支出验证规则
  642 +
  643 +1. **门店ID验证**
  644 + - 必填
  645 + - 如果门店ID不存在,会报错
  646 +
  647 +2. **支出日期验证**
  648 + - 必填
  649 + - 格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss
  650 +
  651 +3. **金额验证**
  652 + - 必填
  653 + - 必须为数字,大于等于0
  654 +
  655 +4. **单价和数量验证**
  656 + - 如果同时提供了单价和数量,系统会验证:金额 = 单价 × 数量
  657 + - 如果验证失败,会报错
  658 +
  659 +## ⚠️ 注意事项
  660 +
  661 +### 合作成本注意事项
  662 +
  663 +1. **月份格式**
  664 + - 月份必须使用YYYYMM格式(如:202511表示2025年11月)
  665 + - 不要使用YYYY-MM格式
  666 +
  667 +2. **唯一性约束**
  668 + - 同一门店、同一年份、同一月份只能有一条有效记录
  669 + - 如果需要修改,请使用更新接口,不要重复创建
  670 +
  671 +3. **门店名称自动填充**
  672 + - 如果未提供门店名称,系统会根据门店ID自动查询并填充
  673 + - 建议前端也自动填充,提升用户体验
  674 +
  675 +4. **导入Excel格式**
  676 + - 第一行必须是表头
  677 + - 门店ID和门店名称至少填写一个
  678 + - 月份格式必须为YYYYMM(6位数字)
  679 +
  680 +### 店内支出注意事项
  681 +
  682 +1. **金额计算**
  683 + - 如果同时提供了单价和数量,系统会验证:金额 = 单价 × 数量
  684 + - 建议前端在用户输入单价和数量时自动计算金额
  685 +
  686 +2. **附件上传**
  687 + - 附件使用FileControlsModel格式
  688 + - 支持多个附件
  689 +
  690 +3. **关联字段**
  691 + - `relatedReimbursementId`:关联报销申请ID(可选)
  692 + - `relatedPurchaseRecordId`:关联购买记录ID(可选)
  693 + - 这些字段用于数据追溯,建议填写
  694 +
  695 +4. **导入Excel格式**
  696 + - 第一行必须是表头
  697 + - 门店ID和门店名称至少填写一个
  698 + - 支出日期格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss
  699 +
  700 +### 通用注意事项
  701 +
  702 +1. **GET请求参数**
  703 + - 所有GET请求使用`params`传参,不使用`data`
  704 + - 分页参数`currentPage`和`pageSize`必填
  705 +
  706 +2. **文件下载**
  707 + - 导出接口返回的是文件流,需要设置`responseType: 'blob'`
  708 + - 下载文件时需要创建Blob对象并触发下载
  709 +
  710 +3. **文件上传**
  711 + - 导入接口需要使用`FormData`格式
  712 + - 设置`Content-Type: 'multipart/form-data'`
  713 +
  714 +4. **错误处理**
  715 + - 所有接口返回标准格式:`{code, msg, data}`
  716 + - `code === 200`表示成功,其他表示失败
  717 + - 失败时查看`msg`字段获取错误信息
  718 +
  719 +5. **数据删除**
  720 + - 删除操作执行的是逻辑删除,不会真正删除数据
  721 + - 删除后的数据在列表中不会显示,但可以通过数据库查询
  722 +
  723 +6. **店长工资计算**
  724 + - 合作成本和店内支出数据用于店长工资计算中的毛利计算
  725 + - 确保数据的准确性和及时性,避免影响工资计算结果
  726 +
... ...
店长工资计算-前置条件检查清单.md 0 → 100644
  1 +# 店长工资计算 - 前置条件检查清单
  2 +
  3 +## 📋 检查时间
  4 +2025-01-XX
  5 +
  6 +---
  7 +
  8 +## ✅ 一、数据表检查
  9 +
  10 +### 1. 核心业务表
  11 +
  12 +| 表名 | 用途 | 状态 | 说明 |
  13 +|------|------|------|------|
  14 +| `lq_kd_kdjlb` | 开单记录表(获取开单业绩) | ✅ 已存在 | 字段:`sfyj`(实付业绩)、`Djmd`(门店ID)、`Kdrq`(开单日期) |
  15 +| `lq_hytk_hytk` | 退卡记录表(获取退款业绩) | ✅ 已存在 | 字段:`F_ActualRefundAmount`(实际退款金额)、`md`(门店ID)、`tksj`(退卡时间) |
  16 +| `lq_inventory_usage` | 库存使用记录表(获取产品物料) | ✅ 已存在 | 字段:`F_TotalAmount`(合计金额)、`F_StoreId`(门店ID)、`F_UsageTime`(使用时间) |
  17 +| `lq_cooperation_cost` | 合作成本表 | ✅ 已创建 | 字段:`F_TotalAmount`(合计金额)、`F_StoreId`(门店ID)、`F_Year`(年份)、`F_Month`(月份) |
  18 +| `lq_store_expense` | 店内支出表 | ✅ 已创建 | 字段:`F_Amount`(金额)、`F_StoreId`(门店ID)、`F_ExpenseDate`(支出日期) |
  19 +| `lq_laundry_flow` | 清洗流水表(获取洗毛巾费用) | ✅ 已存在 | 字段:`F_TotalPrice`(总费用)、`F_StoreId`(门店ID)、`F_FlowType`(流水类型,0=送出) |
  20 +| `lq_md_target` | 门店目标表 | ✅ 已存在 | 字段:`F_StoreId`(门店ID)、`F_Month`(月份)、`F_StoreLifeline`(门店生命线)、`F_HeadcountTarget`(目标人头数)、`F_ConsumeTarget`(目标消耗) |
  21 +| `lq_xh_hyhk` | 耗卡记录表(统计进店消耗人数) | ✅ 已存在 | 用于统计进店消耗人数 |
  22 +| `lq_md_xdbhsj` | 新店保护时间表 | ⚠️ 需确认 | 用于判断是否为新店 |
  23 +| `lq_mdxx` | 门店信息表 | ✅ 已存在 | 字段:`F_Id`(门店ID)、`F_StoreType`(门店类型,判断旗舰店)、`F_StoreCategory`(门店分类,A/B/C类) |
  24 +| `BASE_USER` | 系统用户表 | ✅ 已存在 | 字段:`F_Id`(员工ID)、`F_RealName`(姓名)、`F_GW`(岗位,店长)、`F_MDID`(门店ID) |
  25 +
  26 +---
  27 +
  28 +## ✅ 二、实体类检查
  29 +
  30 +### 1. 核心实体类
  31 +
  32 +| 实体类 | 对应表 | 状态 | 说明 |
  33 +|--------|--------|------|------|
  34 +| `LqKdKdjlbEntity` | `lq_kd_kdjlb` | ✅ 已存在 | 开单记录实体 |
  35 +| `LqHytkHytkEntity` | `lq_hytk_hytk` | ✅ 已存在 | 退卡记录实体 |
  36 +| `LqInventoryUsageEntity` | `lq_inventory_usage` | ✅ 已存在 | 库存使用记录实体 |
  37 +| `LqCooperationCostEntity` | `lq_cooperation_cost` | ✅ 已创建 | 合作成本实体 |
  38 +| `LqStoreExpenseEntity` | `lq_store_expense` | ✅ 已创建 | 店内支出实体 |
  39 +| `LqLaundryFlowEntity` | `lq_laundry_flow` | ✅ 已存在 | 清洗流水实体 |
  40 +| `LqMdTargetEntity` | `lq_md_target` | ✅ 已存在 | 门店目标实体 |
  41 +| `LqXhHyhkEntity` | `lq_xh_hyhk` | ✅ 已存在 | 耗卡记录实体 |
  42 +| `LqMdxxEntity` | `lq_mdxx` | ✅ 已存在 | 门店信息实体 |
  43 +
  44 +---
  45 +
  46 +## ✅ 三、服务类检查
  47 +
  48 +### 1. 数据查询服务
  49 +
  50 +| 服务类 | 功能 | 状态 | 说明 |
  51 +|--------|------|------|------|
  52 +| `LqCooperationCostService` | 合作成本管理 | ✅ 已实现 | CRUD、导入、导出 |
  53 +| `LqStoreExpenseService` | 店内支出管理 | ✅ 已实现 | CRUD、导入、导出 |
  54 +| `LqReimbursementApplicationService` | 报销申请管理 | ✅ 已实现 | 已实现导出本月已审核通过的报销表明细 |
  55 +| `LqInventoryUsageService` | 库存使用管理 | ✅ 已存在 | 可查询仓库领用金额 |
  56 +| `LqLaundryFlowService` | 清洗流水管理 | ⚠️ 需确认 | 需确认是否有查询送洗记录的服务 |
  57 +| `LqKdKdjlbService` | 开单记录管理 | ✅ 已存在 | 可查询开单业绩 |
  58 +| `LqHytkHytkService` | 退卡记录管理 | ⚠️ 需确认 | 需确认是否有查询退卡记录的服务 |
  59 +| `LqMdTargetService` | 门店目标管理 | ⚠️ 需确认 | 需确认是否有查询门店目标的服务 |
  60 +| `LqMdxxService` | 门店信息管理 | ✅ 已存在 | 可查询门店信息、分类、类型 |
  61 +
  62 +---
  63 +
  64 +## ⚠️ 四、待实现功能检查
  65 +
  66 +### 1. 毛利计算接口
  67 +
  68 +| 功能 | 状态 | 优先级 |
  69 +|------|------|--------|
  70 +| 销售业绩查询(开单业绩 - 退款业绩) | ❌ 未实现 | 高 |
  71 +| 产品物料查询(仓库领用金额,特殊规则:11月算10月) | ❌ 未实现 | 高 |
  72 +| 合作项目成本查询 | ✅ 已实现 | - |
  73 +| 店内支出查询 | ✅ 已实现 | - |
  74 +| 洗毛巾查询 | ❌ 未实现 | 高 |
  75 +| 毛利汇总计算接口 | ❌ 未实现 | 高 |
  76 +
  77 +### 2. 工资计算接口
  78 +
  79 +| 功能 | 状态 | 优先级 |
  80 +|------|------|--------|
  81 +| 底薪计算(老店/新店,旗舰店特殊规则) | ❌ 未实现 | 中 |
  82 +| 提成计算(基于毛利,老店/新店不同规则) | ❌ 未实现 | 中 |
  83 +| 考核指标判断(业绩、人头、消耗) | ❌ 未实现 | 中 |
  84 +| 新店判断逻辑 | ❌ 未实现 | 中 |
  85 +| 旗舰店判断逻辑 | ⚠️ 需确认 | 中 |
  86 +| 完整工资计算接口 | ❌ 未实现 | 中 |
  87 +
  88 +---
  89 +
  90 +## 📊 五、数据查询能力检查
  91 +
  92 +### 1. 销售业绩查询
  93 +
  94 +**需求**:
  95 +- 开单业绩:从 `lq_kd_kdjlb` 表查询 `sfyj` 字段,按门店ID和月份统计
  96 +- 退款业绩:从 `lq_hytk_hytk` 表查询 `F_ActualRefundAmount` 字段,按门店ID和月份统计
  97 +- 销售业绩 = 开单业绩 - 退款业绩
  98 +
  99 +**状态**:❌ 需要实现查询接口
  100 +
  101 +---
  102 +
  103 +### 2. 产品物料查询
  104 +
  105 +**需求**:
  106 +- 从 `lq_inventory_usage` 表查询 `F_TotalAmount` 字段
  107 +- 按门店ID和月份统计
  108 +- **特殊规则**:核算11月工资时,算的是10月份的仓库领用
  109 +
  110 +**状态**:❌ 需要实现查询接口(注意11月特殊规则)
  111 +
  112 +---
  113 +
  114 +### 3. 合作项目成本查询
  115 +
  116 +**需求**:
  117 +- 从 `lq_cooperation_cost` 表查询 `F_TotalAmount` 字段
  118 +- 按门店ID、年份、月份查询
  119 +
  120 +**状态**:✅ 已实现(`LqCooperationCostService.GetList`)
  121 +
  122 +---
  123 +
  124 +### 4. 店内支出查询
  125 +
  126 +**需求**:
  127 +- 从 `lq_store_expense` 表查询 `F_Amount` 字段
  128 +- 按门店ID和月份统计
  129 +
  130 +**状态**:✅ 已实现(`LqStoreExpenseService.GetList`)
  131 +
  132 +---
  133 +
  134 +### 5. 洗毛巾查询
  135 +
  136 +**需求**:
  137 +- 从 `lq_laundry_flow` 表查询 `F_TotalPrice` 字段
  138 +- 条件:`F_FlowType = 0`(送出)
  139 +- 按门店ID和月份统计
  140 +
  141 +**状态**:❌ 需要实现查询接口
  142 +
  143 +---
  144 +
  145 +### 6. 门店目标查询
  146 +
  147 +**需求**:
  148 +- 从 `lq_md_target` 表查询门店生命线、目标人头数、目标消耗
  149 +- 按门店ID和月份查询
  150 +
  151 +**状态**:⚠️ 需确认是否有查询接口
  152 +
  153 +---
  154 +
  155 +### 7. 新店判断
  156 +
  157 +**需求**:
  158 +- 从 `lq_md_xdbhsj` 表查询新店保护信息
  159 +- 判断统计月份的第一天是否在保护期内
  160 +
  161 +**状态**:❌ 需要实现新店判断逻辑
  162 +
  163 +---
  164 +
  165 +### 8. 进店消耗人数统计
  166 +
  167 +**需求**:
  168 +- 从 `lq_xh_hyhk` 表统计有消费金额的客户数
  169 +- 按门店按月去重
  170 +
  171 +**状态**:❌ 需要实现统计接口
  172 +
  173 +---
  174 +
  175 +### 9. 门店消耗统计
  176 +
  177 +**需求**:
  178 +- 从 `lq_xh_jksyj` 表统计门店当月总消耗金额
  179 +- 按门店和月份统计
  180 +
  181 +**状态**:❌ 需要实现统计接口
  182 +
  183 +---
  184 +
  185 +## ✅ 六、已完成功能
  186 +
  187 +### 1. 数据表创建
  188 +- ✅ `lq_cooperation_cost` 表已创建
  189 +- ✅ `lq_store_expense` 表已创建
  190 +
  191 +### 2. 实体类创建
  192 +- ✅ `LqCooperationCostEntity` 已创建
  193 +- ✅ `LqStoreExpenseEntity` 已创建
  194 +
  195 +### 3. 服务类实现
  196 +- ✅ `LqCooperationCostService` 已实现(CRUD、导入、导出)
  197 +- ✅ `LqStoreExpenseService` 已实现(CRUD、导入、导出)
  198 +- ✅ `LqReimbursementApplicationService.ExportApprovedDetails` 已实现(导出报销表明细)
  199 +
  200 +---
  201 +
  202 +## ❌ 七、待实现功能
  203 +
  204 +### 1. 毛利计算接口(高优先级)
  205 +
  206 +需要实现以下接口:
  207 +
  208 +1. **销售业绩查询接口**
  209 + - 查询开单业绩(`lq_kd_kdjlb`)
  210 + - 查询退款业绩(`lq_hytk_hytk`)
  211 + - 计算销售业绩 = 开单业绩 - 退款业绩
  212 +
  213 +2. **产品物料查询接口**
  214 + - 查询仓库领用金额(`lq_inventory_usage`)
  215 + - 特殊规则:11月工资算10月数据
  216 +
  217 +3. **洗毛巾查询接口**
  218 + - 查询送洗记录总费用(`lq_laundry_flow`,`F_FlowType = 0`)
  219 +
  220 +4. **毛利汇总计算接口**
  221 + - 汇总所有组成部分
  222 + - 计算:毛利 = 销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾
  223 +
  224 +### 2. 工资计算接口(中优先级)
  225 +
  226 +需要实现以下接口:
  227 +
  228 +1. **门店目标查询接口**
  229 + - 查询门店生命线、目标人头数、目标消耗(`lq_md_target`)
  230 +
  231 +2. **新店判断接口**
  232 + - 判断门店是否为新店(`lq_md_xdbhsj`)
  233 +
  234 +3. **进店消耗人数统计接口**
  235 + - 统计有消费金额的客户数(`lq_xh_hyhk`)
  236 +
  237 +4. **门店消耗统计接口**
  238 + - 统计门店当月总消耗金额(`lq_xh_jksyj`)
  239 +
  240 +5. **底薪计算接口**
  241 + - 老店:3个考核指标(业绩、人头、消耗)
  242 + - 新店:2个考核指标(业绩、人头)
  243 + - 旗舰店:额外扣除800元
  244 +
  245 +6. **提成计算接口**
  246 + - 老店:根据门店分类(A/B/C)和业绩是否达标
  247 + - 新店:不区分分类,统一比例
  248 +
  249 +7. **完整工资计算接口**
  250 + - 整合底薪和提成
  251 + - 返回完整工资计算结果
  252 +
  253 +---
  254 +
  255 +## 📝 八、总结
  256 +
  257 +### ✅ 已完成
  258 +1. 合作成本表和店内支出表已创建并实现CRUD、导入、导出功能
  259 +2. 报销表导出功能已实现
  260 +3. 所有核心数据表都已存在
  261 +4. 所有核心实体类都已存在
  262 +
  263 +### ❌ 待实现
  264 +1. **毛利计算接口**(高优先级)
  265 + - 销售业绩查询
  266 + - 产品物料查询(注意11月特殊规则)
  267 + - 洗毛巾查询
  268 + - 毛利汇总计算
  269 +
  270 +2. **工资计算接口**(中优先级)
  271 + - 门店目标查询
  272 + - 新店判断
  273 + - 进店消耗人数统计
  274 + - 门店消耗统计
  275 + - 底薪计算
  276 + - 提成计算
  277 + - 完整工资计算
  278 +
  279 +### ⚠️ 需确认
  280 +1. 新店保护时间表(`lq_md_xdbhsj`)是否存在
  281 +2. 是否有查询门店目标的服务
  282 +3. 是否有查询送洗记录的服务
  283 +4. 是否有查询退卡记录的服务
  284 +
  285 +---
  286 +
  287 +## 🚀 下一步行动
  288 +
  289 +### 第一步:实现毛利计算接口
  290 +1. 实现销售业绩查询接口
  291 +2. 实现产品物料查询接口(注意11月特殊规则)
  292 +3. 实现洗毛巾查询接口
  293 +4. 实现毛利汇总计算接口
  294 +
  295 +### 第二步:实现工资计算接口
  296 +1. 实现门店目标查询接口
  297 +2. 实现新店判断逻辑
  298 +3. 实现进店消耗人数统计接口
  299 +4. 实现门店消耗统计接口
  300 +5. 实现底薪计算接口
  301 +6. 实现提成计算接口
  302 +7. 实现完整工资计算接口
  303 +
  304 +### 第三步:测试验证
  305 +1. 测试毛利计算准确性
  306 +2. 测试工资计算准确性
  307 +3. 测试各种边界情况(新店、老店、旗舰店、业绩达标/未达标等)
  308 +
... ...
店长工资计算规则-完整逻辑梳理.md 0 → 100644
  1 +# 店长工资计算规则 - 完整逻辑梳理
  2 +
  3 +## 📋 概述
  4 +
  5 +店长工资由以下几个部分组成:
  6 +1. **底薪**:固定4000元,根据考核指标扣款
  7 +2. **提成**:根据门店分类和业绩是否达标,使用不同的提成比例计算(基于毛利)
  8 +
  9 +---
  10 +
  11 +## 💰 工资组成规则
  12 +
  13 +### 1. 底薪规则
  14 +
  15 +**固定底薪**:4000元
  16 +
  17 +#### 老店店长底薪考核
  18 +
  19 +**考核指标**(3个):
  20 +1. **业绩考核**:门店业绩是否达到门店生命线
  21 +2. **人头考核**:进店消耗人数是否达到目标人头数
  22 +3. **消耗考核**:门店消耗是否达到目标消耗
  23 +
  24 +**扣款规则**:
  25 +- 每个指标未达到:扣除500元
  26 +- 如果3个指标都未达到:扣除1500元(500 × 3)
  27 +
  28 +**计算公式**:
  29 +```
  30 +底薪 = 4000 - (未达标指标数 × 500)
  31 +```
  32 +
  33 +#### 新店店长底薪考核
  34 +
  35 +**重要说明**:新店店长涉及全部阶段,都有负奖励机制
  36 +
  37 +**考核指标**(2个):
  38 +1. **业绩考核**:门店业绩是否达到门店生命线
  39 +2. **人头考核**:进店消耗人数是否达到目标人头数
  40 +
  41 +**扣款规则**:
  42 +- 每个指标未达到:扣除800元
  43 +- 如果2个指标都未达到:扣除1600元(800 × 2)
  44 +
  45 +**计算公式**:
  46 +```
  47 +底薪 = 4000 - (未达标指标数 × 800)
  48 +```
  49 +
  50 +**注意**:新店不考核消耗指标
  51 +
  52 +**旗舰店特殊规则**:
  53 +- 旗舰店类型门店需要扣除负奖励800元
  54 +- 判断方式:`lq_mdxx.F_StoreType = 2`(StoreTypeEnum.旗舰店)
  55 +
  56 +---
  57 +
  58 +### 2. 提成规则
  59 +
  60 +**提成计算方式**:根据门店分类和业绩是否达标,使用不同的提成比例
  61 +
  62 +**重要说明**:提成计算基于**毛利**,不是门店业绩
  63 +
  64 +#### 老店店长提成规则
  65 +
  66 +根据门店分类(A、B、C类)和业绩是否达标,使用不同的提成比例:
  67 +
  68 +| 门店分类 | 业绩未达标 | 业绩达标 |
  69 +|---------|----------|---------|
  70 +| A类门店 | 3% | 3.5% |
  71 +| B类门店 | 3.5% | 4% |
  72 +| C类门店 | 4% | 4.5% |
  73 +
  74 +**业绩达标判断**:
  75 +- 业绩达标:门店业绩 ≥ 门店生命线
  76 +- 业绩未达标:门店业绩 < 门店生命线
  77 +
  78 +**计算公式**:
  79 +```
  80 +如果 业绩 ≥ 生命线(业绩达标):
  81 + 提成 = 毛利 × 对应提成比例(业绩达标)
  82 +
  83 +如果 业绩 < 生命线(业绩未达标):
  84 + 提成 = 毛利 × 对应提成比例(业绩未达标)
  85 +```
  86 +
  87 +#### 新店店长提成规则
  88 +
  89 +**提成比例**(不区分门店分类):
  90 +- 业绩未达标:3%
  91 +- 业绩达标:3.5%
  92 +
  93 +**计算公式**:
  94 +```
  95 +如果 业绩 ≥ 生命线(业绩达标):
  96 + 提成 = 毛利 × 3.5%
  97 +
  98 +如果 业绩 < 生命线(业绩未达标):
  99 + 提成 = 毛利 × 3%
  100 +```
  101 +
  102 +---
  103 +
  104 +## 📊 毛利计算公式
  105 +
  106 +**核心公式**:
  107 +```
  108 +毛利 = 销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾
  109 +```
  110 +
  111 +### 1. 销售业绩
  112 +
  113 +**计算公式**:
  114 +```
  115 +销售业绩 = 开单业绩 - 退款业绩
  116 +```
  117 +
  118 +**数据来源**:
  119 +
  120 +1. **开单业绩**:
  121 + - 表:`lq_kd_kdjlb`(开单记录表)
  122 + - 字段:`sfyj`(实付业绩)
  123 + - 条件:
  124 + - `F_IsEffective = 1`(有效记录)
  125 + - `Djmd = @StoreId`(门店ID)
  126 + - `DATE_FORMAT(Kdrq, '%Y%m') = @Month`(月份,YYYYMM格式)
  127 +
  128 +2. **退款业绩**:
  129 + - 表:`lq_hytk_hytk`(退卡记录表)
  130 + - 字段:`F_ActualRefundAmount`(实际退款金额)
  131 + - 条件:
  132 + - `F_IsEffective = 1`(有效记录)
  133 + - `md = @StoreId`(门店ID)
  134 + - `DATE_FORMAT(tksj, '%Y%m') = @Month`(月份,YYYYMM格式)
  135 +
  136 +**SQL示例**:
  137 +```sql
  138 +-- 开单业绩
  139 +SELECT COALESCE(SUM(sfyj), 0) as BillingPerformance
  140 +FROM lq_kd_kdjlb
  141 +WHERE Djmd = @StoreId
  142 + AND DATE_FORMAT(Kdrq, '%Y%m') = @Month
  143 + AND F_IsEffective = 1
  144 +
  145 +-- 退款业绩
  146 +SELECT COALESCE(SUM(F_ActualRefundAmount), 0) as RefundPerformance
  147 +FROM lq_hytk_hytk
  148 +WHERE md = @StoreId
  149 + AND DATE_FORMAT(tksj, '%Y%m') = @Month
  150 + AND F_IsEffective = 1
  151 +
  152 +-- 销售业绩
  153 +销售业绩 = 开单业绩 - 退款业绩
  154 +```
  155 +
  156 +---
  157 +
  158 +### 2. 产品物料
  159 +
  160 +**计算公式**:
  161 +```
  162 +产品物料 = 仓库领用金额合计
  163 +```
  164 +
  165 +**数据来源**:
  166 +- 表:`lq_inventory_usage`(库存使用记录表)
  167 +- 字段:`F_TotalAmount`(合计金额)
  168 +- 条件:
  169 + - `F_IsEffective = 1`(有效记录)
  170 + - `F_StoreId = @StoreId`(门店ID)
  171 + - **特殊规则**:核算11月工资时,算的是10月份的仓库领用
  172 + - 如果计算月份是11月,则查询10月的数据
  173 + - 其他月份正常查询当月数据
  174 +
  175 +**SQL示例**:
  176 +```sql
  177 +-- 产品物料(特殊规则:11月工资算10月数据)
  178 +SET @QueryMonth = @Month;
  179 +IF @Month = '202411' THEN
  180 + SET @QueryMonth = '202410';
  181 +END IF;
  182 +
  183 +SELECT COALESCE(SUM(F_TotalAmount), 0) as MaterialCost
  184 +FROM lq_inventory_usage
  185 +WHERE F_StoreId = @StoreId
  186 + AND DATE_FORMAT(F_UsageTime, '%Y%m') = @QueryMonth
  187 + AND F_IsEffective = 1
  188 +```
  189 +
  190 +---
  191 +
  192 +### 3. 合作项目成本
  193 +
  194 +**计算公式**:
  195 +```
  196 +合作项目成本 = 合作成本表合计金额
  197 +```
  198 +
  199 +**数据来源**:
  200 +- 表:`lq_cooperation_cost`(合作成本表)**需要新建**
  201 +- 字段:`F_TotalAmount`(合计金额)
  202 +- 条件:
  203 + - `F_StoreId = @StoreId`(门店ID)
  204 + - `F_Year = @Year`(年份)
  205 + - `F_Month = @Month`(月份,YYYYMM格式)
  206 +
  207 +**表结构设计**:
  208 +```sql
  209 +CREATE TABLE IF NOT EXISTS `lq_cooperation_cost` (
  210 + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID',
  211 + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)',
  212 + `F_StoreName` VARCHAR(200) NULL COMMENT '门店名称',
  213 + `F_Year` INT NOT NULL COMMENT '年份',
  214 + `F_Month` VARCHAR(6) NOT NULL COMMENT '月份(YYYYMM格式)',
  215 + `F_TotalAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合计金额',
  216 + `F_Remarks` VARCHAR(1000) NULL COMMENT '备注说明',
  217 + `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)',
  218 + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID',
  219 + `F_CreateTime` DATETIME NULL COMMENT '创建时间',
  220 + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID',
  221 + `F_UpdateTime` DATETIME NULL COMMENT '更新时间',
  222 + PRIMARY KEY (`F_Id`),
  223 + KEY `idx_store_month` (`F_StoreId`, `F_Year`, `F_Month`),
  224 + KEY `idx_month` (`F_Year`, `F_Month`)
  225 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合作成本表';
  226 +```
  227 +
  228 +**功能要求**:
  229 +- 支持按门店、年份、月份查询
  230 +- 支持导入功能(Excel导入)
  231 +- 支持增删改查操作
  232 +
  233 +---
  234 +
  235 +### 4. 店内支出
  236 +
  237 +**计算公式**:
  238 +```
  239 +店内支出 = 店内支出表合计金额
  240 +```
  241 +
  242 +**数据来源**:
  243 +- 表:`lq_store_expense`(店内支出表)**需要新建**
  244 +- 字段:`F_Amount`(金额)
  245 +- 条件:
  246 + - `F_StoreId = @StoreId`(门店ID)
  247 + - `DATE_FORMAT(F_ExpenseDate, '%Y%m') = @Month`(月份,YYYYMM格式)
  248 + - `F_IsEffective = 1`(有效记录)
  249 +
  250 +**业务流程**:
  251 +1. 从报销表导出本月审核通过的报销表明细
  252 + - 表:`lq_reimbursement_application`(报销申请表)
  253 + - 关联表:`lq_purchase_records`(购买记录表)
  254 + - 条件:
  255 + - `F_ApprovalStatus = '已通过'`(审批状态)
  256 + - `F_ApplicationStoreId = @StoreId`(门店ID)
  257 + - `DATE_FORMAT(F_ApplicationTime, '%Y%m') = @Month`(月份)
  258 +2. 线下重新整理明细(可能调整金额、分类等)
  259 +3. 导入到店内支出表 `lq_store_expense`
  260 +
  261 +**表结构设计**(参考购买记录表结构):
  262 +```sql
  263 +CREATE TABLE IF NOT EXISTS `lq_store_expense` (
  264 + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID',
  265 + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)',
  266 + `F_StoreName` VARCHAR(200) NULL COMMENT '门店名称',
  267 + `F_ExpenseCategoryId` VARCHAR(50) NULL COMMENT '支出分类ID(关联lq_reimbursement_category.F_Id)',
  268 + `F_ExpenseCategoryName` VARCHAR(200) NULL COMMENT '支出分类名称',
  269 + `F_ExpenseDate` DATETIME NOT NULL COMMENT '支出日期',
  270 + `F_UnitPrice` DECIMAL(18,2) DEFAULT 0.00 COMMENT '单价',
  271 + `F_Quantity` INT DEFAULT 0 COMMENT '数量',
  272 + `F_Amount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '金额',
  273 + `F_Memo` VARCHAR(1000) NULL COMMENT '备注说明',
  274 + `F_Attachment` TEXT NULL COMMENT '附件(JSON格式)',
  275 + `F_RelatedReimbursementId` VARCHAR(50) NULL COMMENT '关联报销申请ID(可选)',
  276 + `F_RelatedPurchaseRecordId` VARCHAR(50) NULL COMMENT '关联购买记录ID(可选)',
  277 + `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)',
  278 + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID',
  279 + `F_CreateTime` DATETIME NULL COMMENT '创建时间',
  280 + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID',
  281 + `F_UpdateTime` DATETIME NULL COMMENT '更新时间',
  282 + PRIMARY KEY (`F_Id`),
  283 + KEY `idx_store_date` (`F_StoreId`, `F_ExpenseDate`),
  284 + KEY `idx_expense_date` (`F_ExpenseDate`),
  285 + KEY `idx_category` (`F_ExpenseCategoryId`)
  286 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店内支出表';
  287 +```
  288 +
  289 +**功能要求**:
  290 +- 支持按门店、月份查询
  291 +- 支持导出报销表明细(用于线下整理)
  292 +- 支持导入功能(Excel导入)
  293 +- 支持增删改查操作
  294 +
  295 +---
  296 +
  297 +### 5. 洗毛巾
  298 +
  299 +**计算公式**:
  300 +```
  301 +洗毛巾 = 送洗记录总费用
  302 +```
  303 +
  304 +**数据来源**:
  305 +- 表:`lq_laundry_flow`(清洗流水表)
  306 +- 字段:`F_TotalPrice`(总费用)
  307 +- 条件:
  308 + - `F_FlowType = 0`(流水类型:送出)
  309 + - `F_StoreId = @StoreId`(门店ID)
  310 + - `DATE_FORMAT(F_CreateTime, '%Y%m') = @Month`(月份,YYYYMM格式)
  311 + - `F_IsEffective = 1`(有效记录)
  312 +
  313 +**重要说明**:
  314 +- 只要送出就产生金额(不管是否送回)
  315 +- 只统计 `F_FlowType = 0`(送出)的记录
  316 +
  317 +**SQL示例**:
  318 +```sql
  319 +SELECT COALESCE(SUM(F_TotalPrice), 0) as LaundryCost
  320 +FROM lq_laundry_flow
  321 +WHERE F_StoreId = @StoreId
  322 + AND F_FlowType = 0
  323 + AND DATE_FORMAT(F_CreateTime, '%Y%m') = @Month
  324 + AND F_IsEffective = 1
  325 +```
  326 +
  327 +---
  328 +
  329 +## 🔍 其他重要规则
  330 +
  331 +### 1. 旗舰店判断
  332 +
  333 +**判断方式**:
  334 +- 表:`lq_mdxx`(门店信息表)
  335 +- 字段:`F_StoreType`(门店类型)
  336 +- 枚举值:
  337 + - `1` = 门店200平(StoreTypeEnum.门店200平)
  338 + - `2` = 旗舰店(StoreTypeEnum.旗舰店)
  339 +
  340 +**代码实现**:
  341 +```csharp
  342 +// 判断是否为旗舰店
  343 +bool isFlagshipStore = store.StoreType == (int)StoreTypeEnum.旗舰店;
  344 +```
  345 +
  346 +**特殊规则**:
  347 +- 旗舰店类型门店需要扣除负奖励800元
  348 +
  349 +---
  350 +
  351 +### 2. 新店店长
  352 +
  353 +**重要说明**:
  354 +- 新店店长所有阶段都有负奖励
  355 +- 新店判断逻辑:需要根据门店开业时间或新店保护期判断(参考其他工资计算服务)
  356 +
  357 +---
  358 +
  359 +### 3. 店长岗位名称
  360 +
  361 +**系统标识**:
  362 +- 岗位名称:`"店长"`
  363 +- 查询条件:`BASE_USER.F_ZW = "店长"` 或 `BASE_USER.F_GW = "店长"`
  364 +
  365 +---
  366 +
  367 +## 📋 数据来源总结
  368 +
  369 +### 核心数据表
  370 +
  371 +1. **门店信息表** (`lq_mdxx`)
  372 + - `F_Id`:门店ID
  373 + - `F_StoreType`:门店类型(判断旗舰店)
  374 + - `F_StoreCategory`:门店分类(A、B、C类)
  375 +
  376 +2. **门店目标表** (`lq_md_target`)
  377 + - `F_StoreId`:门店ID
  378 + - `F_Month`:月份(YYYYMM格式)
  379 + - `F_StoreLifeline`:门店生命线
  380 + - `F_HeadcountTarget`:目标人头数
  381 + - `F_ConsumeTarget`:目标消耗
  382 +
  383 +3. **开单记录表** (`lq_kd_kdjlb`)
  384 + - `sfyj`:实付业绩(开单业绩)
  385 + - `Djmd`:门店ID
  386 + - `Kdrq`:开单日期
  387 +
  388 +4. **退卡记录表** (`lq_hytk_hytk`)
  389 + - `F_ActualRefundAmount`:实际退款金额(退款业绩)
  390 + - `md`:门店ID
  391 + - `tksj`:退卡时间
  392 +
  393 +5. **库存使用记录表** (`lq_inventory_usage`)
  394 + - `F_TotalAmount`:合计金额(产品物料)
  395 + - `F_StoreId`:门店ID
  396 + - `F_UsageTime`:使用时间
  397 +
  398 +6. **合作成本表** (`lq_cooperation_cost`) **需要新建**
  399 + - `F_TotalAmount`:合计金额
  400 + - `F_StoreId`:门店ID
  401 + - `F_Year`:年份
  402 + - `F_Month`:月份
  403 +
  404 +7. **店内支出表** (`lq_store_expense`) **需要新建**
  405 + - `F_Amount`:金额
  406 + - `F_StoreId`:门店ID
  407 + - `F_ExpenseDate`:支出日期
  408 +
  409 +8. **清洗流水表** (`lq_laundry_flow`)
  410 + - `F_TotalPrice`:总费用(洗毛巾)
  411 + - `F_StoreId`:门店ID
  412 + - `F_FlowType`:流水类型(0=送出)
  413 +
  414 +9. **耗卡记录表** (`lq_xh_hyhk`)
  415 + - 用于统计进店消耗人数
  416 +
  417 +10. **系统用户表** (`BASE_USER`)
  418 + - `F_REALNAME`:姓名
  419 + - `F_ZW`:职位(店长)
  420 + - `F_MDID`:门店ID
  421 +
  422 +---
  423 +
  424 +## 🚀 需要先实现的功能
  425 +
  426 +### 第一阶段:基础数据表
  427 +
  428 +#### 1. 合作成本表 (`lq_cooperation_cost`)
  429 +
  430 +**功能清单**:
  431 +- [ ] 创建表结构(SQL)
  432 +- [ ] 创建实体类 (`LqCooperationCostEntity`)
  433 +- [ ] 创建DTO类(输入/输出)
  434 +- [ ] 创建Service类 (`LqCooperationCostService`)
  435 +- [ ] 实现CRUD接口
  436 +- [ ] 实现导入功能(Excel导入)
  437 +- [ ] 实现查询接口(按门店、年份、月份)
  438 +
  439 +**优先级**:高(毛利计算必需)
  440 +
  441 +---
  442 +
  443 +#### 2. 店内支出表 (`lq_store_expense`)
  444 +
  445 +**功能清单**:
  446 +- [ ] 创建表结构(SQL)
  447 +- [ ] 创建实体类 (`LqStoreExpenseEntity`)
  448 +- [ ] 创建DTO类(输入/输出)
  449 +- [ ] 创建Service类 (`LqStoreExpenseService`)
  450 +- [ ] 实现CRUD接口
  451 +- [ ] 实现导出功能(导出报销表明细)
  452 +- [ ] 实现导入功能(Excel导入)
  453 +- [ ] 实现查询接口(按门店、月份)
  454 +
  455 +**优先级**:高(毛利计算必需)
  456 +
  457 +---
  458 +
  459 +### 第二阶段:数据查询接口
  460 +
  461 +#### 3. 毛利计算接口
  462 +
  463 +**功能清单**:
  464 +- [ ] 实现销售业绩查询(开单业绩 - 退款业绩)
  465 +- [ ] 实现产品物料查询(仓库领用金额,特殊规则:11月算10月)
  466 +- [ ] 实现合作项目成本查询
  467 +- [ ] 实现店内支出查询
  468 +- [ ] 实现洗毛巾查询
  469 +- [ ] 实现毛利汇总计算接口
  470 +
  471 +**优先级**:高(工资计算必需)
  472 +
  473 +---
  474 +
  475 +### 第三阶段:工资计算
  476 +
  477 +#### 4. 店长工资计算服务
  478 +
  479 +**功能清单**:
  480 +- [ ] 实现底薪计算(老店/新店,旗舰店特殊规则)
  481 +- [ ] 实现提成计算(基于毛利,老店/新店不同规则)
  482 +- [ ] 实现考核指标判断(业绩、人头、消耗)
  483 +- [ ] 实现新店判断逻辑
  484 +- [ ] 实现旗舰店判断逻辑
  485 +- [ ] 实现完整工资计算接口
  486 +
  487 +**优先级**:中(依赖第一阶段和第二阶段)
  488 +
  489 +---
  490 +
  491 +## 📝 实现顺序建议
  492 +
  493 +### 步骤1:创建合作成本表
  494 +1. 编写SQL创建表结构
  495 +2. 创建实体类、DTO、Service
  496 +3. 实现CRUD接口
  497 +4. 实现导入功能
  498 +
  499 +### 步骤2:创建店内支出表
  500 +1. 编写SQL创建表结构
  501 +2. 创建实体类、DTO、Service
  502 +3. 实现CRUD接口
  503 +4. 实现导出报销表明细功能
  504 +5. 实现导入功能
  505 +
  506 +### 步骤3:实现毛利计算接口
  507 +1. 实现销售业绩查询
  508 +2. 实现产品物料查询(注意11月特殊规则)
  509 +3. 实现合作项目成本查询
  510 +4. 实现店内支出查询
  511 +5. 实现洗毛巾查询
  512 +6. 实现毛利汇总计算
  513 +
  514 +### 步骤4:实现店长工资计算
  515 +1. 实现底薪计算逻辑
  516 +2. 实现提成计算逻辑
  517 +3. 实现考核指标判断
  518 +4. 实现完整工资计算接口
  519 +5. 编写测试用例验证
  520 +
  521 +---
  522 +
  523 +## ⚠️ 注意事项
  524 +
  525 +1. **产品物料特殊规则**:
  526 + - 核算11月工资时,算的是10月份的仓库领用
  527 + - 需要在查询时特殊处理
  528 +
  529 +2. **店内支出流程**:
  530 + - 必须先导出报销表明细
  531 + - 线下整理后再导入
  532 + - 不是直接从报销表计算
  533 +
  534 +3. **洗毛巾计算**:
  535 + - 只要送出就产生金额
  536 + - 只统计 `F_FlowType = 0`(送出)的记录
  537 +
  538 +4. **旗舰店判断**:
  539 + - 使用 `F_StoreType = 2` 判断
  540 + - 需要扣除负奖励800元
  541 +
  542 +5. **新店店长**:
  543 + - 所有阶段都有负奖励
  544 + - 需要确认新店判断逻辑
  545 +
  546 +6. **数据一致性**:
  547 + - 所有金额计算保留2位小数
  548 + - 确保月份格式统一(YYYYMM)
  549 +
  550 +---
  551 +
  552 +## 📌 待确认问题
  553 +
  554 +1. **新店判断逻辑**:
  555 + - 如何判断门店是否为新店?
  556 + - 是否有新店保护期字段?
  557 + - 参考其他工资计算服务的实现
  558 +
  559 +2. **合作成本导入格式**:
  560 + - Excel导入的字段格式要求
  561 + - 是否需要校验门店ID、年份、月份
  562 +
  563 +3. **店内支出导入格式**:
  564 + - Excel导入的字段格式要求
  565 + - 是否需要关联报销申请ID
  566 +
  567 +4. **毛利计算精度**:
  568 + - 各组成部分的精度要求
  569 + - 最终毛利的精度要求
  570 +
  571 +---
  572 +
  573 +## 🔗 相关代码位置
  574 +
  575 +- **门店信息查询**:`LqMdxxService.cs`
  576 +- **门店分类枚举**:`StoreCategoryEnum.cs`
  577 +- **门店类型枚举**:`StoreTypeEnum.cs`
  578 +- **门店实体类**:`LqMdxxEntity.cs`
  579 +- **库存使用服务**:`LqInventoryUsageService.cs`
  580 +- **清洗流水服务**:`LqLaundryFlowService.cs`
  581 +- **报销申请服务**:`LqReimbursementApplicationService.cs`
  582 +- **购买记录服务**:`LqPurchaseRecordsService.cs`
  583 +- **其他工资计算服务**:`LqAssistantSalaryService.cs`、`LqDirectorSalaryService.cs`(参考新店判断逻辑)
  584 +
... ...
测试脚本-创建店长工资测试数据.sh 0 → 100755
  1 +#!/bin/bash
  2 +
  3 +# 店长工资计算测试数据创建脚本
  4 +# 门店ID:1649328471923847172(绿纤华润店)
  5 +# 员工ID:13881949169(易春梅)
  6 +
  7 +TOKEN="Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUzNDA4ODYsIm5iZiI6MTc2NTM0MDg4NiwiZXhwIjoxNzY1Mzk0ODg2LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.s6IjmTLVCF_Dmho3rXWiL1FE-wimA4ZTUYt_W8h4URk"
  8 +
  9 +STORE_ID="1649328471923847172"
  10 +STORE_NAME="绿纤华润店"
  11 +APPROVER_ID="13032810387"
  12 +LAUNDRY_SUPPLIER_ID="764256388720362757"
  13 +EXPENSE_CATEGORY_ID="11156146041171400"
  14 +
  15 +echo "=== 1. 创建合作成本数据(2025年11月)==="
  16 +curl -s -X POST "http://localhost:2011/api/Extend/LqCooperationCost" \
  17 + -H "Authorization: ${TOKEN}" \
  18 + -H "Content-Type: application/json" \
  19 + -d "{
  20 + \"storeId\": \"${STORE_ID}\",
  21 + \"storeName\": \"${STORE_NAME}\",
  22 + \"year\": 2025,
  23 + \"month\": \"202511\",
  24 + \"totalAmount\": 3000.00,
  25 + \"remarks\": \"测试数据-合作成本\"
  26 + }" | jq .
  27 +
  28 +echo ""
  29 +echo "=== 2. 创建店内支出数据(2025年11月)==="
  30 +curl -s -X POST "http://localhost:2011/api/Extend/LqStoreExpense" \
  31 + -H "Authorization: ${TOKEN}" \
  32 + -H "Content-Type: application/json" \
  33 + -d "{
  34 + \"storeId\": \"${STORE_ID}\",
  35 + \"storeName\": \"${STORE_NAME}\",
  36 + \"expenseCategoryId\": \"${EXPENSE_CATEGORY_ID}\",
  37 + \"expenseCategoryName\": \"日常支出\",
  38 + \"expenseDate\": \"2025-11-15T00:00:00\",
  39 + \"unitPrice\": 2000.00,
  40 + \"quantity\": 1,
  41 + \"amount\": 2000.00,
  42 + \"memo\": \"测试数据-店内支出\"
  43 + }" | jq .
  44 +
  45 +echo ""
  46 +echo "=== 3. 创建洗毛巾费用数据(2025年11月,送出)==="
  47 +curl -s -X POST "http://localhost:2011/api/Extend/LqLaundryFlow/Send" \
  48 + -H "Authorization: ${TOKEN}" \
  49 + -H "Content-Type: application/json" \
  50 + -d "{
  51 + \"storeId\": \"${STORE_ID}\",
  52 + \"productType\": \"毛巾\",
  53 + \"laundrySupplierId\": \"${LAUNDRY_SUPPLIER_ID}\",
  54 + \"quantity\": 100,
  55 + \"remark\": \"测试数据-洗毛巾\"
  56 + }" | jq .
  57 +
  58 +echo ""
  59 +echo "=== 4. 创建产品物料数据(2025年10月,用于11月工资计算)==="
  60 +# 先查询一个产品ID
  61 +PRODUCT_ID=$(curl -s -X GET "http://localhost:2011/api/Extend/LqProduct?currentPage=1&pageSize=1" \
  62 + -H "Authorization: ${TOKEN}" | jq -r '.data.list[0].id // empty')
  63 +
  64 +if [ -z "$PRODUCT_ID" ]; then
  65 + echo "警告:未找到产品,跳过产品物料数据创建"
  66 +else
  67 + echo "使用产品ID: ${PRODUCT_ID}"
  68 + curl -s -X POST "http://localhost:2011/api/Extend/LqInventoryUsage/BatchCreate" \
  69 + -H "Authorization: ${TOKEN}" \
  70 + -H "Content-Type: application/json" \
  71 + -d "{
  72 + \"approverId\": \"${APPROVER_ID}\",
  73 + \"applicationStoreId\": \"${STORE_ID}\",
  74 + \"remarks\": \"测试数据-产品物料(10月)\",
  75 + \"usageItems\": [
  76 + {
  77 + \"productId\": \"${PRODUCT_ID}\",
  78 + \"storeId\": \"${STORE_ID}\",
  79 + \"usageTime\": \"2025-10-15T00:00:00\",
  80 + \"usageQuantity\": 10
  81 + }
  82 + ]
  83 + }" | jq .
  84 +fi
  85 +
  86 +echo ""
  87 +echo "=== 5. 重新计算2025年11月店长工资 ==="
  88 +curl -s -X POST "http://localhost:2011/api/Extend/LqStoreManagerSalary/calculate/store-manager?year=2025&month=11" \
  89 + -H "Authorization: ${TOKEN}" | jq .
  90 +
  91 +echo ""
  92 +echo "=== 6. 查询计算结果 ==="
  93 +curl -s -X GET "http://localhost:2011/api/Extend/LqStoreManagerSalary/store-manager?Year=2025&Month=11&currentPage=1&pageSize=5" \
  94 + -H "Authorization: ${TOKEN}" | jq '.data.list[0] | {
  95 + StoreName,
  96 + EmployeeName,
  97 + SalesPerformance,
  98 + ProductMaterial,
  99 + CooperationCost,
  100 + StoreExpense,
  101 + LaundryCost,
  102 + GrossProfit,
  103 + BaseSalary,
  104 + ActualBaseSalary,
  105 + CommissionRate,
  106 + CommissionAmount,
  107 + GrossSalary,
  108 + IsNewStore,
  109 + StoreCategory,
  110 + StoreType
  111 + }'
  112 +
... ...
测试脚本-合作成本表和店内支出表.sh 0 → 100755
  1 +#!/bin/bash
  2 +
  3 +# 测试脚本 - 合作成本表和店内支出表
  4 +# 使用方法:./测试脚本-合作成本表和店内支出表.sh <token> <base_url>
  5 +# 示例:./测试脚本-合作成本表和店内支出表.sh "Bearer xxx" "http://localhost:2011"
  6 +
  7 +TOKEN=${1:-"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUyOTAwNTQsIm5iZiI6MTc2NTI5MDA1NCwiZXhwIjoxNzY1MzQ0MDU0LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.d68JfuAnxusEWSzloJu4uFPd5uK-Tf8_AA2gBPui-k4"}
  8 +BASE_URL=${2:-"http://localhost:2011"}
  9 +
  10 +echo "=========================================="
  11 +echo "开始测试 - 合作成本表和店内支出表"
  12 +echo "=========================================="
  13 +echo ""
  14 +
  15 +# 测试门店ID
  16 +STORE_ID_1="1649328471923847168" # 绿纤总部
  17 +STORE_ID_2="1649328471923847169" # 绿纤紫荆店
  18 +STORE_ID_3="1649328471923847170" # 绿纤龙湖店
  19 +
  20 +# 颜色输出
  21 +GREEN='\033[0;32m'
  22 +RED='\033[0;31m'
  23 +YELLOW='\033[1;33m'
  24 +NC='\033[0m' # No Color
  25 +
  26 +# 测试函数
  27 +test_api() {
  28 + local name=$1
  29 + local method=$2
  30 + local url=$3
  31 + local data=$4
  32 +
  33 + echo -e "${YELLOW}测试: ${name}${NC}"
  34 + echo "请求: ${method} ${url}"
  35 + if [ -n "$data" ]; then
  36 + echo "数据: ${data}"
  37 + response=$(curl -s -w "\n%{http_code}" -X ${method} "${BASE_URL}${url}" \
  38 + -H "Authorization: ${TOKEN}" \
  39 + -H "Content-Type: application/json" \
  40 + -d "${data}")
  41 + else
  42 + response=$(curl -s -w "\n%{http_code}" -X ${method} "${BASE_URL}${url}" \
  43 + -H "Authorization: ${TOKEN}")
  44 + fi
  45 +
  46 + http_code=$(echo "$response" | tail -n1)
  47 + body=$(echo "$response" | sed '$d')
  48 +
  49 + if [ "$http_code" -eq 200 ] || [ "$http_code" -eq 201 ]; then
  50 + echo -e "${GREEN}✓ 成功 (HTTP ${http_code})${NC}"
  51 + echo "响应: ${body}" | jq . 2>/dev/null || echo "响应: ${body}"
  52 + echo ""
  53 + echo "$body"
  54 + else
  55 + echo -e "${RED}✗ 失败 (HTTP ${http_code})${NC}"
  56 + echo "响应: ${body}"
  57 + echo ""
  58 + echo ""
  59 + fi
  60 +}
  61 +
  62 +# ==========================================
  63 +# 第一阶段:合作成本表 CRUD 测试
  64 +# ==========================================
  65 +echo "=========================================="
  66 +echo "第一阶段:合作成本表 CRUD 测试"
  67 +echo "=========================================="
  68 +echo ""
  69 +
  70 +# 1.1 创建合作成本记录
  71 +echo "1.1 创建合作成本记录"
  72 +COOPERATION_COST_DATA='{"storeId":"'${STORE_ID_1}'","storeName":"绿纤总部","year":2025,"month":"202501","totalAmount":5000.00,"remarks":"CRUD测试-创建"}'
  73 +CREATE_RESPONSE=$(test_api "创建合作成本记录" "POST" "/api/Extend/LqCooperationCost" "${COOPERATION_COST_DATA}")
  74 +COOPERATION_COST_ID=$(echo "$CREATE_RESPONSE" | jq -r '.id // empty' 2>/dev/null)
  75 +
  76 +if [ -z "$COOPERATION_COST_ID" ]; then
  77 + echo "无法获取创建的记录ID,尝试从响应中提取..."
  78 + COOPERATION_COST_ID=$(echo "$CREATE_RESPONSE" | grep -oP '"id"\s*:\s*"[^"]*"' | head -1 | cut -d'"' -f4)
  79 +fi
  80 +
  81 +echo "创建的记录ID: ${COOPERATION_COST_ID}"
  82 +echo ""
  83 +
  84 +# 1.2 查询合作成本列表
  85 +echo "1.2 查询合作成本列表"
  86 +test_api "查询合作成本列表" "GET" "/api/Extend/LqCooperationCost?currentPage=1&pageSize=10&storeId=${STORE_ID_1}" ""
  87 +echo ""
  88 +
  89 +# 1.3 查询合作成本详情
  90 +if [ -n "$COOPERATION_COST_ID" ]; then
  91 + echo "1.3 查询合作成本详情"
  92 + test_api "查询合作成本详情" "GET" "/api/Extend/LqCooperationCost/${COOPERATION_COST_ID}" ""
  93 + echo ""
  94 +
  95 + # 1.4 更新合作成本记录
  96 + echo "1.4 更新合作成本记录"
  97 + UPDATE_DATA='{"id":"'${COOPERATION_COST_ID}'","storeId":"'${STORE_ID_1}'","storeName":"绿纤总部","year":2025,"month":"202501","totalAmount":6000.00,"remarks":"CRUD测试-更新"}'
  98 + test_api "更新合作成本记录" "PUT" "/api/Extend/LqCooperationCost/${COOPERATION_COST_ID}" "${UPDATE_DATA}"
  99 + echo ""
  100 +fi
  101 +
  102 +# ==========================================
  103 +# 第二阶段:店内支出表 CRUD 测试
  104 +# ==========================================
  105 +echo "=========================================="
  106 +echo "第二阶段:店内支出表 CRUD 测试"
  107 +echo "=========================================="
  108 +echo ""
  109 +
  110 +# 2.1 创建店内支出记录
  111 +echo "2.1 创建店内支出记录"
  112 +STORE_EXPENSE_DATA='{"storeId":"'${STORE_ID_1}'","storeName":"绿纤总部","expenseCategoryId":"","expenseCategoryName":"办公用品","expenseDate":"2025-01-15T00:00:00","unitPrice":10.50,"quantity":5,"amount":52.50,"memo":"CRUD测试-创建","attachment":[],"relatedReimbursementId":"","relatedPurchaseRecordId":""}'
  113 +CREATE_EXPENSE_RESPONSE=$(test_api "创建店内支出记录" "POST" "/api/Extend/LqStoreExpense" "${STORE_EXPENSE_DATA}")
  114 +STORE_EXPENSE_ID=$(echo "$CREATE_EXPENSE_RESPONSE" | jq -r '.id // empty' 2>/dev/null)
  115 +
  116 +if [ -z "$STORE_EXPENSE_ID" ]; then
  117 + echo "无法获取创建的记录ID,尝试从响应中提取..."
  118 + STORE_EXPENSE_ID=$(echo "$CREATE_EXPENSE_RESPONSE" | grep -oP '"id"\s*:\s*"[^"]*"' | head -1 | cut -d'"' -f4)
  119 +fi
  120 +
  121 +echo "创建的记录ID: ${STORE_EXPENSE_ID}"
  122 +echo ""
  123 +
  124 +# 2.2 查询店内支出列表
  125 +echo "2.2 查询店内支出列表"
  126 +test_api "查询店内支出列表" "GET" "/api/Extend/LqStoreExpense?currentPage=1&pageSize=10&storeId=${STORE_ID_1}" ""
  127 +echo ""
  128 +
  129 +# 2.3 查询店内支出详情
  130 +if [ -n "$STORE_EXPENSE_ID" ]; then
  131 + echo "2.3 查询店内支出详情"
  132 + test_api "查询店内支出详情" "GET" "/api/Extend/LqStoreExpense/${STORE_EXPENSE_ID}" ""
  133 + echo ""
  134 +
  135 + # 2.4 更新店内支出记录
  136 + echo "2.4 更新店内支出记录"
  137 + UPDATE_EXPENSE_DATA='{"id":"'${STORE_EXPENSE_ID}'","storeId":"'${STORE_ID_1}'","storeName":"绿纤总部","expenseCategoryId":"","expenseCategoryName":"水电费","expenseDate":"2025-01-20T00:00:00","unitPrice":0,"quantity":0,"amount":500.00,"memo":"CRUD测试-更新","attachment":[],"relatedReimbursementId":"","relatedPurchaseRecordId":""}'
  138 + test_api "更新店内支出记录" "PUT" "/api/Extend/LqStoreExpense/${STORE_EXPENSE_ID}" "${UPDATE_EXPENSE_DATA}"
  139 + echo ""
  140 +fi
  141 +
  142 +# ==========================================
  143 +# 第三阶段:Excel导入测试
  144 +# ==========================================
  145 +echo "=========================================="
  146 +echo "第三阶段:Excel导入测试"
  147 +echo "=========================================="
  148 +echo ""
  149 +
  150 +# 3.1 导入合作成本表
  151 +echo "3.1 导入合作成本表"
  152 +echo "注意:需要手动执行以下命令(需要提供Excel文件路径)"
  153 +echo "curl -X POST \"${BASE_URL}/api/Extend/LqCooperationCost/Actions/Import\" \\"
  154 +echo " -H \"Authorization: ${TOKEN}\" \\"
  155 +echo " -F \"file=@excel/合作成本表.xlsx\""
  156 +echo ""
  157 +
  158 +# 3.2 导入店内支出表
  159 +echo "3.2 导入店内支出表"
  160 +echo "注意:需要手动执行以下命令(需要提供Excel文件路径)"
  161 +echo "curl -X POST \"${BASE_URL}/api/Extend/LqStoreExpense/Actions/Import\" \\"
  162 +echo " -H \"Authorization: ${TOKEN}\" \\"
  163 +echo " -F \"file=@excel/店内支出表.xlsx\""
  164 +echo ""
  165 +
  166 +# ==========================================
  167 +# 第四阶段:Excel导出测试
  168 +# ==========================================
  169 +echo "=========================================="
  170 +echo "第四阶段:Excel导出测试"
  171 +echo "=========================================="
  172 +echo ""
  173 +
  174 +# 4.1 导出合作成本表
  175 +echo "4.1 导出合作成本表"
  176 +test_api "导出合作成本表" "GET" "/api/Extend/LqCooperationCost/Actions/Export?dataType=1&storeId=${STORE_ID_1}&year=2025&month=202501" ""
  177 +echo ""
  178 +
  179 +# 4.2 导出店内支出表
  180 +echo "4.2 导出店内支出表"
  181 +test_api "导出店内支出表" "GET" "/api/Extend/LqStoreExpense/Actions/Export?dataType=1&storeId=${STORE_ID_1}" ""
  182 +echo ""
  183 +
  184 +# ==========================================
  185 +# 第五阶段:报销表导出测试
  186 +# ==========================================
  187 +echo "=========================================="
  188 +echo "第五阶段:报销表导出测试"
  189 +echo "=========================================="
  190 +echo ""
  191 +
  192 +# 5.1 导出报销表明细
  193 +echo "5.1 导出报销表明细(本月已审核通过的)"
  194 +test_api "导出报销表明细" "GET" "/api/Extend/LqReimbursementApplication/Actions/ExportApprovedDetails?year=2025&month=01" ""
  195 +echo ""
  196 +
  197 +echo "=========================================="
  198 +echo "测试完成"
  199 +echo "=========================================="
  200 +
... ...
测试计划-合作成本表和店内支出表.md 0 → 100644
  1 +# 测试计划 - 合作成本表和店内支出表
  2 +
  3 +## 📋 测试范围
  4 +
  5 +1. **合作成本表**
  6 + - CRUD操作(创建、查询、更新、删除)
  7 + - Excel导入功能
  8 + - Excel导出功能
  9 +
  10 +2. **店内支出表**
  11 + - CRUD操作(创建、查询、更新、删除)
  12 + - Excel导入功能
  13 + - Excel导出功能
  14 +
  15 +3. **报销表导出功能**
  16 + - 导出本月已审核通过的报销表明细
  17 +
  18 +---
  19 +
  20 +## 🧪 测试步骤
  21 +
  22 +### 第一阶段:基础功能测试
  23 +
  24 +#### 1. 合作成本表 - CRUD测试
  25 +
  26 +**1.1 创建合作成本记录**
  27 +```bash
  28 +POST /api/Extend/LqCooperationCost
  29 +Content-Type: application/json
  30 +
  31 +{
  32 + "storeId": "门店ID",
  33 + "storeName": "测试门店",
  34 + "year": 2025,
  35 + "month": "202501",
  36 + "totalAmount": 5000.00,
  37 + "remarks": "测试数据"
  38 +}
  39 +```
  40 +
  41 +**预期结果**:创建成功,返回记录ID
  42 +
  43 +**1.2 查询合作成本列表**
  44 +```bash
  45 +GET /api/Extend/LqCooperationCost?currentPage=1&pageSize=10&storeId=门店ID
  46 +```
  47 +
  48 +**预期结果**:返回分页列表,包含刚创建的记录
  49 +
  50 +**1.3 查询合作成本详情**
  51 +```bash
  52 +GET /api/Extend/LqCooperationCost/{id}
  53 +```
  54 +
  55 +**预期结果**:返回记录详情
  56 +
  57 +**1.4 更新合作成本记录**
  58 +```bash
  59 +PUT /api/Extend/LqCooperationCost/{id}
  60 +Content-Type: application/json
  61 +
  62 +{
  63 + "id": "记录ID",
  64 + "storeId": "门店ID",
  65 + "storeName": "测试门店",
  66 + "year": 2025,
  67 + "month": "202501",
  68 + "totalAmount": 6000.00,
  69 + "remarks": "更新后的测试数据"
  70 +}
  71 +```
  72 +
  73 +**预期结果**:更新成功
  74 +
  75 +**1.5 删除合作成本记录**
  76 +```bash
  77 +DELETE /api/Extend/LqCooperationCost/{id}
  78 +```
  79 +
  80 +**预期结果**:逻辑删除成功(IsEffective = 0)
  81 +
  82 +---
  83 +
  84 +#### 2. 店内支出表 - CRUD测试
  85 +
  86 +**2.1 创建店内支出记录**
  87 +```bash
  88 +POST /api/Extend/LqStoreExpense
  89 +Content-Type: application/json
  90 +
  91 +{
  92 + "storeId": "门店ID",
  93 + "storeName": "测试门店",
  94 + "expenseCategoryId": "",
  95 + "expenseCategoryName": "办公用品",
  96 + "expenseDate": "2025-01-15T00:00:00",
  97 + "unitPrice": 10.50,
  98 + "quantity": 5,
  99 + "amount": 52.50,
  100 + "memo": "测试支出",
  101 + "attachment": [],
  102 + "relatedReimbursementId": "",
  103 + "relatedPurchaseRecordId": ""
  104 +}
  105 +```
  106 +
  107 +**预期结果**:创建成功
  108 +
  109 +**2.2 查询店内支出列表**
  110 +```bash
  111 +GET /api/Extend/LqStoreExpense?currentPage=1&pageSize=10&storeId=门店ID
  112 +```
  113 +
  114 +**预期结果**:返回分页列表
  115 +
  116 +**2.3 查询店内支出详情**
  117 +```bash
  118 +GET /api/Extend/LqStoreExpense/{id}
  119 +```
  120 +
  121 +**预期结果**:返回记录详情
  122 +
  123 +**2.4 更新店内支出记录**
  124 +```bash
  125 +PUT /api/Extend/LqStoreExpense/{id}
  126 +Content-Type: application/json
  127 +
  128 +{
  129 + "id": "记录ID",
  130 + "storeId": "门店ID",
  131 + "storeName": "测试门店",
  132 + "expenseCategoryId": "",
  133 + "expenseCategoryName": "水电费",
  134 + "expenseDate": "2025-01-20T00:00:00",
  135 + "unitPrice": 0,
  136 + "quantity": 0,
  137 + "amount": 500.00,
  138 + "memo": "更新后的测试支出",
  139 + "attachment": [],
  140 + "relatedReimbursementId": "",
  141 + "relatedPurchaseRecordId": ""
  142 +}
  143 +```
  144 +
  145 +**预期结果**:更新成功
  146 +
  147 +**2.5 删除店内支出记录**
  148 +```bash
  149 +DELETE /api/Extend/LqStoreExpense/{id}
  150 +```
  151 +
  152 +**预期结果**:逻辑删除成功
  153 +
  154 +---
  155 +
  156 +### 第二阶段:Excel导入测试
  157 +
  158 +#### 3. 合作成本表 - Excel导入测试
  159 +
  160 +**3.1 准备Excel文件**
  161 +- 按照 `Excel导入格式说明.md` 中的格式创建Excel文件
  162 +- 包含至少3条测试数据
  163 +- 包含1条错误数据(用于测试错误处理)
  164 +
  165 +**3.2 执行导入**
  166 +```bash
  167 +POST /api/Extend/LqCooperationCost/Actions/Import
  168 +Content-Type: multipart/form-data
  169 +
  170 +file: Excel文件
  171 +```
  172 +
  173 +**预期结果**:
  174 +- 成功导入有效数据
  175 +- 错误数据被跳过并返回错误信息
  176 +- 返回导入结果统计
  177 +
  178 +**3.3 验证导入数据**
  179 +```bash
  180 +GET /api/Extend/LqCooperationCost?storeId=门店ID&year=2025&month=202501
  181 +```
  182 +
  183 +**预期结果**:查询到导入的数据
  184 +
  185 +---
  186 +
  187 +#### 4. 店内支出表 - Excel导入测试
  188 +
  189 +**4.1 准备Excel文件**
  190 +- 按照 `Excel导入格式说明.md` 中的格式创建Excel文件
  191 +- 包含至少3条测试数据
  192 +- 包含1条错误数据(用于测试错误处理)
  193 +
  194 +**4.2 执行导入**
  195 +```bash
  196 +POST /api/Extend/LqStoreExpense/Actions/Import
  197 +Content-Type: multipart/form-data
  198 +
  199 +file: Excel文件
  200 +```
  201 +
  202 +**预期结果**:
  203 +- 成功导入有效数据
  204 +- 错误数据被跳过并返回错误信息
  205 +- 返回导入结果统计
  206 +
  207 +**4.3 验证导入数据**
  208 +```bash
  209 +GET /api/Extend/LqStoreExpense?storeId=门店ID
  210 +```
  211 +
  212 +**预期结果**:查询到导入的数据
  213 +
  214 +---
  215 +
  216 +### 第三阶段:Excel导出测试
  217 +
  218 +#### 5. 合作成本表 - Excel导出测试
  219 +
  220 +**5.1 执行导出**
  221 +```bash
  222 +GET /api/Extend/LqCooperationCost/Actions/Export?dataType=1&storeId=门店ID&year=2025&month=202501
  223 +```
  224 +
  225 +**预期结果**:
  226 +- 返回Excel文件下载链接
  227 +- 下载的文件包含所有查询到的数据
  228 +- Excel格式正确
  229 +
  230 +---
  231 +
  232 +#### 6. 店内支出表 - Excel导出测试
  233 +
  234 +**6.1 执行导出**
  235 +```bash
  236 +GET /api/Extend/LqStoreExpense/Actions/Export?dataType=1&storeId=门店ID
  237 +```
  238 +
  239 +**预期结果**:
  240 +- 返回Excel文件下载链接
  241 +- 下载的文件包含所有查询到的数据
  242 +- Excel格式正确
  243 +
  244 +---
  245 +
  246 +### 第四阶段:报销表导出测试
  247 +
  248 +#### 7. 报销表导出测试
  249 +
  250 +**7.1 准备测试数据**
  251 +- 确保有至少1条已审核通过的报销申请(本月)
  252 +- 该报销申请关联了购买记录
  253 +
  254 +**7.2 执行导出**
  255 +```bash
  256 +GET /api/Extend/LqReimbursementApplication/Actions/ExportApprovedDetails?year=2025&month=01
  257 +```
  258 +
  259 +**预期结果**:
  260 +- 返回Excel文件下载链接
  261 +- 下载的文件包含:
  262 + - 报销申请基本信息(申请ID、申请人、门店、申请时间、申请金额)
  263 + - 关联的购买记录明细(每条购买记录一行)
  264 +- Excel格式正确
  265 +
  266 +---
  267 +
  268 +## ✅ 测试检查清单
  269 +
  270 +### 合作成本表
  271 +- [ ] 创建记录成功
  272 +- [ ] 查询列表成功(分页)
  273 +- [ ] 查询详情成功
  274 +- [ ] 更新记录成功
  275 +- [ ] 删除记录成功(逻辑删除)
  276 +- [ ] Excel导入成功(有效数据)
  277 +- [ ] Excel导入错误处理正确(无效数据被跳过)
  278 +- [ ] Excel导出成功
  279 +- [ ] 导出文件格式正确
  280 +
  281 +### 店内支出表
  282 +- [ ] 创建记录成功
  283 +- [ ] 查询列表成功(分页)
  284 +- [ ] 查询详情成功
  285 +- [ ] 更新记录成功
  286 +- [ ] 删除记录成功(逻辑删除)
  287 +- [ ] Excel导入成功(有效数据)
  288 +- [ ] Excel导入错误处理正确(无效数据被跳过)
  289 +- [ ] Excel导出成功
  290 +- [ ] 导出文件格式正确
  291 +
  292 +### 报销表导出
  293 +- [ ] 导出本月已审核通过的报销申请
  294 +- [ ] 包含购买记录明细
  295 +- [ ] 包含门店、金额等信息
  296 +- [ ] Excel格式正确
  297 +
  298 +---
  299 +
  300 +## 🐛 常见问题处理
  301 +
  302 +### 1. 导入失败 - 门店ID不存在
  303 +**问题**:导入时提示门店ID不存在
  304 +**解决**:检查门店ID是否正确,可以通过门店列表接口获取正确的门店ID
  305 +
  306 +### 2. 导入失败 - 月份格式错误
  307 +**问题**:提示月份格式错误
  308 +**解决**:月份必须是YYYYMM格式(6位字符串),如:202501
  309 +
  310 +### 3. 导入失败 - 日期格式错误
  311 +**问题**:店内支出表导入时提示日期格式错误
  312 +**解决**:日期必须使用标准格式,推荐:YYYY-MM-DD,如:2025-01-15
  313 +
  314 +### 4. 导入失败 - 重复数据
  315 +**问题**:合作成本表导入时提示记录已存在
  316 +**解决**:系统会检查相同门店、年份、月份是否已存在,如果存在会跳过该条记录
  317 +
  318 +---
  319 +
  320 +## 📝 测试数据准备
  321 +
  322 +### 需要准备的数据
  323 +
  324 +1. **门店ID**:至少2个有效的门店ID(用于测试)
  325 +2. **Excel文件**:
  326 + - 合作成本表导入文件(至少3条有效数据 + 1条错误数据)
  327 + - 店内支出表导入文件(至少3条有效数据 + 1条错误数据)
  328 +3. **报销申请**:至少1条本月已审核通过的报销申请(关联购买记录)
  329 +
  330 +### 获取门店ID的方法
  331 +
  332 +```bash
  333 +GET /api/Extend/LqMdxx/Selector
  334 +```
  335 +
  336 +返回结果中包含门店ID和名称。
  337 +
  338 +---
  339 +
  340 +## 🚀 开始测试
  341 +
  342 +请按照以下步骤进行:
  343 +
  344 +1. **准备Excel文件**:根据 `Excel导入格式说明.md` 创建Excel文件
  345 +2. **填写测试数据**:我会在您创建的Excel文件中填写测试数据
  346 +3. **执行测试**:按照测试步骤逐一执行测试
  347 +4. **验证结果**:检查每个测试点的预期结果
  348 +
  349 +准备好Excel文件后,请告诉我,我会填写测试数据并开始测试。
  350 +
... ...