diff --git a/Excel导入格式说明.md b/Excel导入格式说明.md new file mode 100644 index 0000000..523ef84 --- /dev/null +++ b/Excel导入格式说明.md @@ -0,0 +1,146 @@ +# Excel导入格式说明 + +## 📋 合作成本表导入格式 + +### 文件要求 +- 文件格式:`.xlsx` 或 `.xls` +- 第一行为标题行(必须) +- 从第二行开始为数据行 + +### Excel列格式 + +| 列序号 | 列名 | 是否必填 | 数据类型 | 说明 | 示例 | +|--------|------|---------|---------|------|------| +| A | 门店ID | ✅ 必填 | 文本 | 门店ID(关联lq_mdxx.F_Id) | `1649328471923847168` | +| B | 门店名称 | ⚪ 可选 | 文本 | 门店名称(如果为空,系统会根据门店ID自动查询) | `川师店` | +| C | 年份 | ✅ 必填 | 数字 | 年份(4位数字) | `2025` | +| D | 月份 | ✅ 必填 | 文本 | 月份(YYYYMM格式,6位) | `202501` | +| E | 合计金额 | ✅ 必填 | 数字 | 合计金额(支持小数) | `5000.00` | +| F | 备注说明 | ⚪ 可选 | 文本 | 备注说明 | `1月合作项目成本` | + +### Excel示例 + +``` +门店ID | 门店名称 | 年份 | 月份 | 合计金额 | 备注说明 +---------------------------|---------|------|--------|----------|------------------ +1649328471923847168 | 川师店 | 2025 | 202501 | 5000.00 | 1月合作项目成本 +1649328471923847169 | 春熙店 | 2025 | 202501 | 3000.00 | 1月合作项目成本 +``` + +### 注意事项 +1. **门店ID**:必须存在系统中,否则导入会失败 +2. **年份**:必须是4位数字,如:2025 +3. **月份**:必须是YYYYMM格式的6位字符串,如:202501(表示2025年1月) +4. **合计金额**:必须是数字,支持小数,如:5000.00 +5. **重复检查**:系统会检查相同门店、年份、月份是否已存在记录,如果存在会跳过并提示 +6. **空行处理**:如果门店ID和门店名称都为空,该行会被跳过 + +--- + +## 📋 店内支出表导入格式 + +### 文件要求 +- 文件格式:`.xlsx` 或 `.xls` +- 第一行为标题行(必须) +- 从第二行开始为数据行 + +### Excel列格式 + +| 列序号 | 列名 | 是否必填 | 数据类型 | 说明 | 示例 | +|--------|------|---------|---------|------|------| +| A | 门店ID | ✅ 必填 | 文本 | 门店ID(关联lq_mdxx.F_Id) | `1649328471923847168` | +| B | 门店名称 | ⚪ 可选 | 文本 | 门店名称(如果为空,系统会根据门店ID自动查询) | `川师店` | +| C | 支出分类ID | ⚪ 可选 | 文本 | 支出分类ID(关联lq_reimbursement_category.F_Id) | `xxx` | +| D | 支出分类名称 | ⚪ 可选 | 文本 | 支出分类名称 | `办公用品` | +| E | 支出日期 | ✅ 必填 | 日期 | 支出日期(格式:YYYY-MM-DD) | `2025-01-15` | +| F | 单价 | ⚪ 可选 | 数字 | 单价(支持小数) | `10.50` | +| G | 数量 | ⚪ 可选 | 数字 | 数量(整数) | `5` | +| H | 金额 | ✅ 必填 | 数字 | 金额(支持小数) | `52.50` | +| I | 备注说明 | ⚪ 可选 | 文本 | 备注说明 | `购买办公用品` | +| J | 关联报销申请ID | ⚪ 可选 | 文本 | 关联的报销申请ID | `xxx` | +| K | 关联购买记录ID | ⚪ 可选 | 文本 | 关联的购买记录ID | `xxx` | + +### Excel示例 + +``` +门店ID | 门店名称 | 支出分类ID | 支出分类名称 | 支出日期 | 单价 | 数量 | 金额 | 备注说明 | 关联报销申请ID | 关联购买记录ID +---------------------------|---------|-----------|------------|-----------|-------|------|--------|-------------|--------------|--------------- +1649328471923847168 | 川师店 | xxx | 办公用品 | 2025-01-15 | 10.50 | 5 | 52.50 | 购买办公用品 | xxx | xxx +1649328471923847168 | 川师店 | xxx | 水电费 | 2025-01-20 | 0.00 | 0 | 500.00 | 1月水电费 | | +``` + +### 注意事项 +1. **门店ID**:必须存在系统中,否则导入会失败 +2. **支出日期**:必须是日期格式,推荐格式:`YYYY-MM-DD`,如:`2025-01-15` +3. **金额**:必须是数字,支持小数,如:52.50 +4. **单价和数量**:如果为空,默认为0 +5. **空行处理**:如果门店ID和门店名称都为空,该行会被跳过 + +--- + +## 🔗 导入接口说明 + +### 合作成本表导入接口 +``` +POST /api/Extend/LqCooperationCost/Actions/Import +Content-Type: multipart/form-data + +参数: +- file: Excel文件(必填) +``` + +### 店内支出表导入接口 +``` +POST /api/Extend/LqStoreExpense/Actions/Import +Content-Type: multipart/form-data + +参数: +- file: Excel文件(必填) +``` + +### 返回结果 +```json +{ + "success": true, + "message": "导入完成:成功2条,失败0条", + "successCount": 2, + "failCount": 0, + "failMessages": [] +} +``` + +--- + +## 📝 测试数据准备 + +### 合作成本表测试数据(示例) + +请创建一个Excel文件,包含以下测试数据: + +| 门店ID | 门店名称 | 年份 | 月份 | 合计金额 | 备注说明 | +|--------|---------|------|------|----------|----------| +| (请填写实际门店ID) | 测试门店1 | 2025 | 202501 | 5000.00 | 测试数据1 | +| (请填写实际门店ID) | 测试门店2 | 2025 | 202501 | 3000.00 | 测试数据2 | +| (请填写实际门店ID) | 测试门店1 | 2025 | 202502 | 6000.00 | 测试数据3 | + +### 店内支出表测试数据(示例) + +请创建一个Excel文件,包含以下测试数据: + +| 门店ID | 门店名称 | 支出分类ID | 支出分类名称 | 支出日期 | 单价 | 数量 | 金额 | 备注说明 | 关联报销申请ID | 关联购买记录ID | +|--------|---------|-----------|------------|---------|------|------|------|----------|--------------|--------------| +| (请填写实际门店ID) | 测试门店1 | | 办公用品 | 2025-01-15 | 10.50 | 5 | 52.50 | 测试支出1 | | | +| (请填写实际门店ID) | 测试门店1 | | 水电费 | 2025-01-20 | 0 | 0 | 500.00 | 测试支出2 | | | +| (请填写实际门店ID) | 测试门店2 | | 交通费 | 2025-01-25 | 50.00 | 2 | 100.00 | 测试支出3 | | | + +--- + +## ⚠️ 重要提示 + +1. **门店ID获取**:可以通过门店列表接口获取实际的门店ID +2. **日期格式**:支出日期必须使用标准日期格式,推荐使用 `YYYY-MM-DD` +3. **月份格式**:合作成本表的月份必须是YYYYMM格式(6位字符串),如:202501 +4. **金额精度**:所有金额字段支持2位小数 +5. **重复数据**:合作成本表会检查相同门店、年份、月份是否已存在,如果存在会跳过 +6. **错误处理**:导入过程中如果有错误,会在返回结果中列出所有错误信息 + diff --git a/antis-ncc-admin/.env.development b/antis-ncc-admin/.env.development index c236b7d..f6590fa 100644 --- a/antis-ncc-admin/.env.development +++ b/antis-ncc-admin/.env.development @@ -2,7 +2,7 @@ VUE_CLI_BABEL_TRANSPILE_MODULES = true # VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com' -VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com' -# VUE_APP_BASE_API = 'http://localhost:2011' +# VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com' +VUE_APP_BASE_API = 'http://localhost:2011' # VUE_APP_BASE_API = 'http://localhost:2011' VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket' diff --git a/excel/合作成本表.xlsx b/excel/合作成本表.xlsx new file mode 100644 index 0000000..862d084 --- /dev/null +++ b/excel/合作成本表.xlsx diff --git a/excel/店内支出表.xlsx b/excel/店内支出表.xlsx new file mode 100644 index 0000000..77bb348 --- /dev/null +++ b/excel/店内支出表.xlsx diff --git a/excel/考勤统计导入模板_11月.xlsx b/excel/考勤统计导入模板_11月.xlsx new file mode 100644 index 0000000..0ab8932 --- /dev/null +++ b/excel/考勤统计导入模板_11月.xlsx diff --git a/netcore/src/Application/NCC.API/Files/TemporaryFile/合作成本表.xls b/netcore/src/Application/NCC.API/Files/TemporaryFile/合作成本表.xls new file mode 100644 index 0000000..1dcef9f --- /dev/null +++ b/netcore/src/Application/NCC.API/Files/TemporaryFile/合作成本表.xls diff --git a/netcore/src/Application/NCC.API/Files/TemporaryFile/店内支出表.xls b/netcore/src/Application/NCC.API/Files/TemporaryFile/店内支出表.xls new file mode 100644 index 0000000..296dc9a --- /dev/null +++ b/netcore/src/Application/NCC.API/Files/TemporaryFile/店内支出表.xls diff --git a/netcore/src/Application/NCC.API/Files/TemporaryFile/报销表明细_2025年01月.xls b/netcore/src/Application/NCC.API/Files/TemporaryFile/报销表明细_2025年01月.xls new file mode 100644 index 0000000..6202775 --- /dev/null +++ b/netcore/src/Application/NCC.API/Files/TemporaryFile/报销表明细_2025年01月.xls diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs new file mode 100644 index 0000000..ff835a3 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostCrInput.cs @@ -0,0 +1,41 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqCooperationCost +{ + /// + /// 合作成本表创建输入参数 + /// + public class LqCooperationCostCrInput + { + /// + /// 门店ID(关联lq_mdxx.F_Id) + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 年份 + /// + public int year { get; set; } + + /// + /// 月份(YYYYMM格式) + /// + public string month { get; set; } + + /// + /// 合计金额 + /// + public decimal totalAmount { get; set; } + + /// + /// 备注说明 + /// + public string remarks { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs new file mode 100644 index 0000000..c2a3238 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostInfoOutput.cs @@ -0,0 +1,66 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqCooperationCost +{ + /// + /// 合作成本表详情输出参数 + /// + public class LqCooperationCostInfoOutput + { + /// + /// 主键ID + /// + public string id { get; set; } + + /// + /// 门店ID + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 年份 + /// + public int year { get; set; } + + /// + /// 月份(YYYYMM格式) + /// + public string month { get; set; } + + /// + /// 合计金额 + /// + public decimal totalAmount { get; set; } + + /// + /// 备注说明 + /// + public string remarks { get; set; } + + /// + /// 创建人ID + /// + public string createUser { get; set; } + + /// + /// 创建时间 + /// + public DateTime? createTime { get; set; } + + /// + /// 更新人ID + /// + public string updateUser { get; set; } + + /// + /// 更新时间 + /// + public DateTime? updateTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs new file mode 100644 index 0000000..75f2c85 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListOutput.cs @@ -0,0 +1,66 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqCooperationCost +{ + /// + /// 合作成本表列表输出参数 + /// + public class LqCooperationCostListOutput + { + /// + /// 主键ID + /// + public string id { get; set; } + + /// + /// 门店ID + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 年份 + /// + public int year { get; set; } + + /// + /// 月份(YYYYMM格式) + /// + public string month { get; set; } + + /// + /// 合计金额 + /// + public decimal totalAmount { get; set; } + + /// + /// 备注说明 + /// + public string remarks { get; set; } + + /// + /// 创建人ID + /// + public string createUser { get; set; } + + /// + /// 创建时间 + /// + public DateTime? createTime { get; set; } + + /// + /// 更新人ID + /// + public string updateUser { get; set; } + + /// + /// 更新时间 + /// + public DateTime? updateTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListQueryInput.cs new file mode 100644 index 0000000..8251256 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostListQueryInput.cs @@ -0,0 +1,41 @@ +using NCC.Common.Filter; + +namespace NCC.Extend.Entitys.Dto.LqCooperationCost +{ + /// + /// 合作成本表列表查询输入参数 + /// + public class LqCooperationCostListQueryInput : PageInputBase + { + /// + /// 选择导出数据key + /// + public string selectKey { get; set; } + + /// + /// 数据类型(0:分页 1:全部) + /// + public int dataType { get; set; } + + /// + /// 门店ID + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 年份 + /// + public int? year { get; set; } + + /// + /// 月份(YYYYMM格式) + /// + public string month { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs new file mode 100644 index 0000000..e71c08f --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCooperationCost/LqCooperationCostUpInput.cs @@ -0,0 +1,46 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqCooperationCost +{ + /// + /// 合作成本表更新输入参数 + /// + public class LqCooperationCostUpInput + { + /// + /// 主键ID + /// + public string id { get; set; } + + /// + /// 门店ID(关联lq_mdxx.F_Id) + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 年份 + /// + public int year { get; set; } + + /// + /// 月份(YYYYMM格式) + /// + public string month { get; set; } + + /// + /// 合计金额 + /// + public decimal totalAmount { get; set; } + + /// + /// 备注说明 + /// + public string remarks { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseCrInput.cs new file mode 100644 index 0000000..4800fe9 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseCrInput.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using NCC.Common.Model; + +namespace NCC.Extend.Entitys.Dto.LqStoreExpense +{ + /// + /// 店内支出表创建输入参数 + /// + public class LqStoreExpenseCrInput + { + /// + /// 门店ID(关联lq_mdxx.F_Id) + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 支出分类ID(关联lq_reimbursement_category.F_Id) + /// + public string expenseCategoryId { get; set; } + + /// + /// 支出分类名称 + /// + public string expenseCategoryName { get; set; } + + /// + /// 支出日期 + /// + public DateTime expenseDate { get; set; } + + /// + /// 单价 + /// + public decimal unitPrice { get; set; } + + /// + /// 数量 + /// + public int quantity { get; set; } + + /// + /// 金额 + /// + public decimal amount { get; set; } + + /// + /// 备注说明 + /// + public string memo { get; set; } + + /// + /// 附件 + /// + public List attachment { get; set; } + + /// + /// 关联报销申请ID(可选) + /// + public string relatedReimbursementId { get; set; } + + /// + /// 关联购买记录ID(可选) + /// + public string relatedPurchaseRecordId { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseInfoOutput.cs new file mode 100644 index 0000000..09251b2 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseInfoOutput.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using NCC.Common.Model; + +namespace NCC.Extend.Entitys.Dto.LqStoreExpense +{ + /// + /// 店内支出表详情输出参数 + /// + public class LqStoreExpenseInfoOutput + { + /// + /// 主键ID + /// + public string id { get; set; } + + /// + /// 门店ID + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 支出分类ID + /// + public string expenseCategoryId { get; set; } + + /// + /// 支出分类名称 + /// + public string expenseCategoryName { get; set; } + + /// + /// 支出日期 + /// + public DateTime expenseDate { get; set; } + + /// + /// 单价 + /// + public decimal unitPrice { get; set; } + + /// + /// 数量 + /// + public int quantity { get; set; } + + /// + /// 金额 + /// + public decimal amount { get; set; } + + /// + /// 备注说明 + /// + public string memo { get; set; } + + /// + /// 附件 + /// + public List attachment { get; set; } + + /// + /// 关联报销申请ID + /// + public string relatedReimbursementId { get; set; } + + /// + /// 关联购买记录ID + /// + public string relatedPurchaseRecordId { get; set; } + + /// + /// 创建人ID + /// + public string createUser { get; set; } + + /// + /// 创建时间 + /// + public DateTime? createTime { get; set; } + + /// + /// 更新人ID + /// + public string updateUser { get; set; } + + /// + /// 更新时间 + /// + public DateTime? updateTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseListOutput.cs new file mode 100644 index 0000000..5bf9201 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseListOutput.cs @@ -0,0 +1,91 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqStoreExpense +{ + /// + /// 店内支出表列表输出参数 + /// + public class LqStoreExpenseListOutput + { + /// + /// 主键ID + /// + public string id { get; set; } + + /// + /// 门店ID + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 支出分类ID + /// + public string expenseCategoryId { get; set; } + + /// + /// 支出分类名称 + /// + public string expenseCategoryName { get; set; } + + /// + /// 支出日期 + /// + public DateTime expenseDate { get; set; } + + /// + /// 单价 + /// + public decimal unitPrice { get; set; } + + /// + /// 数量 + /// + public int quantity { get; set; } + + /// + /// 金额 + /// + public decimal amount { get; set; } + + /// + /// 备注说明 + /// + public string memo { get; set; } + + /// + /// 关联报销申请ID + /// + public string relatedReimbursementId { get; set; } + + /// + /// 关联购买记录ID + /// + public string relatedPurchaseRecordId { get; set; } + + /// + /// 创建人ID + /// + public string createUser { get; set; } + + /// + /// 创建时间 + /// + public DateTime? createTime { get; set; } + + /// + /// 更新人ID + /// + public string updateUser { get; set; } + + /// + /// 更新时间 + /// + public DateTime? updateTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseListQueryInput.cs new file mode 100644 index 0000000..e637356 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseListQueryInput.cs @@ -0,0 +1,46 @@ +using NCC.Common.Filter; + +namespace NCC.Extend.Entitys.Dto.LqStoreExpense +{ + /// + /// 店内支出表列表查询输入参数 + /// + public class LqStoreExpenseListQueryInput : PageInputBase + { + /// + /// 选择导出数据key + /// + public string selectKey { get; set; } + + /// + /// 数据类型(0:分页 1:全部) + /// + public int dataType { get; set; } + + /// + /// 门店ID + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 支出分类ID + /// + public string expenseCategoryId { get; set; } + + /// + /// 支出日期(开始) + /// + public string expenseDateStart { get; set; } + + /// + /// 支出日期(结束) + /// + public string expenseDateEnd { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseUpInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseUpInput.cs new file mode 100644 index 0000000..15a3776 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreExpense/LqStoreExpenseUpInput.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using NCC.Common.Model; + +namespace NCC.Extend.Entitys.Dto.LqStoreExpense +{ + /// + /// 店内支出表更新输入参数 + /// + public class LqStoreExpenseUpInput + { + /// + /// 主键ID + /// + public string id { get; set; } + + /// + /// 门店ID(关联lq_mdxx.F_Id) + /// + public string storeId { get; set; } + + /// + /// 门店名称 + /// + public string storeName { get; set; } + + /// + /// 支出分类ID(关联lq_reimbursement_category.F_Id) + /// + public string expenseCategoryId { get; set; } + + /// + /// 支出分类名称 + /// + public string expenseCategoryName { get; set; } + + /// + /// 支出日期 + /// + public DateTime expenseDate { get; set; } + + /// + /// 单价 + /// + public decimal unitPrice { get; set; } + + /// + /// 数量 + /// + public int quantity { get; set; } + + /// + /// 金额 + /// + public decimal amount { get; set; } + + /// + /// 备注说明 + /// + public string memo { get; set; } + + /// + /// 附件 + /// + public List attachment { get; set; } + + /// + /// 关联报销申请ID(可选) + /// + public string relatedReimbursementId { get; set; } + + /// + /// 关联购买记录ID(可选) + /// + public string relatedPurchaseRecordId { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreManagerSalary/StoreManagerSalaryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreManagerSalary/StoreManagerSalaryInput.cs new file mode 100644 index 0000000..97894e2 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreManagerSalary/StoreManagerSalaryInput.cs @@ -0,0 +1,32 @@ +using NCC.Common.Filter; +using System; + +namespace NCC.Extend.Entitys.Dto.LqStoreManagerSalary +{ + /// + /// 店长工资查询参数 + /// + public class StoreManagerSalaryInput : PageInputBase + { + /// + /// 年份 + /// + public int Year { get; set; } + + /// + /// 月份 + /// + public int Month { get; set; } + + /// + /// 门店ID(可选,用于筛选特定门店) + /// + public string StoreId { get; set; } + + /// + /// 员工姓名/账号(可选,用于模糊搜索) + /// + public string Keyword { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreManagerSalary/StoreManagerSalaryOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreManagerSalary/StoreManagerSalaryOutput.cs new file mode 100644 index 0000000..5d77b43 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreManagerSalary/StoreManagerSalaryOutput.cs @@ -0,0 +1,236 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqStoreManagerSalary +{ + /// + /// 店长工资输出 + /// + public class StoreManagerSalaryOutput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 员工姓名 + /// + public string EmployeeName { get; set; } + + /// + /// 岗位 + /// + public string Position { get; set; } + + /// + /// 门店总业绩 + /// + public decimal StoreTotalPerformance { get; set; } + + /// + /// 门店开单业绩 + /// + public decimal StoreBillingPerformance { get; set; } + + /// + /// 门店退卡业绩 + /// + public decimal StoreRefundPerformance { get; set; } + + /// + /// 门店生命线 + /// + public decimal StoreLifeline { get; set; } + + /// + /// 业绩完成率 + /// + public decimal PerformanceCompletionRate { get; set; } + + /// + /// 业绩是否达标 + /// + public string PerformanceReached { get; set; } + + /// + /// 人头是否达标 + /// + public string HeadCountReached { get; set; } + + /// + /// 消耗是否达标 + /// + public string ConsumeReached { get; set; } + + /// + /// 考核扣款金额 + /// + public decimal AssessmentDeduction { get; set; } + + /// + /// 未达标指标数量 + /// + public int UnreachedIndicatorCount { get; set; } + + /// + /// 进店消耗人数 + /// + public int HeadCount { get; set; } + + /// + /// 目标人头数 + /// + public decimal TargetHeadCount { get; set; } + + /// + /// 门店消耗金额 + /// + public decimal StoreConsume { get; set; } + + /// + /// 目标消耗金额 + /// + public decimal TargetConsume { get; set; } + + /// + /// 销售业绩(开单业绩-退款业绩) + /// + public decimal SalesPerformance { get; set; } + + /// + /// 产品物料(仓库领用金额) + /// + public decimal ProductMaterial { get; set; } + + /// + /// 合作项目成本 + /// + public decimal CooperationCost { get; set; } + + /// + /// 店内支出 + /// + public decimal StoreExpense { get; set; } + + /// + /// 洗毛巾费用 + /// + public decimal LaundryCost { get; set; } + + /// + /// 毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾) + /// + public decimal GrossProfit { get; set; } + + /// + /// 提成比例 + /// + public decimal CommissionRate { get; set; } + + /// + /// 提成金额(基于毛利) + /// + public decimal CommissionAmount { get; set; } + + /// + /// 底薪金额(固定4000元) + /// + public decimal BaseSalary { get; set; } + + /// + /// 旗舰店负奖励(800元) + /// + public decimal FlagshipStoreDeduction { get; set; } + + /// + /// 实际底薪(底薪-考核扣款-旗舰店负奖励) + /// + public decimal ActualBaseSalary { get; set; } + + /// + /// 在店天数 + /// + public int WorkingDays { get; set; } + + /// + /// 请假天数 + /// + public int LeaveDays { get; set; } + + /// + /// 应发工资(实际底薪+提成) + /// + public decimal GrossSalary { get; set; } + + /// + /// 实发工资(应发工资-扣款合计+补贴合计+奖金) + /// + public decimal ActualSalary { get; set; } + + /// + /// 扣款合计 + /// + public decimal TotalDeduction { get; set; } + + /// + /// 补贴合计 + /// + public decimal TotalSubsidy { get; set; } + + /// + /// 发奖金 + /// + public decimal Bonus { get; set; } + + /// + /// 当月是否发放 + /// + public string MonthlyPaymentStatus { get; set; } + + /// + /// 支付金额 + /// + public decimal PaidAmount { get; set; } + + /// + /// 待支付金额 + /// + public decimal PendingAmount { get; set; } + + /// + /// 是否锁定(0未锁定,1已锁定) + /// + public int IsLocked { get; set; } + + /// + /// 门店类型 + /// + public int? StoreType { get; set; } + + /// + /// 门店类别 + /// + public int? StoreCategory { get; set; } + + /// + /// 是否新店 + /// + public string IsNewStore { get; set; } + + /// + /// 新店保护阶段 + /// + public int NewStoreProtectionStage { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdateTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs new file mode 100644 index 0000000..53efc8d --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_cooperation_cost/LqCooperationCostEntity.cs @@ -0,0 +1,87 @@ +using System; +using NCC.Common.Const; +using SqlSugar; + +namespace NCC.Extend.Entitys.lq_cooperation_cost +{ + /// + /// 合作成本表 + /// + [SugarTable("lq_cooperation_cost")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqCooperationCostEntity + { + /// + /// 主键ID + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 门店ID(关联lq_mdxx.F_Id) + /// + [SugarColumn(ColumnName = "F_StoreId")] + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + [SugarColumn(ColumnName = "F_StoreName")] + public string StoreName { get; set; } + + /// + /// 年份 + /// + [SugarColumn(ColumnName = "F_Year")] + public int Year { get; set; } + + /// + /// 月份(YYYYMM格式) + /// + [SugarColumn(ColumnName = "F_Month")] + public string Month { get; set; } + + /// + /// 合计金额 + /// + [SugarColumn(ColumnName = "F_TotalAmount", DecimalDigits = 2)] + public decimal TotalAmount { get; set; } + + /// + /// 备注说明 + /// + [SugarColumn(ColumnName = "F_Remarks")] + public string Remarks { get; set; } + + /// + /// 是否有效(1:有效 0:无效) + /// + [SugarColumn(ColumnName = "F_IsEffective")] + public int IsEffective { get; set; } = 1; + + /// + /// 创建人ID + /// + [SugarColumn(ColumnName = "F_CreateUser")] + public string CreateUser { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "F_CreateTime")] + public DateTime? CreateTime { get; set; } + + /// + /// 更新人ID + /// + [SugarColumn(ColumnName = "F_UpdateUser")] + public string UpdateUser { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(ColumnName = "F_UpdateTime")] + public DateTime? UpdateTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs index b4fa096..556f6ad 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs +++ b/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 } + + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs index 2de8618..8aafa4a 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs +++ b/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 } + + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs index 5826882..15613a3 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs +++ b/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 } + + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_store_expense/LqStoreExpenseEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_store_expense/LqStoreExpenseEntity.cs new file mode 100644 index 0000000..3d172be --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_store_expense/LqStoreExpenseEntity.cs @@ -0,0 +1,123 @@ +using System; +using NCC.Common.Const; +using SqlSugar; + +namespace NCC.Extend.Entitys.lq_store_expense +{ + /// + /// 店内支出表 + /// + [SugarTable("lq_store_expense")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqStoreExpenseEntity + { + /// + /// 主键ID + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 门店ID(关联lq_mdxx.F_Id) + /// + [SugarColumn(ColumnName = "F_StoreId")] + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + [SugarColumn(ColumnName = "F_StoreName")] + public string StoreName { get; set; } + + /// + /// 支出分类ID(关联lq_reimbursement_category.F_Id) + /// + [SugarColumn(ColumnName = "F_ExpenseCategoryId")] + public string ExpenseCategoryId { get; set; } + + /// + /// 支出分类名称 + /// + [SugarColumn(ColumnName = "F_ExpenseCategoryName")] + public string ExpenseCategoryName { get; set; } + + /// + /// 支出日期 + /// + [SugarColumn(ColumnName = "F_ExpenseDate")] + public DateTime ExpenseDate { get; set; } + + /// + /// 单价 + /// + [SugarColumn(ColumnName = "F_UnitPrice", DecimalDigits = 2)] + public decimal UnitPrice { get; set; } + + /// + /// 数量 + /// + [SugarColumn(ColumnName = "F_Quantity")] + public int Quantity { get; set; } + + /// + /// 金额 + /// + [SugarColumn(ColumnName = "F_Amount", DecimalDigits = 2)] + public decimal Amount { get; set; } + + /// + /// 备注说明 + /// + [SugarColumn(ColumnName = "F_Memo")] + public string Memo { get; set; } + + /// + /// 附件(JSON格式) + /// + [SugarColumn(ColumnName = "F_Attachment", ColumnDataType = "TEXT")] + public string Attachment { get; set; } + + /// + /// 关联报销申请ID(可选) + /// + [SugarColumn(ColumnName = "F_RelatedReimbursementId")] + public string RelatedReimbursementId { get; set; } + + /// + /// 关联购买记录ID(可选) + /// + [SugarColumn(ColumnName = "F_RelatedPurchaseRecordId")] + public string RelatedPurchaseRecordId { get; set; } + + /// + /// 是否有效(1:有效 0:无效) + /// + [SugarColumn(ColumnName = "F_IsEffective")] + public int IsEffective { get; set; } = 1; + + /// + /// 创建人ID + /// + [SugarColumn(ColumnName = "F_CreateUser")] + public string CreateUser { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "F_CreateTime")] + public DateTime? CreateTime { get; set; } + + /// + /// 更新人ID + /// + [SugarColumn(ColumnName = "F_UpdateUser")] + public string UpdateUser { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(ColumnName = "F_UpdateTime")] + public DateTime? UpdateTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_store_manager_salary_statistics/LqStoreManagerSalaryStatisticsEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_store_manager_salary_statistics/LqStoreManagerSalaryStatisticsEntity.cs new file mode 100644 index 0000000..1ec9afc --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_store_manager_salary_statistics/LqStoreManagerSalaryStatisticsEntity.cs @@ -0,0 +1,417 @@ +using System; +using NCC.Common.Const; +using SqlSugar; + +namespace NCC.Extend.Entitys.lq_store_manager_salary_statistics +{ + /// + /// 店长工资统计表 + /// + [SugarTable("lq_store_manager_salary_statistics")] + [Tenant(ClaimConst.TENANT_ID)] + public class LqStoreManagerSalaryStatisticsEntity + { + /// + /// 主键ID + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 门店ID + /// + [SugarColumn(ColumnName = "F_StoreId")] + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + [SugarColumn(ColumnName = "F_StoreName")] + public string StoreName { get; set; } + + /// + /// 核算岗位 + /// + [SugarColumn(ColumnName = "F_Position")] + public string Position { get; set; } + + /// + /// 员工ID + /// + [SugarColumn(ColumnName = "F_EmployeeId")] + public string EmployeeId { get; set; } + + /// + /// 员工姓名 + /// + [SugarColumn(ColumnName = "F_EmployeeName")] + public string EmployeeName { get; set; } + + /// + /// 统计月份(YYYYMM) + /// + [SugarColumn(ColumnName = "F_StatisticsMonth")] + public string StatisticsMonth { get; set; } + + /// + /// 门店类型 + /// + [SugarColumn(ColumnName = "F_StoreType")] + public int? StoreType { get; set; } + + /// + /// 门店类别 + /// + [SugarColumn(ColumnName = "F_StoreCategory")] + public int? StoreCategory { get; set; } + + /// + /// 是否新店 + /// + [SugarColumn(ColumnName = "F_IsNewStore")] + public string IsNewStore { get; set; } + + /// + /// 新店保护阶段 + /// + [SugarColumn(ColumnName = "F_NewStoreProtectionStage")] + public int NewStoreProtectionStage { get; set; } + + /// + /// 门店总业绩 + /// + [SugarColumn(ColumnName = "F_StoreTotalPerformance")] + public decimal StoreTotalPerformance { get; set; } + + /// + /// 门店开单业绩 + /// + [SugarColumn(ColumnName = "F_StoreBillingPerformance")] + public decimal StoreBillingPerformance { get; set; } + + /// + /// 门店退卡业绩 + /// + [SugarColumn(ColumnName = "F_StoreRefundPerformance")] + public decimal StoreRefundPerformance { get; set; } + + /// + /// 门店生命线 + /// + [SugarColumn(ColumnName = "F_StoreLifeline")] + public decimal StoreLifeline { get; set; } + + /// + /// 业绩完成率 + /// + [SugarColumn(ColumnName = "F_PerformanceCompletionRate")] + public decimal PerformanceCompletionRate { get; set; } + + /// + /// 业绩是否达标 + /// + [SugarColumn(ColumnName = "F_PerformanceReached")] + public string PerformanceReached { get; set; } + + /// + /// 进店消耗人数 + /// + [SugarColumn(ColumnName = "F_HeadCount")] + public int HeadCount { get; set; } + + /// + /// 目标人头数 + /// + [SugarColumn(ColumnName = "F_TargetHeadCount")] + public decimal TargetHeadCount { get; set; } + + /// + /// 人头是否达标 + /// + [SugarColumn(ColumnName = "F_HeadCountReached")] + public string HeadCountReached { get; set; } + + /// + /// 门店消耗金额 + /// + [SugarColumn(ColumnName = "F_StoreConsume")] + public decimal StoreConsume { get; set; } + + /// + /// 目标消耗金额 + /// + [SugarColumn(ColumnName = "F_TargetConsume")] + public decimal TargetConsume { get; set; } + + /// + /// 消耗是否达标 + /// + [SugarColumn(ColumnName = "F_ConsumeReached")] + public string ConsumeReached { get; set; } + + /// + /// 未达标指标数量 + /// + [SugarColumn(ColumnName = "F_UnreachedIndicatorCount")] + public int UnreachedIndicatorCount { get; set; } + + /// + /// 考核扣款金额 + /// + [SugarColumn(ColumnName = "F_AssessmentDeduction")] + public decimal AssessmentDeduction { get; set; } + + /// + /// 销售业绩(开单业绩-退款业绩) + /// + [SugarColumn(ColumnName = "F_SalesPerformance")] + public decimal SalesPerformance { get; set; } + + /// + /// 产品物料(仓库领用金额) + /// + [SugarColumn(ColumnName = "F_ProductMaterial")] + public decimal ProductMaterial { get; set; } + + /// + /// 合作项目成本 + /// + [SugarColumn(ColumnName = "F_CooperationCost")] + public decimal CooperationCost { get; set; } + + /// + /// 店内支出 + /// + [SugarColumn(ColumnName = "F_StoreExpense")] + public decimal StoreExpense { get; set; } + + /// + /// 洗毛巾费用 + /// + [SugarColumn(ColumnName = "F_LaundryCost")] + public decimal LaundryCost { get; set; } + + /// + /// 毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾) + /// + [SugarColumn(ColumnName = "F_GrossProfit")] + public decimal GrossProfit { get; set; } + + /// + /// 提成比例 + /// + [SugarColumn(ColumnName = "F_CommissionRate")] + public decimal CommissionRate { get; set; } + + /// + /// 提成金额(基于毛利) + /// + [SugarColumn(ColumnName = "F_CommissionAmount")] + public decimal CommissionAmount { get; set; } + + /// + /// 底薪金额(固定4000元) + /// + [SugarColumn(ColumnName = "F_BaseSalary")] + public decimal BaseSalary { get; set; } + + /// + /// 旗舰店负奖励(800元) + /// + [SugarColumn(ColumnName = "F_FlagshipStoreDeduction")] + public decimal FlagshipStoreDeduction { get; set; } + + /// + /// 实际底薪(底薪-考核扣款-旗舰店负奖励) + /// + [SugarColumn(ColumnName = "F_ActualBaseSalary")] + public decimal ActualBaseSalary { get; set; } + + /// + /// 在店天数 + /// + [SugarColumn(ColumnName = "F_WorkingDays")] + public int WorkingDays { get; set; } + + /// + /// 请假天数 + /// + [SugarColumn(ColumnName = "F_LeaveDays")] + public int LeaveDays { get; set; } + + /// + /// 应发工资(实际底薪+提成) + /// + [SugarColumn(ColumnName = "F_GrossSalary")] + public decimal GrossSalary { get; set; } + + /// + /// 实发工资(应发工资-扣款合计+补贴合计+奖金) + /// + [SugarColumn(ColumnName = "F_ActualSalary")] + public decimal ActualSalary { get; set; } + + /// + /// 缺卡扣款 + /// + [SugarColumn(ColumnName = "F_MissingCard")] + public decimal MissingCard { get; set; } + + /// + /// 迟到扣款 + /// + [SugarColumn(ColumnName = "F_LateArrival")] + public decimal LateArrival { get; set; } + + /// + /// 请假扣款 + /// + [SugarColumn(ColumnName = "F_LeaveDeduction")] + public decimal LeaveDeduction { get; set; } + + /// + /// 扣社保 + /// + [SugarColumn(ColumnName = "F_SocialInsuranceDeduction")] + public decimal SocialInsuranceDeduction { get; set; } + + /// + /// 扣除奖励 + /// + [SugarColumn(ColumnName = "F_RewardDeduction")] + public decimal RewardDeduction { get; set; } + + /// + /// 扣住宿费 + /// + [SugarColumn(ColumnName = "F_AccommodationDeduction")] + public decimal AccommodationDeduction { get; set; } + + /// + /// 扣学习期费用 + /// + [SugarColumn(ColumnName = "F_StudyPeriodDeduction")] + public decimal StudyPeriodDeduction { get; set; } + + /// + /// 扣工作服费用 + /// + [SugarColumn(ColumnName = "F_WorkClothesDeduction")] + public decimal WorkClothesDeduction { get; set; } + + /// + /// 扣款合计 + /// + [SugarColumn(ColumnName = "F_TotalDeduction")] + public decimal TotalDeduction { get; set; } + + /// + /// 当月培训补贴 + /// + [SugarColumn(ColumnName = "F_MonthlyTrainingSubsidy")] + public decimal MonthlyTrainingSubsidy { get; set; } + + /// + /// 当月交通补贴 + /// + [SugarColumn(ColumnName = "F_MonthlyTransportSubsidy")] + public decimal MonthlyTransportSubsidy { get; set; } + + /// + /// 上月培训补贴 + /// + [SugarColumn(ColumnName = "F_LastMonthTrainingSubsidy")] + public decimal LastMonthTrainingSubsidy { get; set; } + + /// + /// 上月交通补贴 + /// + [SugarColumn(ColumnName = "F_LastMonthTransportSubsidy")] + public decimal LastMonthTransportSubsidy { get; set; } + + /// + /// 补贴合计 + /// + [SugarColumn(ColumnName = "F_TotalSubsidy")] + public decimal TotalSubsidy { get; set; } + + /// + /// 发奖金 + /// + [SugarColumn(ColumnName = "F_Bonus")] + public decimal Bonus { get; set; } + + /// + /// 退手机押金 + /// + [SugarColumn(ColumnName = "F_ReturnPhoneDeposit")] + public decimal ReturnPhoneDeposit { get; set; } + + /// + /// 退住宿押金 + /// + [SugarColumn(ColumnName = "F_ReturnAccommodationDeposit")] + public decimal ReturnAccommodationDeposit { get; set; } + + /// + /// 当月是否发放 + /// + [SugarColumn(ColumnName = "F_MonthlyPaymentStatus")] + public string MonthlyPaymentStatus { get; set; } + + /// + /// 支付金额 + /// + [SugarColumn(ColumnName = "F_PaidAmount")] + public decimal PaidAmount { get; set; } + + /// + /// 待支付金额 + /// + [SugarColumn(ColumnName = "F_PendingAmount")] + public decimal PendingAmount { get; set; } + + /// + /// 补发上月 + /// + [SugarColumn(ColumnName = "F_LastMonthSupplement")] + public decimal LastMonthSupplement { get; set; } + + /// + /// 当月支付总额 + /// + [SugarColumn(ColumnName = "F_MonthlyTotalPayment")] + public decimal MonthlyTotalPayment { get; set; } + + /// + /// 是否锁定(0未锁定,1已锁定) + /// + [SugarColumn(ColumnName = "F_IsLocked")] + public int IsLocked { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "F_CreateTime")] + public DateTime CreateTime { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(ColumnName = "F_UpdateTime")] + public DateTime UpdateTime { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "F_CreateUser")] + public string CreateUser { get; set; } + + /// + /// 更新人 + /// + [SugarColumn(ColumnName = "F_UpdateUser")] + public string UpdateUser { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ILqCooperationCostService.cs b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ILqCooperationCostService.cs new file mode 100644 index 0000000..0d35793 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ILqCooperationCostService.cs @@ -0,0 +1,7 @@ +namespace NCC.Extend.Interfaces.LqCooperationCost +{ + public interface ILqCooperationCostService + { + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ILqStoreExpenseService.cs b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ILqStoreExpenseService.cs new file mode 100644 index 0000000..e563d30 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ILqStoreExpenseService.cs @@ -0,0 +1,7 @@ +namespace NCC.Extend.Interfaces.LqStoreExpense +{ + public interface ILqStoreExpenseService + { + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs new file mode 100644 index 0000000..ea062f6 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs @@ -0,0 +1,516 @@ +using NCC.Common.Core.Manager; +using NCC.Common.Enum; +using NCC.Common.Extension; +using NCC.Common.Filter; +using NCC.Dependency; +using NCC.DynamicApiController; +using NCC.FriendlyException; +using NCC.Extend.Interfaces.LqCooperationCost; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NCC.Extend.Entitys.lq_cooperation_cost; +using NCC.Extend.Entitys.Dto.LqCooperationCost; +using NCC.Extend.Entitys.lq_mdxx; +using Yitter.IdGenerator; +using NCC.Common.Helper; +using NCC.Common.Model.NPOI; +using NCC.Common.Configuration; +using NCC.DataEncryption; +using NCC.ClayObject; +using NCC.Common.Const; +using NCC.Extend.Entitys.Enum; +using Microsoft.AspNetCore.Http; +using System.IO; +using System.Data; + +namespace NCC.Extend.LqCooperationCost +{ + /// + /// 合作成本表服务 + /// + [ApiDescriptionSettings(Tag = "Extend", Name = "LqCooperationCost", Order = 200)] + [Route("api/Extend/[controller]")] + public class LqCooperationCostService : ILqCooperationCostService, IDynamicApiController, ITransient + { + private readonly ISqlSugarRepository _repository; + private readonly SqlSugarScope _db; + private readonly IUserManager _userManager; + + /// + /// 初始化一个类型的新实例 + /// + public LqCooperationCostService( + ISqlSugarRepository repository, + IUserManager userManager) + { + _repository = repository; + _db = _repository.Context; + _userManager = userManager; + } + + /// + /// 获取合作成本表详情 + /// + /// 主键ID + /// + [HttpGet("{id}")] + public async Task GetInfo(string id) + { + var entity = await _db.Queryable() + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + var output = entity.Adapt(); + return output; + } + + /// + /// 获取合作成本表列表 + /// + /// 请求参数 + /// + [HttpGet("")] + public async Task GetList([FromQuery] LqCooperationCostListQueryInput input) + { + var sidx = input.sidx ?? "CreateTime"; + var sortType = input.sort?.ToLower() == "asc" ? OrderByType.Asc : OrderByType.Desc; + var query = _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(!string.IsNullOrEmpty(input.storeId), x => x.StoreId == input.storeId) + .WhereIF(!string.IsNullOrEmpty(input.storeName), x => x.StoreName.Contains(input.storeName)) + .WhereIF(input.year.HasValue, x => x.Year == input.year.Value) + .WhereIF(!string.IsNullOrEmpty(input.month), x => x.Month == input.month); + + // 处理排序 + switch (sidx.ToLower()) + { + case "id": + query = query.OrderBy(x => x.Id, sortType); + break; + case "createtime": + query = query.OrderBy(x => x.CreateTime, sortType); + break; + case "storename": + query = query.OrderBy(x => x.StoreName, sortType); + break; + case "year": + query = query.OrderBy(x => x.Year, sortType); + break; + case "month": + query = query.OrderBy(x => x.Month, sortType); + break; + default: + query = query.OrderBy(x => x.CreateTime, OrderByType.Desc); + break; + } + + var total = await query.CountAsync(); + var list = await query.ToPageListAsync(input.currentPage, input.pageSize); + + var result = list.Select(x => new LqCooperationCostListOutput + { + id = x.Id, + storeId = x.StoreId, + storeName = x.StoreName, + year = x.Year, + month = x.Month, + totalAmount = x.TotalAmount, + remarks = x.Remarks, + createUser = x.CreateUser, + createTime = x.CreateTime, + updateUser = x.UpdateUser, + updateTime = x.UpdateTime + }).ToList(); + + return PageResult.SqlSugarPageResult( + new SqlSugarPagedList + { + list = result, + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total } + }); + } + + /// + /// 获取合作成本表无分页列表 + /// + /// 请求参数 + /// + [HttpGet("Actions/GetNoPagingList")] + public async Task> GetNoPagingList([FromQuery] LqCooperationCostListQueryInput input) + { + var sidx = input.sidx ?? "CreateTime"; + var sortType = input.sort?.ToLower() == "asc" ? OrderByType.Asc : OrderByType.Desc; + var query = _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(!string.IsNullOrEmpty(input.storeId), x => x.StoreId == input.storeId) + .WhereIF(!string.IsNullOrEmpty(input.storeName), x => x.StoreName.Contains(input.storeName)) + .WhereIF(input.year.HasValue, x => x.Year == input.year.Value) + .WhereIF(!string.IsNullOrEmpty(input.month), x => x.Month == input.month); + + // 处理排序 + switch (sidx.ToLower()) + { + case "id": + query = query.OrderBy(x => x.Id, sortType); + break; + case "createtime": + query = query.OrderBy(x => x.CreateTime, sortType); + break; + case "storename": + query = query.OrderBy(x => x.StoreName, sortType); + break; + case "year": + query = query.OrderBy(x => x.Year, sortType); + break; + case "month": + query = query.OrderBy(x => x.Month, sortType); + break; + default: + query = query.OrderBy(x => x.CreateTime, OrderByType.Desc); + break; + } + + var list = await query + .Select(x => new LqCooperationCostListOutput + { + id = x.Id, + storeId = x.StoreId, + storeName = x.StoreName, + year = x.Year, + month = x.Month, + totalAmount = x.TotalAmount, + remarks = x.Remarks, + createUser = x.CreateUser, + createTime = x.CreateTime, + updateUser = x.UpdateUser, + updateTime = x.UpdateTime + }) + .ToListAsync(); + return list; + } + + /// + /// 创建合作成本表 + /// + /// 参数 + /// + [HttpPost("")] + public async Task Create([FromBody] LqCooperationCostCrInput input) + { + var userInfo = await _userManager.GetUserInfo(); + var entity = input.Adapt(); + entity.Id = YitIdHelper.NextId().ToString(); + entity.IsEffective = StatusEnum.有效.GetHashCode(); + entity.CreateUser = _userManager.UserId; + entity.CreateTime = DateTime.Now; + + // 如果未提供门店名称,根据门店ID查询 + if (string.IsNullOrEmpty(entity.StoreName) && !string.IsNullOrEmpty(entity.StoreId)) + { + var store = await _db.Queryable() + .Where(x => x.Id == entity.StoreId) + .Select(x => x.Dm) + .FirstAsync(); + entity.StoreName = store; + } + + var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); + } + + /// + /// 更新合作成本表 + /// + /// 主键 + /// 参数 + /// + [HttpPut("{id}")] + public async Task Update(string id, [FromBody] LqCooperationCostUpInput input) + { + var entity = await _db.Queryable() + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + + entity.StoreId = input.storeId; + entity.StoreName = input.storeName; + entity.Year = input.year; + entity.Month = input.month; + entity.TotalAmount = input.totalAmount; + entity.Remarks = input.remarks; + entity.UpdateUser = _userManager.UserId; + entity.UpdateTime = DateTime.Now; + + // 如果未提供门店名称,根据门店ID查询 + if (string.IsNullOrEmpty(entity.StoreName) && !string.IsNullOrEmpty(entity.StoreId)) + { + var store = await _db.Queryable() + .Where(x => x.Id == entity.StoreId) + .Select(x => x.Dm) + .FirstAsync(); + entity.StoreName = store; + } + + var isOk = await _db.Updateable(entity).ExecuteCommandAsync(); + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001); + } + + /// + /// 删除合作成本表 + /// + /// 主键 + /// + [HttpDelete("{id}")] + public async Task Delete(string id) + { + var entity = await _db.Queryable() + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + + // 逻辑删除 + entity.IsEffective = StatusEnum.无效.GetHashCode(); + entity.UpdateUser = _userManager.UserId; + entity.UpdateTime = DateTime.Now; + + var isOk = await _db.Updateable(entity).ExecuteCommandAsync(); + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1002); + } + + /// + /// 导出合作成本表 + /// + /// 请求参数 + /// + [HttpGet("Actions/Export")] + public async Task Export([FromQuery] LqCooperationCostListQueryInput input) + { + var userInfo = await _userManager.GetUserInfo(); + var exportData = new List(); + if (input.dataType == 0) + { + var data = Clay.Object(await this.GetList(input)); + exportData = data.Solidify>().list; + } + else + { + exportData = await this.GetNoPagingList(input); + } + List 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(); + ExcelConfig excelconfig = new ExcelConfig(); + excelconfig.FileName = "合作成本表.xls"; + excelconfig.HeadFont = "微软雅黑"; + excelconfig.HeadPoint = 10; + excelconfig.IsAllSizeColumn = true; + excelconfig.ColumnModel = new List(); + List selectKeyList = !string.IsNullOrEmpty(input.selectKey) ? input.selectKey.Split(',').ToList() : paramList.Select(p => p.field).ToList(); + foreach (var item in selectKeyList) + { + var isExist = paramList.Find(p => p.field == item); + if (isExist != null) + { + excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = isExist.field, ExcelColumn = isExist.value }); + } + } + var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName; + ExcelExportHelper.Export(exportData, excelconfig, addPath); + var fileName = _userManager.UserId + "|" + addPath + "|xls"; + var output = new + { + name = excelconfig.FileName, + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") + }; + return output; + } + + /// + /// 导入合作成本数据 + /// + /// + /// 从Excel文件导入合作成本数据 + /// + /// Excel格式要求: + /// 第一行为标题行:门店ID、门店名称、年份、月份、合计金额、备注说明 + /// 从第二行开始为数据行 + /// + /// 示例请求: + /// POST /api/Extend/LqCooperationCost/Actions/Import + /// Content-Type: multipart/form-data + /// + /// Excel文件 + /// 导入结果 + /// 导入成功 + /// 文件格式错误或数据验证失败 + [HttpPost("Actions/Import")] + public async Task Import(IFormFile file) + { + try + { + if (file == null || file.Length == 0) + { + throw NCCException.Oh("请选择要上传的Excel文件"); + } + + // 检查文件格式 + var allowedExtensions = new[] { ".xlsx", ".xls" }; + var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (!allowedExtensions.Contains(fileExtension)) + { + throw NCCException.Oh("只支持.xlsx和.xls格式的Excel文件"); + } + + var successCount = 0; + var failCount = 0; + var failMessages = new List(); + + // 保存临时文件 + var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName)); + try + { + using (var stream = new FileStream(tempFilePath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + + // 使用ExcelImportHelper读取Excel文件(第一行为标题行) + var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0); + + if (dataTable.Rows.Count <= 1) + { + throw NCCException.Oh("Excel文件中没有数据行(至少需要标题行和一行数据)"); + } + + // 从第1行开始读取数据(跳过标题行) + for (int i = 1; i < dataTable.Rows.Count; i++) + { + try + { + var row = dataTable.Rows[i]; + var storeId = row[0]?.ToString()?.Trim(); + var storeName = row[1]?.ToString()?.Trim(); + var yearText = row[2]?.ToString()?.Trim(); + var monthText = row[3]?.ToString()?.Trim(); + var totalAmountText = row[4]?.ToString()?.Trim(); + var remarks = row[5]?.ToString()?.Trim(); + + // 跳过空行 + if (string.IsNullOrEmpty(storeId) && string.IsNullOrEmpty(storeName)) + { + continue; + } + + // 验证必填字段 + if (string.IsNullOrEmpty(storeId)) + { + failMessages.Add($"第{i + 1}行:门店ID不能为空"); + failCount++; + continue; + } + + if (string.IsNullOrEmpty(yearText) || !int.TryParse(yearText, out int year)) + { + failMessages.Add($"第{i + 1}行:年份格式错误(应为数字)"); + failCount++; + continue; + } + + if (string.IsNullOrEmpty(monthText) || monthText.Length != 6) + { + failMessages.Add($"第{i + 1}行:月份格式错误(应为YYYYMM格式,如:202501)"); + failCount++; + continue; + } + + if (string.IsNullOrEmpty(totalAmountText) || !decimal.TryParse(totalAmountText, out decimal totalAmount)) + { + failMessages.Add($"第{i + 1}行:合计金额格式错误(应为数字)"); + failCount++; + continue; + } + + // 如果未提供门店名称,根据门店ID查询 + if (string.IsNullOrEmpty(storeName)) + { + var store = await _db.Queryable() + .Where(x => x.Id == storeId) + .Select(x => x.Dm) + .FirstAsync(); + storeName = store ?? ""; + } + + // 检查是否已存在相同门店、年份、月份的记录 + var exists = await _db.Queryable() + .Where(x => x.StoreId == storeId && x.Year == year && x.Month == monthText && x.IsEffective == StatusEnum.有效.GetHashCode()) + .AnyAsync(); + + if (exists) + { + failMessages.Add($"第{i + 1}行:该门店{year}年{monthText}月的记录已存在"); + failCount++; + continue; + } + + // 创建记录 + var entity = new LqCooperationCostEntity + { + Id = YitIdHelper.NextId().ToString(), + StoreId = storeId, + StoreName = storeName, + Year = year, + Month = monthText, + TotalAmount = totalAmount, + Remarks = remarks, + IsEffective = StatusEnum.有效.GetHashCode(), + CreateUser = _userManager.UserId, + CreateTime = DateTime.Now + }; + + var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); + if (isOk > 0) + { + successCount++; + } + else + { + failMessages.Add($"第{i + 1}行:保存失败"); + failCount++; + } + } + catch (Exception ex) + { + failMessages.Add($"第{i + 1}行:{ex.Message}"); + failCount++; + } + } + } + finally + { + // 清理临时文件 + if (File.Exists(tempFilePath)) + { + File.Delete(tempFilePath); + } + } + + return new + { + success = true, + message = $"导入完成:成功{successCount}条,失败{failCount}条", + successCount = successCount, + failCount = failCount, + failMessages = failMessages + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"导入失败:{ex.Message}"); + } + } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs index 3de354f..5ce9e32 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs @@ -1470,5 +1470,233 @@ namespace NCC.Extend.LqReimbursementApplication return new List(); } + + /// + /// 导出本月已审核通过的报销表明细 + /// + /// + /// 导出本月已审核通过的报销申请及其关联的购买记录明细 + /// 用于线下整理后导入到店内支出表 + /// + /// 示例请求: + /// GET /api/Extend/LqReimbursementApplication/Actions/ExportApprovedDetails?year=2025&month=01 + /// + /// 年份(可选,默认当前年份) + /// 月份(可选,默认当前月份,格式:01-12) + /// 导出文件信息 + /// 导出成功 + /// 服务器错误 + [HttpGet("Actions/ExportApprovedDetails")] + public async Task ExportApprovedDetails([FromQuery] int? year = null, [FromQuery] string month = null) + { + try + { + var userInfo = await _userManager.GetUserInfo(); + var now = DateTime.Now; + var queryYear = year ?? now.Year; + var queryMonth = !string.IsNullOrEmpty(month) ? month : now.ToString("MM"); + + // 构建月份字符串(YYYYMM格式) + var monthStr = $"{queryYear}{queryMonth}"; + + // 计算月份的开始和结束日期 + var startDate = new DateTime(queryYear, int.Parse(queryMonth), 1); + var endDate = startDate.AddMonths(1).AddDays(-1); + + // 查询本月已审核通过的报销申请 + var applications = await _db.Queryable() + .Where(x => (x.ApprovalStatus ?? x.ApproveStatus) == "已通过") + .Where(x => x.ApplicationTime.HasValue && + x.ApplicationTime.Value.Year == queryYear && + x.ApplicationTime.Value.Month == int.Parse(queryMonth)) + .ToListAsync(); + + // 获取所有关联的购买记录 + var applicationIds = applications.Select(x => x.Id).ToList(); + var purchaseRecords = new List(); + if (applicationIds.Any()) + { + purchaseRecords = await _db.Queryable() + .Where(x => applicationIds.Contains(x.ApplicationId)) + .OrderBy(x => x.ApplicationId) + .OrderBy(x => x.CreateTime) + .ToListAsync(); + } + + // 获取门店信息 + var storeIds = applications.Where(x => !string.IsNullOrEmpty(x.ApplicationStoreId)) + .Select(x => x.ApplicationStoreId) + .Distinct() + .ToList(); + var stores = new Dictionary(); + if (storeIds.Any()) + { + var storeList = await _db.Queryable() + .Where(x => storeIds.Contains(x.Id)) + .Select(x => new { x.Id, x.Dm }) + .ToListAsync(); + stores = storeList.ToDictionary(x => x.Id, x => x.Dm ?? ""); + } + + // 组装导出数据(包含报销申请和购买记录明细) + var exportData = new List(); + foreach (var app in applications) + { + var appPurchaseRecords = purchaseRecords.Where(x => x.ApplicationId == app.Id).ToList(); + + if (appPurchaseRecords.Any()) + { + // 每个购买记录作为一行 + foreach (var pr in appPurchaseRecords) + { + exportData.Add(new ReimbursementDetailExportOutput + { + applicationId = app.Id, + applicationUserName = app.ApplicationUserName, + applicationStoreId = app.ApplicationStoreId, + applicationStoreName = !string.IsNullOrEmpty(app.ApplicationStoreId) && stores.ContainsKey(app.ApplicationStoreId) + ? stores[app.ApplicationStoreId] + : "", + applicationTime = app.ApplicationTime, + applicationAmount = !string.IsNullOrEmpty(app.Amount) ? decimal.Parse(app.Amount) : 0m, + purchaseRecordId = pr.Id, + reimbursementCategoryId = pr.ReimbursementCategoryId, + reimbursementCategoryName = pr.ReimbursementCategoryName, + unitPrice = pr.UnitPrice, + quantity = pr.Quantity ?? 0, + amount = pr.Amount, + memo = pr.Memo, + purchaseTime = pr.PurchaseTime + }); + } + } + else + { + // 如果没有购买记录,至少导出报销申请基本信息 + exportData.Add(new ReimbursementDetailExportOutput + { + applicationId = app.Id, + applicationUserName = app.ApplicationUserName, + applicationStoreId = app.ApplicationStoreId, + applicationStoreName = !string.IsNullOrEmpty(app.ApplicationStoreId) && stores.ContainsKey(app.ApplicationStoreId) + ? stores[app.ApplicationStoreId] + : "", + applicationTime = app.ApplicationTime, + applicationAmount = !string.IsNullOrEmpty(app.Amount) ? decimal.Parse(app.Amount) : 0m, + purchaseRecordId = "", + reimbursementCategoryId = "", + reimbursementCategoryName = "", + unitPrice = 0m, + quantity = 0, + amount = 0m, + memo = "", + purchaseTime = null + }); + } + } + + // 导出Excel + List 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(); + ExcelConfig excelconfig = new ExcelConfig(); + excelconfig.FileName = $"报销表明细_{queryYear}年{queryMonth}月.xls"; + excelconfig.HeadFont = "微软雅黑"; + excelconfig.HeadPoint = 10; + excelconfig.IsAllSizeColumn = true; + excelconfig.ColumnModel = new List(); + foreach (var param in paramList) + { + excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = param.field, ExcelColumn = param.value }); + } + var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName; + ExcelExportHelper.Export(exportData, excelconfig, addPath); + var fileName = _userManager.UserId + "|" + addPath + "|xls"; + var output = new + { + name = excelconfig.FileName, + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") + }; + return output; + } + catch (Exception ex) + { + throw NCCException.Oh($"导出失败:{ex.Message}"); + } + } + } + + /// + /// 报销表明细导出输出 + /// + public class ReimbursementDetailExportOutput + { + /// + /// 报销申请ID + /// + public string applicationId { get; set; } + + /// + /// 申请人姓名 + /// + public string applicationUserName { get; set; } + + /// + /// 门店ID + /// + public string applicationStoreId { get; set; } + + /// + /// 门店名称 + /// + public string applicationStoreName { get; set; } + + /// + /// 申请时间 + /// + public DateTime? applicationTime { get; set; } + + /// + /// 申请金额 + /// + public decimal applicationAmount { get; set; } + + /// + /// 购买记录ID + /// + public string purchaseRecordId { get; set; } + + /// + /// 支出分类ID + /// + public string reimbursementCategoryId { get; set; } + + /// + /// 支出分类名称 + /// + public string reimbursementCategoryName { get; set; } + + /// + /// 单价 + /// + public decimal unitPrice { get; set; } + + /// + /// 数量 + /// + public int quantity { get; set; } + + /// + /// 金额 + /// + public decimal amount { get; set; } + + /// + /// 备注说明 + /// + public string memo { get; set; } + + /// + /// 购买时间 + /// + public DateTime? purchaseTime { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs index 0291cb9..62a415c 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs @@ -4070,12 +4070,14 @@ namespace NCC.Extend.LqStatistics var whereClause = whereConditions.Any() ? "WHERE " + string.Join(" AND ", whereConditions) : ""; // 使用子查询优化性能,避免复杂的JOIN和GROUP BY + // 注意:邀约、预约、消耗、开单表存储的是会员ID(F_MemberId),不是线索池ID(F_Id) + // 需要通过线索池的F_MemberId去关联这些表 var sql = $@" SELECT tk.F_Id as LeadCustomerId, tk.F_CustomerName as CustomerName, tk.F_ExpansionTime as ExpansionTime, - -- 是否邀约:通过拓客编号关联 + -- 是否邀约:通过会员ID关联(邀约表的yykh字段存储的是会员ID) CASE WHEN yaoy_stats.has_invite = 1 THEN '是' ELSE '否' END as HasInvite, -- 是否预约:通过邀约ID关联(只统计通过邀约产生的预约) CASE WHEN yy_stats.has_appointment = 1 THEN '是' ELSE '否' END as HasAppointment, @@ -4096,37 +4098,41 @@ namespace NCC.Extend.LqStatistics -- 实际开单记录数(不管是否通过预约产生) COALESCE(kd_actual.count, 0) as ActualBillingCount FROM lq_tkjlb tk - -- 邀约统计子查询 + -- 邀约统计子查询(通过会员ID关联:邀约表的yykh字段存储的是会员ID) LEFT JOIN ( SELECT - yaoy.tkbh as tk_id, + yaoy.yykh as member_id, 1 as has_invite FROM lq_yaoyjl yaoy - GROUP BY yaoy.tkbh - ) yaoy_stats ON yaoy_stats.tk_id = tk.F_Id + WHERE yaoy.yykh IS NOT NULL + GROUP BY yaoy.yykh + ) yaoy_stats ON yaoy_stats.member_id = tk.F_MemberId -- 预约统计子查询(只统计通过邀约产生的预约) + -- 通过会员ID关联:线索池 -> 邀约(通过会员ID) -> 预约(通过邀约ID,且会员ID匹配) LEFT JOIN ( SELECT tk_inner.F_MemberId as member_id, 1 as has_appointment, MAX(yy.F_NoDealRemark) as no_billing_reason FROM lq_tkjlb tk_inner - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId GROUP BY tk_inner.F_MemberId ) yy_stats ON yy_stats.member_id = tk.F_MemberId -- 消耗统计子查询(只统计通过预约产生的耗卡) + -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 消耗(通过预约ID,且会员ID匹配) LEFT JOIN ( SELECT tk_inner.F_MemberId as member_id, 1 as has_consume FROM lq_tkjlb tk_inner - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id - INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId + 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 GROUP BY tk_inner.F_MemberId ) xh_stats ON xh_stats.member_id = tk.F_MemberId -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项) + -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配) LEFT JOIN ( SELECT tk_inner.F_MemberId as member_id, @@ -4134,9 +4140,9 @@ namespace NCC.Extend.LqStatistics SUM(kd.zdyj) as billing_amount, GROUP_CONCAT(DISTINCT kdpx.pxmc SEPARATOR '、') as billing_items FROM lq_tkjlb tk_inner - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id - INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.F_IsEffective = 1 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId + 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 LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1 GROUP BY tk_inner.F_MemberId ) kd_stats ON kd_stats.member_id = tk.F_MemberId @@ -4186,40 +4192,69 @@ namespace NCC.Extend.LqStatistics var result = await _db.Ado.SqlQueryAsync(sql, parameters); // 生成问题分析说明 + // 完整链路1:邀约 -> 预约 -> 开单 + // 完整链路2:邀约 -> 预约 -> 消耗 foreach (var item in result) { var analysisList = new List(); + var completeChains = new List(); + + // 判断是否形成完整链路 + if (item.HasInvite == "是" && item.HasAppointment == "是") + { + if (item.HasBilling == "是") + { + completeChains.Add("邀约->预约->开单"); + } + if (item.HasConsume == "是") + { + completeChains.Add("邀约->预约->消耗"); + } + } + // 如果有完整链路,先说明链路完整 + if (completeChains.Count > 0) + { + analysisList.Add($"✓ 链路完整:{string.Join("、", completeChains)}"); + } + + // 有预约记录,但未通过邀约产生 if (item.HasInvite == "否" && item.HasAppointment == "否" && item.ActualAppointmentCount > 0) { analysisList.Add($"有{item.ActualAppointmentCount}条预约记录,但未通过邀约产生(F_InviteId为null)"); } + // 有消耗记录,但未通过完整链路产生(邀约->预约->消耗) if (item.HasAppointment == "否" && item.HasConsume == "否" && item.ActualConsumeCount > 0) { - analysisList.Add($"有{item.ActualConsumeCount}条消耗记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)"); + analysisList.Add($"有{item.ActualConsumeCount}条消耗记录,但未通过完整链路产生(邀约->预约->消耗)"); } + // 有开单记录,但未通过完整链路产生(邀约->预约->开单) if (item.HasAppointment == "否" && item.HasBilling == "否" && item.ActualBillingCount > 0) { - analysisList.Add($"有{item.ActualBillingCount}条开单记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)"); + analysisList.Add($"有{item.ActualBillingCount}条开单记录,但未通过完整链路产生(邀约->预约->开单)"); } + // 有邀约记录,有预约记录,但预约记录的F_InviteId未关联到邀约记录 if (item.HasInvite == "是" && item.HasAppointment == "否" && item.ActualAppointmentCount > 0) { analysisList.Add($"有邀约记录,有{item.ActualAppointmentCount}条预约记录,但预约记录的F_InviteId未关联到邀约记录"); } + // 有预约记录(通过邀约产生),有消耗记录,但消耗记录的F_AppointmentId未关联到预约记录 if (item.HasAppointment == "是" && item.HasConsume == "否" && item.ActualConsumeCount > 0) { - analysisList.Add($"有预约记录,有{item.ActualConsumeCount}条消耗记录,但消耗记录的F_AppointmentId未关联到预约记录"); + analysisList.Add($"有预约记录(通过邀约产生),有{item.ActualConsumeCount}条消耗记录,但消耗记录的F_AppointmentId未关联到预约记录,未形成完整链路(邀约->预约->消耗)"); } + // 有预约记录(通过邀约产生),有开单记录,但开单记录的F_AppointmentId未关联到预约记录 if (item.HasAppointment == "是" && item.HasBilling == "否" && item.ActualBillingCount > 0) { - analysisList.Add($"有预约记录,有{item.ActualBillingCount}条开单记录,但开单记录的F_AppointmentId未关联到预约记录"); + analysisList.Add($"有预约记录(通过邀约产生),有{item.ActualBillingCount}条开单记录,但开单记录的F_AppointmentId未关联到预约记录,未形成完整链路(邀约->预约->开单)"); } + // 生成最终分析说明 if (analysisList.Count == 0) { item.Analysis = "数据正常,符合业务链路:拓客 -> 邀约 -> 预约 -> 开单/消耗"; @@ -4394,37 +4429,41 @@ namespace NCC.Extend.LqStatistics -- 实际开单记录数(不管是否通过预约产生) COALESCE(kd_actual.count, 0) as ActualBillingCount FROM lq_tkjlb tk - -- 邀约统计子查询 + -- 邀约统计子查询(通过会员ID关联:邀约表的yykh字段存储的是会员ID) LEFT JOIN ( SELECT - yaoy.tkbh as tk_id, + yaoy.yykh as member_id, 1 as has_invite FROM lq_yaoyjl yaoy - GROUP BY yaoy.tkbh - ) yaoy_stats ON yaoy_stats.tk_id = tk.F_Id + WHERE yaoy.yykh IS NOT NULL + GROUP BY yaoy.yykh + ) yaoy_stats ON yaoy_stats.member_id = tk.F_MemberId -- 预约统计子查询(只统计通过邀约产生的预约) + -- 通过会员ID关联:线索池 -> 邀约(通过会员ID) -> 预约(通过邀约ID,且会员ID匹配) LEFT JOIN ( SELECT tk_inner.F_MemberId as member_id, 1 as has_appointment, MAX(yy.F_NoDealRemark) as no_billing_reason FROM lq_tkjlb tk_inner - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId GROUP BY tk_inner.F_MemberId ) yy_stats ON yy_stats.member_id = tk.F_MemberId -- 消耗统计子查询(只统计通过预约产生的耗卡) + -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 消耗(通过预约ID,且会员ID匹配) LEFT JOIN ( SELECT tk_inner.F_MemberId as member_id, 1 as has_consume FROM lq_tkjlb tk_inner - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id - INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId + 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 GROUP BY tk_inner.F_MemberId ) xh_stats ON xh_stats.member_id = tk.F_MemberId -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项) + -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配) LEFT JOIN ( SELECT tk_inner.F_MemberId as member_id, @@ -4432,9 +4471,9 @@ namespace NCC.Extend.LqStatistics SUM(kd.zdyj) as billing_amount, GROUP_CONCAT(DISTINCT kdpx.pxmc SEPARATOR '、') as billing_items FROM lq_tkjlb tk_inner - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id - INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.F_IsEffective = 1 + INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId + 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 LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1 GROUP BY tk_inner.F_MemberId ) kd_stats ON kd_stats.member_id = tk.F_MemberId diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreExpenseService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreExpenseService.cs new file mode 100644 index 0000000..e9228df --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreExpenseService.cs @@ -0,0 +1,564 @@ +using NCC.Common.Core.Manager; +using NCC.Common.Enum; +using NCC.Common.Extension; +using NCC.Common.Filter; +using NCC.Dependency; +using NCC.DynamicApiController; +using NCC.FriendlyException; +using NCC.Extend.Interfaces.LqStoreExpense; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NCC.Extend.Entitys.lq_store_expense; +using NCC.Extend.Entitys.Dto.LqStoreExpense; +using NCC.Extend.Entitys.lq_mdxx; +using Yitter.IdGenerator; +using NCC.Common.Helper; +using NCC.Common.Model.NPOI; +using NCC.Common.Configuration; +using NCC.DataEncryption; +using NCC.ClayObject; +using NCC.Common.Const; +using NCC.JsonSerialization; +using NCC.Extend.Entitys.Enum; +using Microsoft.AspNetCore.Http; +using System.IO; +using System.Data; + +namespace NCC.Extend.LqStoreExpense +{ + /// + /// 店内支出表服务 + /// + [ApiDescriptionSettings(Tag = "Extend", Name = "LqStoreExpense", Order = 200)] + [Route("api/Extend/[controller]")] + public class LqStoreExpenseService : ILqStoreExpenseService, IDynamicApiController, ITransient + { + private readonly ISqlSugarRepository _repository; + private readonly SqlSugarScope _db; + private readonly IUserManager _userManager; + + /// + /// 初始化一个类型的新实例 + /// + public LqStoreExpenseService( + ISqlSugarRepository repository, + IUserManager userManager) + { + _repository = repository; + _db = _repository.Context; + _userManager = userManager; + } + + /// + /// 获取店内支出表详情 + /// + /// 主键ID + /// + [HttpGet("{id}")] + public async Task GetInfo(string id) + { + var entity = await _db.Queryable() + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + var output = entity.Adapt(); + if (!string.IsNullOrEmpty(entity.Attachment)) + { + output.attachment = entity.Attachment.ToObject>(); + } + return output; + } + + /// + /// 获取店内支出表列表 + /// + /// 请求参数 + /// + [HttpGet("")] + public async Task GetList([FromQuery] LqStoreExpenseListQueryInput input) + { + var sidx = input.sidx ?? "ExpenseDate"; + var sortType = input.sort?.ToLower() == "asc" ? OrderByType.Asc : OrderByType.Desc; + List queryExpenseDate = input.expenseDateStart != null && input.expenseDateEnd != null + ? new List { input.expenseDateStart, input.expenseDateEnd } + : null; + DateTime? startExpenseDate = queryExpenseDate != null ? Ext.GetDateTime(queryExpenseDate.First()) : null; + DateTime? endExpenseDate = queryExpenseDate != null ? Ext.GetDateTime(queryExpenseDate.Last()) : null; + + var query = _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(!string.IsNullOrEmpty(input.storeId), x => x.StoreId == input.storeId) + .WhereIF(!string.IsNullOrEmpty(input.storeName), x => x.StoreName.Contains(input.storeName)) + .WhereIF(!string.IsNullOrEmpty(input.expenseCategoryId), x => x.ExpenseCategoryId == input.expenseCategoryId) + .WhereIF(queryExpenseDate != null, x => x.ExpenseDate >= new DateTime(startExpenseDate.ToDate().Year, startExpenseDate.ToDate().Month, startExpenseDate.ToDate().Day, 0, 0, 0)) + .WhereIF(queryExpenseDate != null, x => x.ExpenseDate <= new DateTime(endExpenseDate.ToDate().Year, endExpenseDate.ToDate().Month, endExpenseDate.ToDate().Day, 23, 59, 59)); + + // 处理排序 + switch (sidx.ToLower()) + { + case "id": + query = query.OrderBy(x => x.Id, sortType); + break; + case "expensedate": + query = query.OrderBy(x => x.ExpenseDate, sortType); + break; + case "storename": + query = query.OrderBy(x => x.StoreName, sortType); + break; + case "amount": + query = query.OrderBy(x => x.Amount, sortType); + break; + case "createtime": + query = query.OrderBy(x => x.CreateTime, sortType); + break; + default: + query = query.OrderBy(x => x.ExpenseDate, OrderByType.Desc); + break; + } + + var total = await query.CountAsync(); + var list = await query.ToPageListAsync(input.currentPage, input.pageSize); + + var result = list.Select(x => new LqStoreExpenseListOutput + { + id = x.Id, + storeId = x.StoreId, + storeName = x.StoreName, + expenseCategoryId = x.ExpenseCategoryId, + expenseCategoryName = x.ExpenseCategoryName, + expenseDate = x.ExpenseDate, + unitPrice = x.UnitPrice, + quantity = x.Quantity, + amount = x.Amount, + memo = x.Memo, + relatedReimbursementId = x.RelatedReimbursementId, + relatedPurchaseRecordId = x.RelatedPurchaseRecordId, + createUser = x.CreateUser, + createTime = x.CreateTime, + updateUser = x.UpdateUser, + updateTime = x.UpdateTime + }).ToList(); + + return PageResult.SqlSugarPageResult( + new SqlSugarPagedList + { + list = result, + pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total } + }); + } + + /// + /// 获取店内支出表无分页列表 + /// + /// 请求参数 + /// + [HttpGet("Actions/GetNoPagingList")] + public async Task> GetNoPagingList([FromQuery] LqStoreExpenseListQueryInput input) + { + var sidx = input.sidx ?? "ExpenseDate"; + var sortType = input.sort?.ToLower() == "asc" ? OrderByType.Asc : OrderByType.Desc; + List queryExpenseDate = input.expenseDateStart != null && input.expenseDateEnd != null + ? new List { input.expenseDateStart, input.expenseDateEnd } + : null; + DateTime? startExpenseDate = queryExpenseDate != null ? Ext.GetDateTime(queryExpenseDate.First()) : null; + DateTime? endExpenseDate = queryExpenseDate != null ? Ext.GetDateTime(queryExpenseDate.Last()) : null; + + var query = _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(!string.IsNullOrEmpty(input.storeId), x => x.StoreId == input.storeId) + .WhereIF(!string.IsNullOrEmpty(input.storeName), x => x.StoreName.Contains(input.storeName)) + .WhereIF(!string.IsNullOrEmpty(input.expenseCategoryId), x => x.ExpenseCategoryId == input.expenseCategoryId) + .WhereIF(queryExpenseDate != null, x => x.ExpenseDate >= new DateTime(startExpenseDate.ToDate().Year, startExpenseDate.ToDate().Month, startExpenseDate.ToDate().Day, 0, 0, 0)) + .WhereIF(queryExpenseDate != null, x => x.ExpenseDate <= new DateTime(endExpenseDate.ToDate().Year, endExpenseDate.ToDate().Month, endExpenseDate.ToDate().Day, 23, 59, 59)); + + // 处理排序 + switch (sidx.ToLower()) + { + case "id": + query = query.OrderBy(x => x.Id, sortType); + break; + case "expensedate": + query = query.OrderBy(x => x.ExpenseDate, sortType); + break; + case "storename": + query = query.OrderBy(x => x.StoreName, sortType); + break; + case "amount": + query = query.OrderBy(x => x.Amount, sortType); + break; + case "createtime": + query = query.OrderBy(x => x.CreateTime, sortType); + break; + default: + query = query.OrderBy(x => x.ExpenseDate, OrderByType.Desc); + break; + } + + var list = await query + .Select(x => new LqStoreExpenseListOutput + { + id = x.Id, + storeId = x.StoreId, + storeName = x.StoreName, + expenseCategoryId = x.ExpenseCategoryId, + expenseCategoryName = x.ExpenseCategoryName, + expenseDate = x.ExpenseDate, + unitPrice = x.UnitPrice, + quantity = x.Quantity, + amount = x.Amount, + memo = x.Memo, + relatedReimbursementId = x.RelatedReimbursementId, + relatedPurchaseRecordId = x.RelatedPurchaseRecordId, + createUser = x.CreateUser, + createTime = x.CreateTime, + updateUser = x.UpdateUser, + updateTime = x.UpdateTime + }) + .ToListAsync(); + return list; + } + + /// + /// 创建店内支出表 + /// + /// 参数 + /// + [HttpPost("")] + public async Task Create([FromBody] LqStoreExpenseCrInput input) + { + var userInfo = await _userManager.GetUserInfo(); + var entity = input.Adapt(); + entity.Id = YitIdHelper.NextId().ToString(); + entity.IsEffective = StatusEnum.有效.GetHashCode(); + entity.CreateUser = _userManager.UserId; + entity.CreateTime = DateTime.Now; + + if (input.attachment != null && input.attachment.Any()) + { + entity.Attachment = input.attachment.ToJson(); + } + + // 如果未提供门店名称,根据门店ID查询 + if (string.IsNullOrEmpty(entity.StoreName) && !string.IsNullOrEmpty(entity.StoreId)) + { + var store = await _db.Queryable() + .Where(x => x.Id == entity.StoreId) + .Select(x => x.Dm) + .FirstAsync(); + entity.StoreName = store; + } + + var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); + } + + /// + /// 更新店内支出表 + /// + /// 主键 + /// 参数 + /// + [HttpPut("{id}")] + public async Task Update(string id, [FromBody] LqStoreExpenseUpInput input) + { + var entity = await _db.Queryable() + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + + entity.StoreId = input.storeId; + entity.StoreName = input.storeName; + entity.ExpenseCategoryId = input.expenseCategoryId; + entity.ExpenseCategoryName = input.expenseCategoryName; + entity.ExpenseDate = input.expenseDate; + entity.UnitPrice = input.unitPrice; + entity.Quantity = input.quantity; + entity.Amount = input.amount; + entity.Memo = input.memo; + entity.RelatedReimbursementId = input.relatedReimbursementId; + entity.RelatedPurchaseRecordId = input.relatedPurchaseRecordId; + entity.UpdateUser = _userManager.UserId; + entity.UpdateTime = DateTime.Now; + + if (input.attachment != null && input.attachment.Any()) + { + entity.Attachment = input.attachment.ToJson(); + } + + // 如果未提供门店名称,根据门店ID查询 + if (string.IsNullOrEmpty(entity.StoreName) && !string.IsNullOrEmpty(entity.StoreId)) + { + var store = await _db.Queryable() + .Where(x => x.Id == entity.StoreId) + .Select(x => x.Dm) + .FirstAsync(); + entity.StoreName = store; + } + + var isOk = await _db.Updateable(entity).ExecuteCommandAsync(); + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001); + } + + /// + /// 删除店内支出表 + /// + /// 主键 + /// + [HttpDelete("{id}")] + public async Task Delete(string id) + { + var entity = await _db.Queryable() + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); + + // 逻辑删除 + entity.IsEffective = StatusEnum.无效.GetHashCode(); + entity.UpdateUser = _userManager.UserId; + entity.UpdateTime = DateTime.Now; + + var isOk = await _db.Updateable(entity).ExecuteCommandAsync(); + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1002); + } + + /// + /// 导出店内支出表 + /// + /// 请求参数 + /// + [HttpGet("Actions/Export")] + public async Task Export([FromQuery] LqStoreExpenseListQueryInput input) + { + var userInfo = await _userManager.GetUserInfo(); + var exportData = new List(); + if (input.dataType == 0) + { + var data = Clay.Object(await this.GetList(input)); + exportData = data.Solidify>().list; + } + else + { + exportData = await this.GetNoPagingList(input); + } + List 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(); + ExcelConfig excelconfig = new ExcelConfig(); + excelconfig.FileName = "店内支出表.xls"; + excelconfig.HeadFont = "微软雅黑"; + excelconfig.HeadPoint = 10; + excelconfig.IsAllSizeColumn = true; + excelconfig.ColumnModel = new List(); + List selectKeyList = !string.IsNullOrEmpty(input.selectKey) ? input.selectKey.Split(',').ToList() : paramList.Select(p => p.field).ToList(); + foreach (var item in selectKeyList) + { + var isExist = paramList.Find(p => p.field == item); + if (isExist != null) + { + excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = isExist.field, ExcelColumn = isExist.value }); + } + } + var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName; + ExcelExportHelper.Export(exportData, excelconfig, addPath); + var fileName = _userManager.UserId + "|" + addPath + "|xls"; + var output = new + { + name = excelconfig.FileName, + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") + }; + return output; + } + + /// + /// 导入店内支出数据 + /// + /// + /// 从Excel文件导入店内支出数据 + /// + /// Excel格式要求: + /// 第一行为标题行:门店ID、门店名称、支出分类ID、支出分类名称、支出日期、单价、数量、金额、备注说明、关联报销申请ID、关联购买记录ID + /// 从第二行开始为数据行 + /// + /// 示例请求: + /// POST /api/Extend/LqStoreExpense/Actions/Import + /// Content-Type: multipart/form-data + /// + /// Excel文件 + /// 导入结果 + /// 导入成功 + /// 文件格式错误或数据验证失败 + [HttpPost("Actions/Import")] + public async Task Import(IFormFile file) + { + try + { + if (file == null || file.Length == 0) + { + throw NCCException.Oh("请选择要上传的Excel文件"); + } + + // 检查文件格式 + var allowedExtensions = new[] { ".xlsx", ".xls" }; + var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (!allowedExtensions.Contains(fileExtension)) + { + throw NCCException.Oh("只支持.xlsx和.xls格式的Excel文件"); + } + + var successCount = 0; + var failCount = 0; + var failMessages = new List(); + + // 保存临时文件 + var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName)); + try + { + using (var stream = new FileStream(tempFilePath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + + // 使用ExcelImportHelper读取Excel文件(第一行为标题行) + var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0); + + if (dataTable.Rows.Count <= 1) + { + throw NCCException.Oh("Excel文件中没有数据行(至少需要标题行和一行数据)"); + } + + // 从第1行开始读取数据(跳过标题行) + for (int i = 1; i < dataTable.Rows.Count; i++) + { + try + { + var row = dataTable.Rows[i]; + var storeId = row[0]?.ToString()?.Trim(); + var storeName = row[1]?.ToString()?.Trim(); + var expenseCategoryId = row[2]?.ToString()?.Trim(); + var expenseCategoryName = row[3]?.ToString()?.Trim(); + var expenseDateText = row[4]?.ToString()?.Trim(); + var unitPriceText = row[5]?.ToString()?.Trim(); + var quantityText = row[6]?.ToString()?.Trim(); + var amountText = row[7]?.ToString()?.Trim(); + var memo = row[8]?.ToString()?.Trim(); + var relatedReimbursementId = row[9]?.ToString()?.Trim(); + var relatedPurchaseRecordId = row[10]?.ToString()?.Trim(); + + // 跳过空行 + if (string.IsNullOrEmpty(storeId) && string.IsNullOrEmpty(storeName)) + { + continue; + } + + // 验证必填字段 + if (string.IsNullOrEmpty(storeId)) + { + failMessages.Add($"第{i + 1}行:门店ID不能为空"); + failCount++; + continue; + } + + if (string.IsNullOrEmpty(expenseDateText) || !DateTime.TryParse(expenseDateText, out DateTime expenseDate)) + { + failMessages.Add($"第{i + 1}行:支出日期格式错误(应为日期格式,如:2025-01-15)"); + failCount++; + continue; + } + + if (string.IsNullOrEmpty(amountText) || !decimal.TryParse(amountText, out decimal amount)) + { + failMessages.Add($"第{i + 1}行:金额格式错误(应为数字)"); + failCount++; + continue; + } + + // 解析可选字段 + decimal unitPrice = 0m; + if (!string.IsNullOrEmpty(unitPriceText) && decimal.TryParse(unitPriceText, out decimal up)) + { + unitPrice = up; + } + + int quantity = 0; + if (!string.IsNullOrEmpty(quantityText) && int.TryParse(quantityText, out int qty)) + { + quantity = qty; + } + + // 如果未提供门店名称,根据门店ID查询 + if (string.IsNullOrEmpty(storeName)) + { + var store = await _db.Queryable() + .Where(x => x.Id == storeId) + .Select(x => x.Dm) + .FirstAsync(); + storeName = store ?? ""; + } + + // 创建记录 + var entity = new LqStoreExpenseEntity + { + Id = YitIdHelper.NextId().ToString(), + StoreId = storeId, + StoreName = storeName, + ExpenseCategoryId = expenseCategoryId, + ExpenseCategoryName = expenseCategoryName, + ExpenseDate = expenseDate, + UnitPrice = unitPrice, + Quantity = quantity, + Amount = amount, + Memo = memo, + RelatedReimbursementId = relatedReimbursementId, + RelatedPurchaseRecordId = relatedPurchaseRecordId, + IsEffective = StatusEnum.有效.GetHashCode(), + CreateUser = _userManager.UserId, + CreateTime = DateTime.Now + }; + + var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); + if (isOk > 0) + { + successCount++; + } + else + { + failMessages.Add($"第{i + 1}行:保存失败"); + failCount++; + } + } + catch (Exception ex) + { + failMessages.Add($"第{i + 1}行:{ex.Message}"); + failCount++; + } + } + } + finally + { + // 清理临时文件 + if (File.Exists(tempFilePath)) + { + File.Delete(tempFilePath); + } + } + + return new + { + success = true, + message = $"导入完成:成功{successCount}条,失败{failCount}条", + successCount = successCount, + failCount = failCount, + failMessages = failMessages + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"导入失败:{ex.Message}"); + } + } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs new file mode 100644 index 0000000..e09be64 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreManagerSalaryService.cs @@ -0,0 +1,610 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using NCC.Common.Filter; +using NCC.Common.Helper; +using NCC.Dependency; +using NCC.DynamicApiController; +using NCC.Extend.Entitys.Dto.LqStoreManagerSalary; +using NCC.Extend.Entitys.Enum; +using NCC.Extend.Entitys.lq_attendance_summary; +using NCC.Extend.Entitys.lq_cooperation_cost; +using NCC.Extend.Entitys.lq_hytk_hytk; +using NCC.Extend.Entitys.lq_inventory_usage; +using NCC.Extend.Entitys.lq_kd_kdjlb; +using NCC.Extend.Entitys.lq_laundry_flow; +using NCC.Extend.Entitys.lq_md_target; +using NCC.Extend.Entitys.lq_md_xdbhsj; +using NCC.Extend.Entitys.lq_mdxx; +using NCC.Extend.Entitys.lq_store_expense; +using NCC.Extend.Entitys.lq_store_manager_salary_statistics; +using NCC.Extend.Entitys.lq_xh_hyhk; +using NCC.Extend.Entitys.lq_xh_jksyj; +using NCC.System.Entitys.Permission; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Yitter.IdGenerator; + +namespace NCC.Extend +{ + /// + /// 店长薪酬服务 + /// + [ApiDescriptionSettings(Tag = "店长薪酬服务", Name = "LqStoreManagerSalary", Order = 303)] + [Route("api/Extend/[controller]")] + public class LqStoreManagerSalaryService : IDynamicApiController, ITransient + { + private readonly ISqlSugarClient _db; + + /// + /// 初始化一个类型的新实例 + /// + public LqStoreManagerSalaryService(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 获取店长工资列表 + /// + /// 查询参数 + /// 店长工资分页列表 + [HttpGet("store-manager")] + public async Task GetStoreManagerSalaryList([FromQuery] StoreManagerSalaryInput input) + { + var monthStr = $"{input.Year}{input.Month:D2}"; + + // 1. 检查当月是否已生成工资数据 + var exists = await _db.Queryable() + .AnyAsync(x => x.StatisticsMonth == monthStr); + + // 2. 如果没有数据,则进行计算 + if (!exists) + { + await CalculateStoreManagerSalary(input.Year, input.Month); + } + + // 3. 查询数据 + var query = _db.Queryable() + .Where(x => x.StatisticsMonth == monthStr); + + if (!string.IsNullOrEmpty(input.StoreId)) + { + query = query.Where(x => x.StoreId == input.StoreId); + } + + if (!string.IsNullOrEmpty(input.Keyword)) + { + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword)); + } + + var list = await query.Select(x => new StoreManagerSalaryOutput + { + Id = x.Id, + StoreName = x.StoreName, + EmployeeName = x.EmployeeName, + Position = x.Position, + StoreTotalPerformance = x.StoreTotalPerformance, + StoreBillingPerformance = x.StoreBillingPerformance, + StoreRefundPerformance = x.StoreRefundPerformance, + StoreLifeline = x.StoreLifeline, + PerformanceCompletionRate = x.PerformanceCompletionRate, + PerformanceReached = x.PerformanceReached, + HeadCountReached = x.HeadCountReached, + ConsumeReached = x.ConsumeReached, + AssessmentDeduction = x.AssessmentDeduction, + UnreachedIndicatorCount = x.UnreachedIndicatorCount, + HeadCount = x.HeadCount, + TargetHeadCount = x.TargetHeadCount, + StoreConsume = x.StoreConsume, + TargetConsume = x.TargetConsume, + SalesPerformance = x.SalesPerformance, + ProductMaterial = x.ProductMaterial, + CooperationCost = x.CooperationCost, + StoreExpense = x.StoreExpense, + LaundryCost = x.LaundryCost, + GrossProfit = x.GrossProfit, + CommissionRate = x.CommissionRate, + CommissionAmount = x.CommissionAmount, + BaseSalary = x.BaseSalary, + FlagshipStoreDeduction = x.FlagshipStoreDeduction, + ActualBaseSalary = x.ActualBaseSalary, + WorkingDays = x.WorkingDays, + LeaveDays = x.LeaveDays, + GrossSalary = x.GrossSalary, + ActualSalary = x.ActualSalary, + TotalDeduction = x.TotalDeduction, + TotalSubsidy = x.TotalSubsidy, + Bonus = x.Bonus, + MonthlyPaymentStatus = x.MonthlyPaymentStatus, + PaidAmount = x.PaidAmount, + PendingAmount = x.PendingAmount, + IsLocked = x.IsLocked, + StoreType = x.StoreType, + StoreCategory = x.StoreCategory, + IsNewStore = x.IsNewStore, + NewStoreProtectionStage = x.NewStoreProtectionStage, + UpdateTime = x.UpdateTime + }) + .ToPagedListAsync(input.currentPage, input.pageSize); + + return PageResult.SqlSugarPageResult(list); + } + + /// + /// 计算店长工资 + /// + /// 年份 + /// 月份 + /// + [HttpPost("calculate/store-manager")] + public async Task CalculateStoreManagerSalary(int year, int month) + { + var startDate = new DateTime(year, month, 1); + var endDate = startDate.AddMonths(1).AddDays(-1); + var monthStr = $"{year}{month:D2}"; + + // 1. 获取基础数据 + + // 1.1 获取店长员工列表(从BASE_USER表,岗位为"店长") + var storeManagerUserList = await _db.Queryable() + .Where(x => x.Gw == "店长" && x.DeleteMark == null && x.EnabledMark == 1) + .Select(x => new { x.Id, x.RealName, x.Mdid, x.Gw }) + .ToListAsync(); + + if (!storeManagerUserList.Any()) + { + // 如果没有店长员工,直接返回 + return; + } + + // 1.2 门店信息 (lq_mdxx) + var storeList = await _db.Queryable().ToListAsync(); + var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x); + + // 1.3 门店目标信息 (lq_md_target) + var storeTargets = await _db.Queryable() + .Where(x => x.Month == monthStr) + .ToListAsync(); + var storeTargetDict = storeTargets.Where(x => !string.IsNullOrEmpty(x.StoreId)) + .ToDictionary(x => x.StoreId, x => x); + + // 1.4 门店新店保护信息 (lq_md_xdbhsj) + var newStoreProtectionList = await _db.Queryable() + .Where(x => x.Sfqy == 1) + .ToListAsync(); + var newStoreProtectionDict = newStoreProtectionList + .Where(x => x.Bhkssj <= startDate && x.Bhjssj >= startDate) + .GroupBy(x => x.Mdid) + .ToDictionary(g => g.Key, g => g.First()); + + // 1.5 门店总业绩计算 (开单实付 - 退卡金额) + // 开单实付 + var storeBillingList = await _db.Queryable() + .Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1) + .Select(x => new { x.Djmd, x.Sfyj }) + .ToListAsync(); + var storeBillingDict = storeBillingList + .Where(x => !string.IsNullOrEmpty(x.Djmd)) + .GroupBy(x => x.Djmd) + .ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj)); + + // 退卡金额(使用F_ActualRefundAmount,如果没有则使用tkje) + var storeRefundList = await _db.Queryable() + .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1) + .Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje }) + .ToListAsync(); + var storeRefundDict = storeRefundList + .Where(x => !string.IsNullOrEmpty(x.Md)) + .GroupBy(x => x.Md) + .ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0)); + + // 1.6 门店消耗金额统计(按门店统计当月总消耗) + var storeConsumeSql = $@" + SELECT + hyhk.md as StoreId, + COALESCE(SUM(jksyj.jksyj), 0) as ConsumeAmount + FROM lq_xh_jksyj jksyj + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id + WHERE jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND hyhk.hksj >= @startDate + AND hyhk.hksj <= @endDate + GROUP BY hyhk.md"; + + var storeConsumeData = await _db.Ado.SqlQueryAsync(storeConsumeSql, new { startDate, endDate = endDate.AddDays(1) }); + var storeConsumeDict = storeConsumeData + .Where(x => x.StoreId != null) + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ConsumeAmount ?? 0)); + + // 1.7 进店消耗人数统计(有消费金额的,按门店按月去重客户数) + var headcountSql = $@" + SELECT + hyhk.md as StoreId, + COUNT(DISTINCT hyhk.hy) as HeadCount + FROM lq_xh_hyhk hyhk + WHERE hyhk.F_IsEffective = 1 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @monthStr + AND EXISTS ( + SELECT 1 + FROM lq_xh_jksyj jksyj + WHERE jksyj.glkdbh = hyhk.F_Id + AND jksyj.F_IsEffective = 1 + AND jksyj.jksyj > 0 + ) + GROUP BY hyhk.md"; + + var headcountData = await _db.Ado.SqlQueryAsync(headcountSql, new { monthStr }); + var headcountDict = headcountData + .Where(x => x.StoreId != null) + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToInt32(x.HeadCount)); + + // 1.8 考勤数据 (lq_attendance_summary) + var attendanceList = await _db.Queryable() + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) + .ToListAsync(); + var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x); + + // 1.9 产品物料统计(仓库领用金额,注意11月特殊规则) + var queryMonth = monthStr; + if (month == 11) + { + // 11月工资算10月数据 + queryMonth = $"{year}10"; + } + var productMaterialSql = $@" + SELECT + F_StoreId as StoreId, + COALESCE(SUM(F_TotalAmount), 0) as MaterialAmount + FROM lq_inventory_usage + WHERE F_IsEffective = 1 + AND DATE_FORMAT(F_UsageTime, '%Y%m') = @queryMonth + GROUP BY F_StoreId"; + + var productMaterialData = await _db.Ado.SqlQueryAsync(productMaterialSql, new { queryMonth }); + var productMaterialDict = productMaterialData + .Where(x => x.StoreId != null) + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.MaterialAmount ?? 0)); + + // 1.10 合作项目成本统计 + var cooperationCostList = await _db.Queryable() + .Where(x => x.Year == year && x.Month == monthStr && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new { x.StoreId, x.TotalAmount }) + .ToListAsync(); + var cooperationCostDict = cooperationCostList + .Where(x => !string.IsNullOrEmpty(x.StoreId)) + .GroupBy(x => x.StoreId) + .ToDictionary(g => g.Key, g => g.Sum(x => x.TotalAmount)); + + // 1.11 店内支出统计 + var storeExpenseSql = $@" + SELECT + F_StoreId as StoreId, + COALESCE(SUM(F_Amount), 0) as ExpenseAmount + FROM lq_store_expense + WHERE F_IsEffective = 1 + AND DATE_FORMAT(F_ExpenseDate, '%Y%m') = @monthStr + GROUP BY F_StoreId"; + + var storeExpenseData = await _db.Ado.SqlQueryAsync(storeExpenseSql, new { monthStr }); + var storeExpenseDict = storeExpenseData + .Where(x => x.StoreId != null) + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.ExpenseAmount ?? 0)); + + // 1.12 洗毛巾费用统计(只统计送出的记录,F_FlowType = 0) + var laundryCostSql = $@" + SELECT + F_StoreId as StoreId, + COALESCE(SUM(F_TotalPrice), 0) as LaundryAmount + FROM lq_laundry_flow + WHERE F_IsEffective = 1 + AND F_FlowType = 0 + AND DATE_FORMAT(F_CreateTime, '%Y%m') = @monthStr + GROUP BY F_StoreId"; + + var laundryCostData = await _db.Ado.SqlQueryAsync(laundryCostSql, new { monthStr }); + var laundryCostDict = laundryCostData + .Where(x => x.StoreId != null) + .ToDictionary(x => x.StoreId.ToString(), x => Convert.ToDecimal(x.LaundryAmount ?? 0)); + + // 2. 计算每个店长的工资 + var storeManagerSalaryList = new List(); + + foreach (var storeManagerUser in storeManagerUserList) + { + var salary = new LqStoreManagerSalaryStatisticsEntity + { + Id = YitIdHelper.NextId().ToString(), + EmployeeId = storeManagerUser.Id, + EmployeeName = storeManagerUser.RealName, + StatisticsMonth = monthStr, + Position = "店长", + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now, + IsLocked = 0, + MonthlyPaymentStatus = "未发放" + }; + + // 2.1 填充门店信息 + string storeId = storeManagerUser.Mdid; + if (string.IsNullOrEmpty(storeId)) + { + // 如果用户没有门店ID,跳过 + continue; + } + + salary.StoreId = storeId; + + if (storeDict.ContainsKey(storeId)) + { + var store = storeDict[storeId]; + salary.StoreName = store.Dm; + salary.StoreType = store.StoreType; + salary.StoreCategory = store.StoreCategory; + } + else + { + // 如果门店不存在,跳过 + continue; + } + + // 2.2 填充新店保护信息 + bool isNewStore = false; + if (newStoreProtectionDict.ContainsKey(storeId)) + { + var protection = newStoreProtectionDict[storeId]; + salary.IsNewStore = "是"; + salary.NewStoreProtectionStage = protection.Stage; + isNewStore = true; + } + else + { + salary.IsNewStore = "否"; + salary.NewStoreProtectionStage = 0; + } + + // 2.2.1 数据校验:只有新店才需要校验门店分类和门店类型 + if (isNewStore) + { + if (!salary.StoreCategory.HasValue) + { + // 新店如果没有设置门店分类,默认设置为B类门店 + salary.StoreCategory = (int)StoreCategoryEnum.B类门店; + } + if (!salary.StoreType.HasValue) + { + // 新店如果没有设置门店类型,默认设置为200平门店 + salary.StoreType = (int)StoreTypeEnum.门店200平; + } + } + + // 2.3 获取门店目标信息(门店生命线、目标人头、目标消耗) + if (!storeTargetDict.ContainsKey(storeId)) + { + throw new Exception($"门店【{salary.StoreName ?? storeId}】在门店目标表中未配置{monthStr}月份的目标数据,无法计算店长工资"); + } + + var storeTarget = storeTargetDict[storeId]; + salary.StoreLifeline = storeTarget.StoreLifeline; + salary.TargetHeadCount = storeTarget.StoreHeadcountTarget; + salary.TargetConsume = storeTarget.StoreConsumeTarget; + + // 数据校验:门店生命线、目标人头必须设置 + if (salary.StoreLifeline <= 0) + { + throw new Exception($"门店【{salary.StoreName ?? storeId}】的门店生命线未设置,无法计算店长工资"); + } + if (salary.TargetHeadCount <= 0) + { + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标人头数未设置,无法计算店长工资"); + } + if (!isNewStore && salary.TargetConsume <= 0) + { + throw new Exception($"门店【{salary.StoreName ?? storeId}】的目标消耗未设置,无法计算店长工资(老店需要考核消耗)"); + } + + // 2.4 计算门店业绩 + decimal billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0; + decimal refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0; + salary.StoreBillingPerformance = billing; + salary.StoreRefundPerformance = refund; + salary.StoreTotalPerformance = billing - refund; + + // 计算业绩完成率 + if (salary.StoreLifeline > 0) + { + salary.PerformanceCompletionRate = salary.StoreTotalPerformance / salary.StoreLifeline; + } + else + { + salary.PerformanceCompletionRate = 0; + } + + // 2.5 统计门店消耗金额 + salary.StoreConsume = storeConsumeDict.ContainsKey(storeId) ? storeConsumeDict[storeId] : 0; + + // 2.6 统计进店消耗人数 + salary.HeadCount = headcountDict.ContainsKey(storeId) ? headcountDict[storeId] : 0; + + // 2.7 计算考核指标(业绩、人头、消耗是否达标) + bool performanceReached = salary.StoreTotalPerformance >= salary.StoreLifeline; + bool headCountReached = salary.HeadCount >= salary.TargetHeadCount; + bool consumeReached = salary.StoreConsume >= salary.TargetConsume; + + salary.PerformanceReached = performanceReached ? "是" : "否"; + salary.HeadCountReached = headCountReached ? "是" : "否"; + salary.ConsumeReached = consumeReached ? "是" : "否"; + + // 计算未达标指标数量 + int unreachedCount = 0; + if (!performanceReached) unreachedCount++; + if (!headCountReached) unreachedCount++; + // 新店不考核消耗 + if (!isNewStore && !consumeReached) unreachedCount++; + + salary.UnreachedIndicatorCount = unreachedCount; + + // 2.8 计算底薪 + salary.BaseSalary = 4000m; // 固定底薪4000元 + + // 考核扣款:老店每个指标500元,新店每个指标800元 + if (isNewStore) + { + salary.AssessmentDeduction = unreachedCount * 800m; + } + else + { + salary.AssessmentDeduction = unreachedCount * 500m; + } + + // 旗舰店负奖励(800元) + bool isFlagshipStore = salary.StoreType.HasValue && salary.StoreType.Value == (int)StoreTypeEnum.旗舰店; + salary.FlagshipStoreDeduction = isFlagshipStore ? 800m : 0m; + + // 实际底薪 = 底薪 - 考核扣款 - 旗舰店负奖励 + salary.ActualBaseSalary = salary.BaseSalary - salary.AssessmentDeduction - salary.FlagshipStoreDeduction; + + // 2.9 计算毛利 + // 销售业绩 = 开单业绩 - 退款业绩 + salary.SalesPerformance = salary.StoreTotalPerformance; + + // 产品物料(注意11月特殊规则已在查询时处理) + salary.ProductMaterial = productMaterialDict.ContainsKey(storeId) ? productMaterialDict[storeId] : 0; + + // 合作项目成本 + salary.CooperationCost = cooperationCostDict.ContainsKey(storeId) ? cooperationCostDict[storeId] : 0; + + // 店内支出 + salary.StoreExpense = storeExpenseDict.ContainsKey(storeId) ? storeExpenseDict[storeId] : 0; + + // 洗毛巾费用 + salary.LaundryCost = laundryCostDict.ContainsKey(storeId) ? laundryCostDict[storeId] : 0; + + // 毛利 = 销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾 + salary.GrossProfit = salary.SalesPerformance - salary.ProductMaterial - salary.CooperationCost - salary.StoreExpense - salary.LaundryCost; + + // 2.10 计算提成(基于毛利) + CalculateCommission(salary, isNewStore, performanceReached); + + // 2.11 考勤数据 + if (attendanceDict.ContainsKey(storeManagerUser.Id)) + { + var attendance = attendanceDict[storeManagerUser.Id]; + salary.WorkingDays = (int)attendance.WorkDays; + salary.LeaveDays = (int)attendance.LeaveDays; + } + else + { + salary.WorkingDays = 0; + salary.LeaveDays = 0; + } + + // 2.12 计算应发工资 + salary.GrossSalary = salary.ActualBaseSalary + salary.CommissionAmount; + + // 2.13 初始化扣款、补贴、奖金字段(默认值为0) + salary.MissingCard = 0; + salary.LateArrival = 0; + salary.LeaveDeduction = 0; + salary.SocialInsuranceDeduction = 0; + salary.RewardDeduction = 0; + salary.AccommodationDeduction = 0; + salary.StudyPeriodDeduction = 0; + salary.WorkClothesDeduction = 0; + salary.TotalDeduction = 0; + + salary.MonthlyTrainingSubsidy = 0; + salary.MonthlyTransportSubsidy = 0; + salary.LastMonthTrainingSubsidy = 0; + salary.LastMonthTransportSubsidy = 0; + salary.TotalSubsidy = 0; + + salary.Bonus = 0; + salary.ReturnPhoneDeposit = 0; + salary.ReturnAccommodationDeposit = 0; + + // 2.14 计算实发工资 + salary.ActualSalary = salary.GrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus; + + // 2.15 初始化支付相关字段 + salary.PaidAmount = 0; + salary.PendingAmount = salary.ActualSalary; + salary.LastMonthSupplement = 0; + salary.MonthlyTotalPayment = 0; + + storeManagerSalaryList.Add(salary); + } + + // 3. 保存数据 + if (storeManagerSalaryList.Any()) + { + // 先删除当月旧数据 (防止重复) + await _db.Deleteable() + .Where(x => x.StatisticsMonth == monthStr) + .ExecuteCommandAsync(); + + await _db.Insertable(storeManagerSalaryList).ExecuteCommandAsync(); + } + } + + /// + /// 计算提成(基于毛利) + /// + /// 工资实体 + /// 是否新店 + /// 业绩是否达标 + private void CalculateCommission(LqStoreManagerSalaryStatisticsEntity salary, bool isNewStore, bool performanceReached) + { + decimal grossProfit = salary.GrossProfit; + + // 确定提成比例 + decimal commissionRate; + + if (isNewStore) + { + // 新店:根据门店分类和门店类型确定提成比例 + int? storeCategory = salary.StoreCategory; + int? storeType = salary.StoreType; + + if (!storeCategory.HasValue) + { + throw new Exception($"新店【{salary.StoreName}】的门店分类未设置,无法计算提成"); + } + if (!storeType.HasValue) + { + throw new Exception($"新店【{salary.StoreName}】的门店类型未设置,无法计算提成"); + } + + // 新店根据门店分类和门店类型确定提成比例 + // 这里需要根据实际业务规则确定新店的提成比例 + // 暂时使用统一的提成比例,后续可以根据业务规则调整 + if (performanceReached) + { + commissionRate = 0.035m; // 3.5%(业绩达标) + } + else + { + commissionRate = 0.03m; // 3%(业绩未达标) + } + } + else + { + // 老店:不区分门店分类和门店类型,使用统一比例 + if (performanceReached) + { + commissionRate = 0.04m; // 4%(业绩达标) + } + else + { + commissionRate = 0.035m; // 3.5%(业绩未达标) + } + } + + salary.CommissionRate = commissionRate; + salary.CommissionAmount = grossProfit * commissionRate; + } + } +} + diff --git a/sql/创建合作成本表和店内支出表.sql b/sql/创建合作成本表和店内支出表.sql new file mode 100644 index 0000000..fe85f9d --- /dev/null +++ b/sql/创建合作成本表和店内支出表.sql @@ -0,0 +1,60 @@ +-- ============================================ +-- 创建合作成本表和店内支出表 +-- ============================================ +-- 说明:用于店长工资计算中的毛利计算 +-- 执行时间:2025年 +-- ============================================ + +-- ============================================ +-- 1. 创建合作成本表 (lq_cooperation_cost) +-- ============================================ +CREATE TABLE IF NOT EXISTS `lq_cooperation_cost` ( + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID', + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)', + `F_StoreName` VARCHAR(200) NULL COMMENT '门店名称', + `F_Year` INT NOT NULL COMMENT '年份', + `F_Month` VARCHAR(6) NOT NULL COMMENT '月份(YYYYMM格式)', + `F_TotalAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合计金额', + `F_Remarks` VARCHAR(1000) NULL COMMENT '备注说明', + `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)', + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID', + `F_CreateTime` DATETIME NULL COMMENT '创建时间', + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID', + `F_UpdateTime` DATETIME NULL COMMENT '更新时间', + PRIMARY KEY (`F_Id`), + KEY `idx_store_month` (`F_StoreId`, `F_Year`, `F_Month`), + KEY `idx_month` (`F_Year`, `F_Month`), + KEY `idx_store_id` (`F_StoreId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合作成本表'; + +-- ============================================ +-- 2. 创建店内支出表 (lq_store_expense) +-- ============================================ +CREATE TABLE IF NOT EXISTS `lq_store_expense` ( + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID', + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)', + `F_StoreName` VARCHAR(200) NULL COMMENT '门店名称', + `F_ExpenseCategoryId` VARCHAR(50) NULL COMMENT '支出分类ID(关联lq_reimbursement_category.F_Id)', + `F_ExpenseCategoryName` VARCHAR(200) NULL COMMENT '支出分类名称', + `F_ExpenseDate` DATETIME NOT NULL COMMENT '支出日期', + `F_UnitPrice` DECIMAL(18,2) DEFAULT 0.00 COMMENT '单价', + `F_Quantity` INT DEFAULT 0 COMMENT '数量', + `F_Amount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '金额', + `F_Memo` VARCHAR(1000) NULL COMMENT '备注说明', + `F_Attachment` TEXT NULL COMMENT '附件(JSON格式)', + `F_RelatedReimbursementId` VARCHAR(50) NULL COMMENT '关联报销申请ID(可选)', + `F_RelatedPurchaseRecordId` VARCHAR(50) NULL COMMENT '关联购买记录ID(可选)', + `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)', + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID', + `F_CreateTime` DATETIME NULL COMMENT '创建时间', + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID', + `F_UpdateTime` DATETIME NULL COMMENT '更新时间', + PRIMARY KEY (`F_Id`), + KEY `idx_store_date` (`F_StoreId`, `F_ExpenseDate`), + KEY `idx_expense_date` (`F_ExpenseDate`), + KEY `idx_category` (`F_ExpenseCategoryId`), + KEY `idx_store_id` (`F_StoreId`), + KEY `idx_related_reimbursement` (`F_RelatedReimbursementId`), + KEY `idx_related_purchase` (`F_RelatedPurchaseRecordId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店内支出表'; + diff --git a/sql/创建库存使用申请审批流程表.sql b/sql/创建库存使用申请审批流程表.sql index 7d1c43b..df03491 100644 --- a/sql/创建库存使用申请审批流程表.sql +++ b/sql/创建库存使用申请审批流程表.sql @@ -100,3 +100,5 @@ SET u.`F_UnitPrice` = p.`F_Price`, WHERE u.`F_UnitPrice` = 0 OR u.`F_UnitPrice` IS NULL; + + diff --git a/sql/创建店长工资统计表.sql b/sql/创建店长工资统计表.sql new file mode 100644 index 0000000..834dda7 --- /dev/null +++ b/sql/创建店长工资统计表.sql @@ -0,0 +1,108 @@ +-- 创建店长工资统计表 +-- 表名:lq_store_manager_salary_statistics + +CREATE TABLE IF NOT EXISTS `lq_store_manager_salary_statistics` ( + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID', + + -- 基础信息 + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)', + `F_StoreName` VARCHAR(200) NULL COMMENT '门店名称', + `F_Position` VARCHAR(50) NULL COMMENT '核算岗位(店长)', + `F_EmployeeId` VARCHAR(50) NOT NULL COMMENT '员工ID(关联BASE_USER.F_Id)', + `F_EmployeeName` VARCHAR(100) NULL COMMENT '员工姓名', + `F_StatisticsMonth` VARCHAR(6) NOT NULL COMMENT '统计月份(YYYYMM格式)', + + -- 门店信息 + `F_StoreType` INT NULL COMMENT '门店类型(判断旗舰店)', + `F_StoreCategory` INT NULL COMMENT '门店类别(A/B/C类)', + `F_IsNewStore` VARCHAR(10) NULL COMMENT '是否新店(是/否)', + `F_NewStoreProtectionStage` INT DEFAULT 0 COMMENT '新店保护阶段', + + -- 业绩相关 + `F_StoreTotalPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店总业绩(开单业绩-退卡业绩)', + `F_StoreBillingPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店开单业绩', + `F_StoreRefundPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店退卡业绩', + `F_StoreLifeline` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店生命线', + `F_PerformanceCompletionRate` DECIMAL(18,4) DEFAULT 0.0000 COMMENT '业绩完成率', + `F_PerformanceReached` VARCHAR(10) NULL COMMENT '业绩是否达标(是/否)', + + -- 考核相关 + `F_HeadCount` INT DEFAULT 0 COMMENT '进店消耗人数', + `F_TargetHeadCount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '目标人头数', + `F_HeadCountReached` VARCHAR(10) NULL COMMENT '人头是否达标(是/否)', + `F_StoreConsume` DECIMAL(18,2) DEFAULT 0.00 COMMENT '门店消耗金额', + `F_TargetConsume` DECIMAL(18,2) DEFAULT 0.00 COMMENT '目标消耗金额', + `F_ConsumeReached` VARCHAR(10) NULL COMMENT '消耗是否达标(是/否)', + `F_UnreachedIndicatorCount` INT DEFAULT 0 COMMENT '未达标指标数量', + `F_AssessmentDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '考核扣款金额', + + -- 毛利相关 + `F_SalesPerformance` DECIMAL(18,2) DEFAULT 0.00 COMMENT '销售业绩(开单业绩-退款业绩)', + `F_ProductMaterial` DECIMAL(18,2) DEFAULT 0.00 COMMENT '产品物料(仓库领用金额)', + `F_CooperationCost` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合作项目成本', + `F_StoreExpense` DECIMAL(18,2) DEFAULT 0.00 COMMENT '店内支出', + `F_LaundryCost` DECIMAL(18,2) DEFAULT 0.00 COMMENT '洗毛巾费用', + `F_GrossProfit` DECIMAL(18,2) DEFAULT 0.00 COMMENT '毛利(销售业绩-产品物料-合作项目成本-店内支出-洗毛巾)', + + -- 提成相关 + `F_CommissionRate` DECIMAL(18,4) DEFAULT 0.0000 COMMENT '提成比例', + `F_CommissionAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '提成金额(基于毛利)', + + -- 底薪相关 + `F_BaseSalary` DECIMAL(18,2) DEFAULT 4000.00 COMMENT '底薪金额(固定4000元)', + `F_FlagshipStoreDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '旗舰店负奖励(800元)', + `F_ActualBaseSalary` DECIMAL(18,2) DEFAULT 0.00 COMMENT '实际底薪(底薪-考核扣款-旗舰店负奖励)', + + -- 考勤相关 + `F_WorkingDays` INT DEFAULT 0 COMMENT '在店天数', + `F_LeaveDays` INT DEFAULT 0 COMMENT '请假天数', + + -- 工资相关 + `F_GrossSalary` DECIMAL(18,2) DEFAULT 0.00 COMMENT '应发工资(实际底薪+提成)', + `F_ActualSalary` DECIMAL(18,2) DEFAULT 0.00 COMMENT '实发工资(应发工资-扣款合计+补贴合计+奖金)', + + -- 扣款相关 + `F_MissingCard` DECIMAL(18,2) DEFAULT 0.00 COMMENT '缺卡扣款', + `F_LateArrival` DECIMAL(18,2) DEFAULT 0.00 COMMENT '迟到扣款', + `F_LeaveDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '请假扣款', + `F_SocialInsuranceDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣社保', + `F_RewardDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣除奖励', + `F_AccommodationDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣住宿费', + `F_StudyPeriodDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣学习期费用', + `F_WorkClothesDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣工作服费用', + `F_TotalDeduction` DECIMAL(18,2) DEFAULT 0.00 COMMENT '扣款合计', + + -- 补贴相关 + `F_MonthlyTrainingSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '当月培训补贴', + `F_MonthlyTransportSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '当月交通补贴', + `F_LastMonthTrainingSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '上月培训补贴', + `F_LastMonthTransportSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '上月交通补贴', + `F_TotalSubsidy` DECIMAL(18,2) DEFAULT 0.00 COMMENT '补贴合计', + + -- 奖金相关 + `F_Bonus` DECIMAL(18,2) DEFAULT 0.00 COMMENT '发奖金', + `F_ReturnPhoneDeposit` DECIMAL(18,2) DEFAULT 0.00 COMMENT '退手机押金', + `F_ReturnAccommodationDeposit` DECIMAL(18,2) DEFAULT 0.00 COMMENT '退住宿押金', + + -- 支付相关 + `F_MonthlyPaymentStatus` VARCHAR(50) NULL COMMENT '当月是否发放', + `F_PaidAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '支付金额', + `F_PendingAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '待支付金额', + `F_LastMonthSupplement` DECIMAL(18,2) DEFAULT 0.00 COMMENT '补发上月', + `F_MonthlyTotalPayment` DECIMAL(18,2) DEFAULT 0.00 COMMENT '当月支付总额', + + -- 其他 + `F_IsLocked` INT DEFAULT 0 COMMENT '是否锁定(0未锁定,1已锁定)', + `F_CreateTime` DATETIME NULL COMMENT '创建时间', + `F_UpdateTime` DATETIME NULL COMMENT '更新时间', + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人', + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人', + + PRIMARY KEY (`F_Id`), + KEY `idx_store_month` (`F_StoreId`, `F_StatisticsMonth`), + KEY `idx_employee_month` (`F_EmployeeId`, `F_StatisticsMonth`), + KEY `idx_statistics_month` (`F_StatisticsMonth`), + KEY `idx_store_id` (`F_StoreId`), + KEY `idx_employee_id` (`F_EmployeeId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店长工资统计表'; + diff --git a/合作成本和店内支出-前端调用说明.md b/合作成本和店内支出-前端调用说明.md new file mode 100644 index 0000000..4b7ea1f --- /dev/null +++ b/合作成本和店内支出-前端调用说明.md @@ -0,0 +1,726 @@ +# 合作成本和店内支出 - 前端调用说明 + +## 📋 目录 +- [业务流程概述](#业务流程概述) +- [合作成本接口列表](#合作成本接口列表) +- [店内支出接口列表](#店内支出接口列表) +- [完整流程示例](#完整流程示例) +- [接口详细说明](#接口详细说明) +- [数据验证说明](#数据验证说明) +- [注意事项](#注意事项) + +## 🔄 业务流程概述 + +### 合作成本管理 +合作成本用于记录门店每月的合作项目成本,用于店长工资计算中的毛利计算。 + +**核心功能:** +1. **创建合作成本** - 录入门店某年某月的合作成本金额 +2. **查询合作成本** - 支持列表查询和详情查询,可按门店、年份、月份筛选 +3. **更新合作成本** - 修改已创建的合作成本记录 +4. **删除合作成本** - 删除合作成本记录(逻辑删除) +5. **导入合作成本** - 通过Excel批量导入合作成本数据 +6. **导出合作成本** - 导出合作成本数据为Excel文件 + +### 店内支出管理 +店内支出用于记录门店每月的各项支出,用于店长工资计算中的毛利计算。 + +**核心功能:** +1. **创建店内支出** - 录入门店的支出明细(支持分类、单价、数量、金额等) +2. **查询店内支出** - 支持列表查询和详情查询,可按门店、分类、日期范围筛选 +3. **更新店内支出** - 修改已创建的店内支出记录 +4. **删除店内支出** - 删除店内支出记录(逻辑删除) +5. **导入店内支出** - 通过Excel批量导入店内支出数据 +6. **导出店内支出** - 导出店内支出数据为Excel文件 + +## 📡 合作成本接口列表 + +| 接口 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 获取合作成本详情 | GET | `/api/Extend/LqCooperationCost/{id}` | 根据ID获取合作成本详情 | +| 获取合作成本列表 | GET | `/api/Extend/LqCooperationCost` | 分页查询合作成本列表,支持多条件筛选 | +| 获取全部数据(不分页) | GET | `/api/Extend/LqCooperationCost/Actions/GetNoPagingList` | 获取全部数据,用于导出 | +| 创建合作成本 | POST | `/api/Extend/LqCooperationCost` | 创建新的合作成本记录 | +| 更新合作成本 | PUT | `/api/Extend/LqCooperationCost/{id}` | 更新合作成本记录 | +| 删除合作成本 | DELETE | `/api/Extend/LqCooperationCost/{id}` | 删除合作成本记录(逻辑删除) | +| 导出合作成本 | GET | `/api/Extend/LqCooperationCost/Actions/Export` | 导出合作成本数据为Excel | +| 导入合作成本 | POST | `/api/Extend/LqCooperationCost/Actions/Import` | 通过Excel导入合作成本数据 | + +## 📡 店内支出接口列表 + +| 接口 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 获取店内支出详情 | GET | `/api/Extend/LqStoreExpense/{id}` | 根据ID获取店内支出详情 | +| 获取店内支出列表 | GET | `/api/Extend/LqStoreExpense` | 分页查询店内支出列表,支持多条件筛选 | +| 获取全部数据(不分页) | GET | `/api/Extend/LqStoreExpense/Actions/GetNoPagingList` | 获取全部数据,用于导出 | +| 创建店内支出 | POST | `/api/Extend/LqStoreExpense` | 创建新的店内支出记录 | +| 更新店内支出 | PUT | `/api/Extend/LqStoreExpense/{id}` | 更新店内支出记录 | +| 删除店内支出 | DELETE | `/api/Extend/LqStoreExpense/{id}` | 删除店内支出记录(逻辑删除) | +| 导出店内支出 | GET | `/api/Extend/LqStoreExpense/Actions/Export` | 导出店内支出数据为Excel | +| 导入店内支出 | POST | `/api/Extend/LqStoreExpense/Actions/Import` | 通过Excel导入店内支出数据 | + +## 🚀 完整流程示例 + +### 合作成本管理流程 + +#### 步骤1:创建合作成本 + +**接口:** `POST /api/Extend/LqCooperationCost` + +**请求示例:** +```javascript +const response = await request({ + url: '/api/Extend/LqCooperationCost', + method: 'POST', + data: { + storeId: '1649328471923847172', // 门店ID(必填) + storeName: '绿纤华润店', // 门店名称(可选,不传则自动查询) + year: 2025, // 年份(必填) + month: '202511', // 月份(必填,格式:YYYYMM,如202511表示2025年11月) + totalAmount: 3000.00, // 合计金额(必填) + remarks: '测试数据-合作成本' // 备注说明(可选) + } +}); + +// 响应示例 +// { +// "code": 200, +// "msg": "操作成功", +// "data": null +// } +``` + +**说明:** +- 如果未提供门店名称,系统会根据门店ID自动查询并填充 +- 月份格式必须为YYYYMM(如:202511表示2025年11月) +- 同一门店、同一年份、同一月份只能有一条有效记录(如果已存在会报错) + +#### 步骤2:查询合作成本列表 + +**接口:** `GET /api/Extend/LqCooperationCost` + +**请求示例:** +```javascript +const response = await request({ + url: '/api/Extend/LqCooperationCost', + method: 'GET', + params: { + currentPage: 1, // 当前页码(必填) + pageSize: 10, // 每页数量(必填) + storeId: '1649328471923847172', // 门店ID(可选) + storeName: '绿纤华润店', // 门店名称(可选,模糊查询) + year: 2025, // 年份(可选) + month: '202511', // 月份(可选,格式:YYYYMM) + sidx: 'CreateTime', // 排序字段(可选,默认:CreateTime) + sort: 'desc' // 排序方式(可选,asc/desc,默认:desc) + } +}); + +// 响应示例 +// { +// "code": 200, +// "msg": "操作成功", +// "data": { +// "list": [ +// { +// "id": "768041985045955845", +// "storeId": "1649328471923847172", +// "storeName": "绿纤华润店", +// "year": 2025, +// "month": "202511", +// "totalAmount": 3000.00, +// "remarks": "测试数据-合作成本", +// "createUser": "admin", +// "createTime": "2025-01-10T10:00:00", +// "updateUser": "admin", +// "updateTime": "2025-01-10T10:00:00" +// } +// ], +// "pagination": { +// "total": 1, +// "pageSize": 10, +// "currentPage": 1 +// } +// } +// } +``` + +#### 步骤3:更新合作成本 + +**接口:** `PUT /api/Extend/LqCooperationCost/{id}` + +**请求示例:** +```javascript +const response = await request({ + url: `/api/Extend/LqCooperationCost/${id}`, + method: 'PUT', + data: { + storeId: '1649328471923847172', + storeName: '绿纤华润店', + year: 2025, + month: '202511', + totalAmount: 3500.00, // 修改金额 + remarks: '更新后的备注' + } +}); +``` + +#### 步骤4:删除合作成本 + +**接口:** `DELETE /api/Extend/LqCooperationCost/{id}` + +**请求示例:** +```javascript +const response = await request({ + url: `/api/Extend/LqCooperationCost/${id}`, + method: 'DELETE' +}); +``` + +#### 步骤5:导出合作成本 + +**接口:** `GET /api/Extend/LqCooperationCost/Actions/Export` + +**请求示例:** +```javascript +const response = await request({ + url: '/api/Extend/LqCooperationCost/Actions/Export', + method: 'GET', + params: { + storeId: '1649328471923847172', // 可选 + year: 2025, // 可选 + month: '202511' // 可选 + }, + responseType: 'blob' // 重要:设置响应类型为blob +}); + +// 下载文件 +const blob = new Blob([response.data]); +const url = window.URL.createObjectURL(blob); +const link = document.createElement('a'); +link.href = url; +link.setAttribute('download', `合作成本表_${new Date().getTime()}.xlsx`); +document.body.appendChild(link); +link.click(); +document.body.removeChild(link); +``` + +#### 步骤6:导入合作成本 + +**接口:** `POST /api/Extend/LqCooperationCost/Actions/Import` + +**请求示例:** +```javascript +const formData = new FormData(); +formData.append('file', file); // file是File对象 + +const response = await request({ + url: '/api/Extend/LqCooperationCost/Actions/Import', + method: 'POST', + data: formData, + headers: { + 'Content-Type': 'multipart/form-data' + } +}); + +// 响应示例 +// { +// "code": 200, +// "msg": "导入成功", +// "data": { +// "successCount": 10, +// "failCount": 2, +// "failMessages": [ +// "第3行:门店ID不存在", +// "第5行:该门店2025年11月的记录已存在" +// ] +// } +// } +``` + +**Excel格式要求:** +- 第一行为表头:门店ID、门店名称、年份、月份、合计金额、备注 +- 门店ID和门店名称至少填写一个 +- 月份格式:YYYYMM(如:202511) +- 合计金额必须为数字 + +### 店内支出管理流程 + +#### 步骤1:创建店内支出 + +**接口:** `POST /api/Extend/LqStoreExpense` + +**请求示例:** +```javascript +const response = await request({ + url: '/api/Extend/LqStoreExpense', + method: 'POST', + data: { + storeId: '1649328471923847172', // 门店ID(必填) + storeName: '绿纤华润店', // 门店名称(可选,不传则自动查询) + expenseCategoryId: '11156146041171400', // 支出分类ID(可选) + expenseCategoryName: '日常支出', // 支出分类名称(可选) + expenseDate: '2025-11-15T00:00:00', // 支出日期(必填) + unitPrice: 2000.00, // 单价(可选) + quantity: 1, // 数量(可选) + amount: 2000.00, // 金额(必填) + memo: '测试数据-店内支出', // 备注说明(可选) + attachment: [], // 附件(可选,FileControlsModel数组) + relatedReimbursementId: '', // 关联报销申请ID(可选) + relatedPurchaseRecordId: '' // 关联购买记录ID(可选) + } +}); + +// 响应示例 +// { +// "code": 200, +// "msg": "操作成功", +// "data": null +// } +``` + +**说明:** +- 如果未提供门店名称,系统会根据门店ID自动查询并填充 +- 金额字段为必填,单价和数量为可选(如果提供了单价和数量,系统会验证:金额 = 单价 × 数量) +- 支持附件上传(FileControlsModel格式) + +#### 步骤2:查询店内支出列表 + +**接口:** `GET /api/Extend/LqStoreExpense` + +**请求示例:** +```javascript +const response = await request({ + url: '/api/Extend/LqStoreExpense', + method: 'GET', + params: { + currentPage: 1, // 当前页码(必填) + pageSize: 10, // 每页数量(必填) + storeId: '1649328471923847172', // 门店ID(可选) + storeName: '绿纤华润店', // 门店名称(可选,模糊查询) + expenseCategoryId: '11156146041171400', // 支出分类ID(可选) + expenseDateStart: '2025-11-01', // 支出日期开始(可选) + expenseDateEnd: '2025-11-30', // 支出日期结束(可选) + sidx: 'ExpenseDate', // 排序字段(可选,默认:ExpenseDate) + sort: 'desc' // 排序方式(可选,asc/desc,默认:desc) + } +}); + +// 响应示例 +// { +// "code": 200, +// "msg": "操作成功", +// "data": { +// "list": [ +// { +// "id": "768041985045955845", +// "storeId": "1649328471923847172", +// "storeName": "绿纤华润店", +// "expenseCategoryId": "11156146041171400", +// "expenseCategoryName": "日常支出", +// "expenseDate": "2025-11-15T00:00:00", +// "unitPrice": 2000.00, +// "quantity": 1, +// "amount": 2000.00, +// "memo": "测试数据-店内支出", +// "attachment": [], +// "relatedReimbursementId": "", +// "relatedPurchaseRecordId": "", +// "createUser": "admin", +// "createTime": "2025-01-10T10:00:00", +// "updateUser": "admin", +// "updateTime": "2025-01-10T10:00:00" +// } +// ], +// "pagination": { +// "total": 1, +// "pageSize": 10, +// "currentPage": 1 +// } +// } +// } +``` + +#### 步骤3:更新店内支出 + +**接口:** `PUT /api/Extend/LqStoreExpense/{id}` + +**请求示例:** +```javascript +const response = await request({ + url: `/api/Extend/LqStoreExpense/${id}`, + method: 'PUT', + data: { + storeId: '1649328471923847172', + storeName: '绿纤华润店', + expenseCategoryId: '11156146041171400', + expenseCategoryName: '日常支出', + expenseDate: '2025-11-15T00:00:00', + unitPrice: 2500.00, // 修改单价 + quantity: 1, + amount: 2500.00, // 修改金额 + memo: '更新后的备注', + attachment: [] + } +}); +``` + +#### 步骤4:删除店内支出 + +**接口:** `DELETE /api/Extend/LqStoreExpense/{id}` + +**请求示例:** +```javascript +const response = await request({ + url: `/api/Extend/LqStoreExpense/${id}`, + method: 'DELETE' +}); +``` + +#### 步骤5:导出店内支出 + +**接口:** `GET /api/Extend/LqStoreExpense/Actions/Export` + +**请求示例:** +```javascript +const response = await request({ + url: '/api/Extend/LqStoreExpense/Actions/Export', + method: 'GET', + params: { + storeId: '1649328471923847172', // 可选 + expenseCategoryId: '11156146041171400', // 可选 + expenseDateStart: '2025-11-01', // 可选 + expenseDateEnd: '2025-11-30' // 可选 + }, + responseType: 'blob' // 重要:设置响应类型为blob +}); + +// 下载文件(同合作成本导出) +``` + +#### 步骤6:导入店内支出 + +**接口:** `POST /api/Extend/LqStoreExpense/Actions/Import` + +**请求示例:** +```javascript +const formData = new FormData(); +formData.append('file', file); // file是File对象 + +const response = await request({ + url: '/api/Extend/LqStoreExpense/Actions/Import', + method: 'POST', + data: formData, + headers: { + 'Content-Type': 'multipart/form-data' + } +}); + +// 响应示例(同合作成本导入) +``` + +**Excel格式要求:** +- 第一行为表头:门店ID、门店名称、支出分类ID、支出分类名称、支出日期、单价、数量、金额、备注 +- 门店ID和门店名称至少填写一个 +- 支出日期格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss +- 金额必须为数字 + +## 📝 接口详细说明 + +### 合作成本接口 + +#### 1. 获取合作成本详情 + +**接口:** `GET /api/Extend/LqCooperationCost/{id}` + +**路径参数:** +- `id`:合作成本记录ID(必填) + +**响应示例:** +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "id": "768041985045955845", + "storeId": "1649328471923847172", + "storeName": "绿纤华润店", + "year": 2025, + "month": "202511", + "totalAmount": 3000.00, + "remarks": "测试数据-合作成本", + "createUser": "admin", + "createTime": "2025-01-10T10:00:00", + "updateUser": "admin", + "updateTime": "2025-01-10T10:00:00" + } +} +``` + +#### 2. 获取合作成本列表 + +**接口:** `GET /api/Extend/LqCooperationCost` + +**查询参数:** +- `currentPage`:当前页码(必填,默认:1) +- `pageSize`:每页数量(必填,默认:10) +- `storeId`:门店ID(可选) +- `storeName`:门店名称(可选,模糊查询) +- `year`:年份(可选) +- `month`:月份(可选,格式:YYYYMM) +- `sidx`:排序字段(可选,默认:CreateTime) +- `sort`:排序方式(可选,asc/desc,默认:desc) + +**响应格式:** 同列表查询标准格式 + +#### 3. 创建合作成本 + +**接口:** `POST /api/Extend/LqCooperationCost` + +**请求体:** +```json +{ + "storeId": "1649328471923847172", // 门店ID(必填) + "storeName": "绿纤华润店", // 门店名称(可选) + "year": 2025, // 年份(必填) + "month": "202511", // 月份(必填,格式:YYYYMM) + "totalAmount": 3000.00, // 合计金额(必填) + "remarks": "备注说明" // 备注(可选) +} +``` + +**验证规则:** +- 门店ID必填 +- 年份必填,必须大于0 +- 月份必填,格式必须为YYYYMM(6位数字) +- 合计金额必填,必须大于等于0 +- 同一门店、同一年份、同一月份只能有一条有效记录 + +#### 4. 更新合作成本 + +**接口:** `PUT /api/Extend/LqCooperationCost/{id}` + +**路径参数:** +- `id`:合作成本记录ID(必填) + +**请求体:** 同创建接口 + +#### 5. 删除合作成本 + +**接口:** `DELETE /api/Extend/LqCooperationCost/{id}` + +**路径参数:** +- `id`:合作成本记录ID(必填) + +**说明:** 执行逻辑删除,不会真正删除数据 + +### 店内支出接口 + +#### 1. 获取店内支出详情 + +**接口:** `GET /api/Extend/LqStoreExpense/{id}` + +**路径参数:** +- `id`:店内支出记录ID(必填) + +**响应示例:** +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "id": "768041985045955845", + "storeId": "1649328471923847172", + "storeName": "绿纤华润店", + "expenseCategoryId": "11156146041171400", + "expenseCategoryName": "日常支出", + "expenseDate": "2025-11-15T00:00:00", + "unitPrice": 2000.00, + "quantity": 1, + "amount": 2000.00, + "memo": "测试数据-店内支出", + "attachment": [], + "relatedReimbursementId": "", + "relatedPurchaseRecordId": "", + "createUser": "admin", + "createTime": "2025-01-10T10:00:00", + "updateUser": "admin", + "updateTime": "2025-01-10T10:00:00" + } +} +``` + +#### 2. 获取店内支出列表 + +**接口:** `GET /api/Extend/LqStoreExpense` + +**查询参数:** +- `currentPage`:当前页码(必填,默认:1) +- `pageSize`:每页数量(必填,默认:10) +- `storeId`:门店ID(可选) +- `storeName`:门店名称(可选,模糊查询) +- `expenseCategoryId`:支出分类ID(可选) +- `expenseDateStart`:支出日期开始(可选,格式:YYYY-MM-DD) +- `expenseDateEnd`:支出日期结束(可选,格式:YYYY-MM-DD) +- `sidx`:排序字段(可选,默认:ExpenseDate) +- `sort`:排序方式(可选,asc/desc,默认:desc) + +**响应格式:** 同列表查询标准格式 + +#### 3. 创建店内支出 + +**接口:** `POST /api/Extend/LqStoreExpense` + +**请求体:** +```json +{ + "storeId": "1649328471923847172", // 门店ID(必填) + "storeName": "绿纤华润店", // 门店名称(可选) + "expenseCategoryId": "11156146041171400", // 支出分类ID(可选) + "expenseCategoryName": "日常支出", // 支出分类名称(可选) + "expenseDate": "2025-11-15T00:00:00", // 支出日期(必填) + "unitPrice": 2000.00, // 单价(可选) + "quantity": 1, // 数量(可选) + "amount": 2000.00, // 金额(必填) + "memo": "备注说明", // 备注(可选) + "attachment": [], // 附件(可选,FileControlsModel数组) + "relatedReimbursementId": "", // 关联报销申请ID(可选) + "relatedPurchaseRecordId": "" // 关联购买记录ID(可选) +} +``` + +**验证规则:** +- 门店ID必填 +- 支出日期必填 +- 金额必填,必须大于等于0 +- 如果提供了单价和数量,系统会验证:金额 = 单价 × 数量 + +#### 4. 更新店内支出 + +**接口:** `PUT /api/Extend/LqStoreExpense/{id}` + +**路径参数:** +- `id`:店内支出记录ID(必填) + +**请求体:** 同创建接口 + +#### 5. 删除店内支出 + +**接口:** `DELETE /api/Extend/LqStoreExpense/{id}` + +**路径参数:** +- `id`:店内支出记录ID(必填) + +**说明:** 执行逻辑删除,不会真正删除数据 + +## ✅ 数据验证说明 + +### 合作成本验证规则 + +1. **门店ID验证** + - 必填 + - 如果门店ID不存在,会报错 + +2. **年份验证** + - 必填 + - 必须为整数,大于0 + +3. **月份验证** + - 必填 + - 格式必须为YYYYMM(6位数字,如:202511) + - 月份必须在1-12之间 + +4. **合计金额验证** + - 必填 + - 必须为数字,大于等于0 + +5. **唯一性验证** + - 同一门店、同一年份、同一月份只能有一条有效记录 + - 如果已存在,创建时会报错 + +### 店内支出验证规则 + +1. **门店ID验证** + - 必填 + - 如果门店ID不存在,会报错 + +2. **支出日期验证** + - 必填 + - 格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss + +3. **金额验证** + - 必填 + - 必须为数字,大于等于0 + +4. **单价和数量验证** + - 如果同时提供了单价和数量,系统会验证:金额 = 单价 × 数量 + - 如果验证失败,会报错 + +## ⚠️ 注意事项 + +### 合作成本注意事项 + +1. **月份格式** + - 月份必须使用YYYYMM格式(如:202511表示2025年11月) + - 不要使用YYYY-MM格式 + +2. **唯一性约束** + - 同一门店、同一年份、同一月份只能有一条有效记录 + - 如果需要修改,请使用更新接口,不要重复创建 + +3. **门店名称自动填充** + - 如果未提供门店名称,系统会根据门店ID自动查询并填充 + - 建议前端也自动填充,提升用户体验 + +4. **导入Excel格式** + - 第一行必须是表头 + - 门店ID和门店名称至少填写一个 + - 月份格式必须为YYYYMM(6位数字) + +### 店内支出注意事项 + +1. **金额计算** + - 如果同时提供了单价和数量,系统会验证:金额 = 单价 × 数量 + - 建议前端在用户输入单价和数量时自动计算金额 + +2. **附件上传** + - 附件使用FileControlsModel格式 + - 支持多个附件 + +3. **关联字段** + - `relatedReimbursementId`:关联报销申请ID(可选) + - `relatedPurchaseRecordId`:关联购买记录ID(可选) + - 这些字段用于数据追溯,建议填写 + +4. **导入Excel格式** + - 第一行必须是表头 + - 门店ID和门店名称至少填写一个 + - 支出日期格式:YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss + +### 通用注意事项 + +1. **GET请求参数** + - 所有GET请求使用`params`传参,不使用`data` + - 分页参数`currentPage`和`pageSize`必填 + +2. **文件下载** + - 导出接口返回的是文件流,需要设置`responseType: 'blob'` + - 下载文件时需要创建Blob对象并触发下载 + +3. **文件上传** + - 导入接口需要使用`FormData`格式 + - 设置`Content-Type: 'multipart/form-data'` + +4. **错误处理** + - 所有接口返回标准格式:`{code, msg, data}` + - `code === 200`表示成功,其他表示失败 + - 失败时查看`msg`字段获取错误信息 + +5. **数据删除** + - 删除操作执行的是逻辑删除,不会真正删除数据 + - 删除后的数据在列表中不会显示,但可以通过数据库查询 + +6. **店长工资计算** + - 合作成本和店内支出数据用于店长工资计算中的毛利计算 + - 确保数据的准确性和及时性,避免影响工资计算结果 + diff --git a/店长工资计算-前置条件检查清单.md b/店长工资计算-前置条件检查清单.md new file mode 100644 index 0000000..b8506b8 --- /dev/null +++ b/店长工资计算-前置条件检查清单.md @@ -0,0 +1,308 @@ +# 店长工资计算 - 前置条件检查清单 + +## 📋 检查时间 +2025-01-XX + +--- + +## ✅ 一、数据表检查 + +### 1. 核心业务表 + +| 表名 | 用途 | 状态 | 说明 | +|------|------|------|------| +| `lq_kd_kdjlb` | 开单记录表(获取开单业绩) | ✅ 已存在 | 字段:`sfyj`(实付业绩)、`Djmd`(门店ID)、`Kdrq`(开单日期) | +| `lq_hytk_hytk` | 退卡记录表(获取退款业绩) | ✅ 已存在 | 字段:`F_ActualRefundAmount`(实际退款金额)、`md`(门店ID)、`tksj`(退卡时间) | +| `lq_inventory_usage` | 库存使用记录表(获取产品物料) | ✅ 已存在 | 字段:`F_TotalAmount`(合计金额)、`F_StoreId`(门店ID)、`F_UsageTime`(使用时间) | +| `lq_cooperation_cost` | 合作成本表 | ✅ 已创建 | 字段:`F_TotalAmount`(合计金额)、`F_StoreId`(门店ID)、`F_Year`(年份)、`F_Month`(月份) | +| `lq_store_expense` | 店内支出表 | ✅ 已创建 | 字段:`F_Amount`(金额)、`F_StoreId`(门店ID)、`F_ExpenseDate`(支出日期) | +| `lq_laundry_flow` | 清洗流水表(获取洗毛巾费用) | ✅ 已存在 | 字段:`F_TotalPrice`(总费用)、`F_StoreId`(门店ID)、`F_FlowType`(流水类型,0=送出) | +| `lq_md_target` | 门店目标表 | ✅ 已存在 | 字段:`F_StoreId`(门店ID)、`F_Month`(月份)、`F_StoreLifeline`(门店生命线)、`F_HeadcountTarget`(目标人头数)、`F_ConsumeTarget`(目标消耗) | +| `lq_xh_hyhk` | 耗卡记录表(统计进店消耗人数) | ✅ 已存在 | 用于统计进店消耗人数 | +| `lq_md_xdbhsj` | 新店保护时间表 | ⚠️ 需确认 | 用于判断是否为新店 | +| `lq_mdxx` | 门店信息表 | ✅ 已存在 | 字段:`F_Id`(门店ID)、`F_StoreType`(门店类型,判断旗舰店)、`F_StoreCategory`(门店分类,A/B/C类) | +| `BASE_USER` | 系统用户表 | ✅ 已存在 | 字段:`F_Id`(员工ID)、`F_RealName`(姓名)、`F_GW`(岗位,店长)、`F_MDID`(门店ID) | + +--- + +## ✅ 二、实体类检查 + +### 1. 核心实体类 + +| 实体类 | 对应表 | 状态 | 说明 | +|--------|--------|------|------| +| `LqKdKdjlbEntity` | `lq_kd_kdjlb` | ✅ 已存在 | 开单记录实体 | +| `LqHytkHytkEntity` | `lq_hytk_hytk` | ✅ 已存在 | 退卡记录实体 | +| `LqInventoryUsageEntity` | `lq_inventory_usage` | ✅ 已存在 | 库存使用记录实体 | +| `LqCooperationCostEntity` | `lq_cooperation_cost` | ✅ 已创建 | 合作成本实体 | +| `LqStoreExpenseEntity` | `lq_store_expense` | ✅ 已创建 | 店内支出实体 | +| `LqLaundryFlowEntity` | `lq_laundry_flow` | ✅ 已存在 | 清洗流水实体 | +| `LqMdTargetEntity` | `lq_md_target` | ✅ 已存在 | 门店目标实体 | +| `LqXhHyhkEntity` | `lq_xh_hyhk` | ✅ 已存在 | 耗卡记录实体 | +| `LqMdxxEntity` | `lq_mdxx` | ✅ 已存在 | 门店信息实体 | + +--- + +## ✅ 三、服务类检查 + +### 1. 数据查询服务 + +| 服务类 | 功能 | 状态 | 说明 | +|--------|------|------|------| +| `LqCooperationCostService` | 合作成本管理 | ✅ 已实现 | CRUD、导入、导出 | +| `LqStoreExpenseService` | 店内支出管理 | ✅ 已实现 | CRUD、导入、导出 | +| `LqReimbursementApplicationService` | 报销申请管理 | ✅ 已实现 | 已实现导出本月已审核通过的报销表明细 | +| `LqInventoryUsageService` | 库存使用管理 | ✅ 已存在 | 可查询仓库领用金额 | +| `LqLaundryFlowService` | 清洗流水管理 | ⚠️ 需确认 | 需确认是否有查询送洗记录的服务 | +| `LqKdKdjlbService` | 开单记录管理 | ✅ 已存在 | 可查询开单业绩 | +| `LqHytkHytkService` | 退卡记录管理 | ⚠️ 需确认 | 需确认是否有查询退卡记录的服务 | +| `LqMdTargetService` | 门店目标管理 | ⚠️ 需确认 | 需确认是否有查询门店目标的服务 | +| `LqMdxxService` | 门店信息管理 | ✅ 已存在 | 可查询门店信息、分类、类型 | + +--- + +## ⚠️ 四、待实现功能检查 + +### 1. 毛利计算接口 + +| 功能 | 状态 | 优先级 | +|------|------|--------| +| 销售业绩查询(开单业绩 - 退款业绩) | ❌ 未实现 | 高 | +| 产品物料查询(仓库领用金额,特殊规则:11月算10月) | ❌ 未实现 | 高 | +| 合作项目成本查询 | ✅ 已实现 | - | +| 店内支出查询 | ✅ 已实现 | - | +| 洗毛巾查询 | ❌ 未实现 | 高 | +| 毛利汇总计算接口 | ❌ 未实现 | 高 | + +### 2. 工资计算接口 + +| 功能 | 状态 | 优先级 | +|------|------|--------| +| 底薪计算(老店/新店,旗舰店特殊规则) | ❌ 未实现 | 中 | +| 提成计算(基于毛利,老店/新店不同规则) | ❌ 未实现 | 中 | +| 考核指标判断(业绩、人头、消耗) | ❌ 未实现 | 中 | +| 新店判断逻辑 | ❌ 未实现 | 中 | +| 旗舰店判断逻辑 | ⚠️ 需确认 | 中 | +| 完整工资计算接口 | ❌ 未实现 | 中 | + +--- + +## 📊 五、数据查询能力检查 + +### 1. 销售业绩查询 + +**需求**: +- 开单业绩:从 `lq_kd_kdjlb` 表查询 `sfyj` 字段,按门店ID和月份统计 +- 退款业绩:从 `lq_hytk_hytk` 表查询 `F_ActualRefundAmount` 字段,按门店ID和月份统计 +- 销售业绩 = 开单业绩 - 退款业绩 + +**状态**:❌ 需要实现查询接口 + +--- + +### 2. 产品物料查询 + +**需求**: +- 从 `lq_inventory_usage` 表查询 `F_TotalAmount` 字段 +- 按门店ID和月份统计 +- **特殊规则**:核算11月工资时,算的是10月份的仓库领用 + +**状态**:❌ 需要实现查询接口(注意11月特殊规则) + +--- + +### 3. 合作项目成本查询 + +**需求**: +- 从 `lq_cooperation_cost` 表查询 `F_TotalAmount` 字段 +- 按门店ID、年份、月份查询 + +**状态**:✅ 已实现(`LqCooperationCostService.GetList`) + +--- + +### 4. 店内支出查询 + +**需求**: +- 从 `lq_store_expense` 表查询 `F_Amount` 字段 +- 按门店ID和月份统计 + +**状态**:✅ 已实现(`LqStoreExpenseService.GetList`) + +--- + +### 5. 洗毛巾查询 + +**需求**: +- 从 `lq_laundry_flow` 表查询 `F_TotalPrice` 字段 +- 条件:`F_FlowType = 0`(送出) +- 按门店ID和月份统计 + +**状态**:❌ 需要实现查询接口 + +--- + +### 6. 门店目标查询 + +**需求**: +- 从 `lq_md_target` 表查询门店生命线、目标人头数、目标消耗 +- 按门店ID和月份查询 + +**状态**:⚠️ 需确认是否有查询接口 + +--- + +### 7. 新店判断 + +**需求**: +- 从 `lq_md_xdbhsj` 表查询新店保护信息 +- 判断统计月份的第一天是否在保护期内 + +**状态**:❌ 需要实现新店判断逻辑 + +--- + +### 8. 进店消耗人数统计 + +**需求**: +- 从 `lq_xh_hyhk` 表统计有消费金额的客户数 +- 按门店按月去重 + +**状态**:❌ 需要实现统计接口 + +--- + +### 9. 门店消耗统计 + +**需求**: +- 从 `lq_xh_jksyj` 表统计门店当月总消耗金额 +- 按门店和月份统计 + +**状态**:❌ 需要实现统计接口 + +--- + +## ✅ 六、已完成功能 + +### 1. 数据表创建 +- ✅ `lq_cooperation_cost` 表已创建 +- ✅ `lq_store_expense` 表已创建 + +### 2. 实体类创建 +- ✅ `LqCooperationCostEntity` 已创建 +- ✅ `LqStoreExpenseEntity` 已创建 + +### 3. 服务类实现 +- ✅ `LqCooperationCostService` 已实现(CRUD、导入、导出) +- ✅ `LqStoreExpenseService` 已实现(CRUD、导入、导出) +- ✅ `LqReimbursementApplicationService.ExportApprovedDetails` 已实现(导出报销表明细) + +--- + +## ❌ 七、待实现功能 + +### 1. 毛利计算接口(高优先级) + +需要实现以下接口: + +1. **销售业绩查询接口** + - 查询开单业绩(`lq_kd_kdjlb`) + - 查询退款业绩(`lq_hytk_hytk`) + - 计算销售业绩 = 开单业绩 - 退款业绩 + +2. **产品物料查询接口** + - 查询仓库领用金额(`lq_inventory_usage`) + - 特殊规则:11月工资算10月数据 + +3. **洗毛巾查询接口** + - 查询送洗记录总费用(`lq_laundry_flow`,`F_FlowType = 0`) + +4. **毛利汇总计算接口** + - 汇总所有组成部分 + - 计算:毛利 = 销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾 + +### 2. 工资计算接口(中优先级) + +需要实现以下接口: + +1. **门店目标查询接口** + - 查询门店生命线、目标人头数、目标消耗(`lq_md_target`) + +2. **新店判断接口** + - 判断门店是否为新店(`lq_md_xdbhsj`) + +3. **进店消耗人数统计接口** + - 统计有消费金额的客户数(`lq_xh_hyhk`) + +4. **门店消耗统计接口** + - 统计门店当月总消耗金额(`lq_xh_jksyj`) + +5. **底薪计算接口** + - 老店:3个考核指标(业绩、人头、消耗) + - 新店:2个考核指标(业绩、人头) + - 旗舰店:额外扣除800元 + +6. **提成计算接口** + - 老店:根据门店分类(A/B/C)和业绩是否达标 + - 新店:不区分分类,统一比例 + +7. **完整工资计算接口** + - 整合底薪和提成 + - 返回完整工资计算结果 + +--- + +## 📝 八、总结 + +### ✅ 已完成 +1. 合作成本表和店内支出表已创建并实现CRUD、导入、导出功能 +2. 报销表导出功能已实现 +3. 所有核心数据表都已存在 +4. 所有核心实体类都已存在 + +### ❌ 待实现 +1. **毛利计算接口**(高优先级) + - 销售业绩查询 + - 产品物料查询(注意11月特殊规则) + - 洗毛巾查询 + - 毛利汇总计算 + +2. **工资计算接口**(中优先级) + - 门店目标查询 + - 新店判断 + - 进店消耗人数统计 + - 门店消耗统计 + - 底薪计算 + - 提成计算 + - 完整工资计算 + +### ⚠️ 需确认 +1. 新店保护时间表(`lq_md_xdbhsj`)是否存在 +2. 是否有查询门店目标的服务 +3. 是否有查询送洗记录的服务 +4. 是否有查询退卡记录的服务 + +--- + +## 🚀 下一步行动 + +### 第一步:实现毛利计算接口 +1. 实现销售业绩查询接口 +2. 实现产品物料查询接口(注意11月特殊规则) +3. 实现洗毛巾查询接口 +4. 实现毛利汇总计算接口 + +### 第二步:实现工资计算接口 +1. 实现门店目标查询接口 +2. 实现新店判断逻辑 +3. 实现进店消耗人数统计接口 +4. 实现门店消耗统计接口 +5. 实现底薪计算接口 +6. 实现提成计算接口 +7. 实现完整工资计算接口 + +### 第三步:测试验证 +1. 测试毛利计算准确性 +2. 测试工资计算准确性 +3. 测试各种边界情况(新店、老店、旗舰店、业绩达标/未达标等) + diff --git a/店长工资计算规则-完整逻辑梳理.md b/店长工资计算规则-完整逻辑梳理.md new file mode 100644 index 0000000..cebafe6 --- /dev/null +++ b/店长工资计算规则-完整逻辑梳理.md @@ -0,0 +1,584 @@ +# 店长工资计算规则 - 完整逻辑梳理 + +## 📋 概述 + +店长工资由以下几个部分组成: +1. **底薪**:固定4000元,根据考核指标扣款 +2. **提成**:根据门店分类和业绩是否达标,使用不同的提成比例计算(基于毛利) + +--- + +## 💰 工资组成规则 + +### 1. 底薪规则 + +**固定底薪**:4000元 + +#### 老店店长底薪考核 + +**考核指标**(3个): +1. **业绩考核**:门店业绩是否达到门店生命线 +2. **人头考核**:进店消耗人数是否达到目标人头数 +3. **消耗考核**:门店消耗是否达到目标消耗 + +**扣款规则**: +- 每个指标未达到:扣除500元 +- 如果3个指标都未达到:扣除1500元(500 × 3) + +**计算公式**: +``` +底薪 = 4000 - (未达标指标数 × 500) +``` + +#### 新店店长底薪考核 + +**重要说明**:新店店长涉及全部阶段,都有负奖励机制 + +**考核指标**(2个): +1. **业绩考核**:门店业绩是否达到门店生命线 +2. **人头考核**:进店消耗人数是否达到目标人头数 + +**扣款规则**: +- 每个指标未达到:扣除800元 +- 如果2个指标都未达到:扣除1600元(800 × 2) + +**计算公式**: +``` +底薪 = 4000 - (未达标指标数 × 800) +``` + +**注意**:新店不考核消耗指标 + +**旗舰店特殊规则**: +- 旗舰店类型门店需要扣除负奖励800元 +- 判断方式:`lq_mdxx.F_StoreType = 2`(StoreTypeEnum.旗舰店) + +--- + +### 2. 提成规则 + +**提成计算方式**:根据门店分类和业绩是否达标,使用不同的提成比例 + +**重要说明**:提成计算基于**毛利**,不是门店业绩 + +#### 老店店长提成规则 + +根据门店分类(A、B、C类)和业绩是否达标,使用不同的提成比例: + +| 门店分类 | 业绩未达标 | 业绩达标 | +|---------|----------|---------| +| A类门店 | 3% | 3.5% | +| B类门店 | 3.5% | 4% | +| C类门店 | 4% | 4.5% | + +**业绩达标判断**: +- 业绩达标:门店业绩 ≥ 门店生命线 +- 业绩未达标:门店业绩 < 门店生命线 + +**计算公式**: +``` +如果 业绩 ≥ 生命线(业绩达标): + 提成 = 毛利 × 对应提成比例(业绩达标) + +如果 业绩 < 生命线(业绩未达标): + 提成 = 毛利 × 对应提成比例(业绩未达标) +``` + +#### 新店店长提成规则 + +**提成比例**(不区分门店分类): +- 业绩未达标:3% +- 业绩达标:3.5% + +**计算公式**: +``` +如果 业绩 ≥ 生命线(业绩达标): + 提成 = 毛利 × 3.5% + +如果 业绩 < 生命线(业绩未达标): + 提成 = 毛利 × 3% +``` + +--- + +## 📊 毛利计算公式 + +**核心公式**: +``` +毛利 = 销售业绩 - 产品物料 - 合作项目成本 - 店内支出 - 洗毛巾 +``` + +### 1. 销售业绩 + +**计算公式**: +``` +销售业绩 = 开单业绩 - 退款业绩 +``` + +**数据来源**: + +1. **开单业绩**: + - 表:`lq_kd_kdjlb`(开单记录表) + - 字段:`sfyj`(实付业绩) + - 条件: + - `F_IsEffective = 1`(有效记录) + - `Djmd = @StoreId`(门店ID) + - `DATE_FORMAT(Kdrq, '%Y%m') = @Month`(月份,YYYYMM格式) + +2. **退款业绩**: + - 表:`lq_hytk_hytk`(退卡记录表) + - 字段:`F_ActualRefundAmount`(实际退款金额) + - 条件: + - `F_IsEffective = 1`(有效记录) + - `md = @StoreId`(门店ID) + - `DATE_FORMAT(tksj, '%Y%m') = @Month`(月份,YYYYMM格式) + +**SQL示例**: +```sql +-- 开单业绩 +SELECT COALESCE(SUM(sfyj), 0) as BillingPerformance +FROM lq_kd_kdjlb +WHERE Djmd = @StoreId + AND DATE_FORMAT(Kdrq, '%Y%m') = @Month + AND F_IsEffective = 1 + +-- 退款业绩 +SELECT COALESCE(SUM(F_ActualRefundAmount), 0) as RefundPerformance +FROM lq_hytk_hytk +WHERE md = @StoreId + AND DATE_FORMAT(tksj, '%Y%m') = @Month + AND F_IsEffective = 1 + +-- 销售业绩 +销售业绩 = 开单业绩 - 退款业绩 +``` + +--- + +### 2. 产品物料 + +**计算公式**: +``` +产品物料 = 仓库领用金额合计 +``` + +**数据来源**: +- 表:`lq_inventory_usage`(库存使用记录表) +- 字段:`F_TotalAmount`(合计金额) +- 条件: + - `F_IsEffective = 1`(有效记录) + - `F_StoreId = @StoreId`(门店ID) + - **特殊规则**:核算11月工资时,算的是10月份的仓库领用 + - 如果计算月份是11月,则查询10月的数据 + - 其他月份正常查询当月数据 + +**SQL示例**: +```sql +-- 产品物料(特殊规则:11月工资算10月数据) +SET @QueryMonth = @Month; +IF @Month = '202411' THEN + SET @QueryMonth = '202410'; +END IF; + +SELECT COALESCE(SUM(F_TotalAmount), 0) as MaterialCost +FROM lq_inventory_usage +WHERE F_StoreId = @StoreId + AND DATE_FORMAT(F_UsageTime, '%Y%m') = @QueryMonth + AND F_IsEffective = 1 +``` + +--- + +### 3. 合作项目成本 + +**计算公式**: +``` +合作项目成本 = 合作成本表合计金额 +``` + +**数据来源**: +- 表:`lq_cooperation_cost`(合作成本表)**需要新建** +- 字段:`F_TotalAmount`(合计金额) +- 条件: + - `F_StoreId = @StoreId`(门店ID) + - `F_Year = @Year`(年份) + - `F_Month = @Month`(月份,YYYYMM格式) + +**表结构设计**: +```sql +CREATE TABLE IF NOT EXISTS `lq_cooperation_cost` ( + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID', + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)', + `F_StoreName` VARCHAR(200) NULL COMMENT '门店名称', + `F_Year` INT NOT NULL COMMENT '年份', + `F_Month` VARCHAR(6) NOT NULL COMMENT '月份(YYYYMM格式)', + `F_TotalAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '合计金额', + `F_Remarks` VARCHAR(1000) NULL COMMENT '备注说明', + `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)', + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID', + `F_CreateTime` DATETIME NULL COMMENT '创建时间', + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID', + `F_UpdateTime` DATETIME NULL COMMENT '更新时间', + PRIMARY KEY (`F_Id`), + KEY `idx_store_month` (`F_StoreId`, `F_Year`, `F_Month`), + KEY `idx_month` (`F_Year`, `F_Month`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合作成本表'; +``` + +**功能要求**: +- 支持按门店、年份、月份查询 +- 支持导入功能(Excel导入) +- 支持增删改查操作 + +--- + +### 4. 店内支出 + +**计算公式**: +``` +店内支出 = 店内支出表合计金额 +``` + +**数据来源**: +- 表:`lq_store_expense`(店内支出表)**需要新建** +- 字段:`F_Amount`(金额) +- 条件: + - `F_StoreId = @StoreId`(门店ID) + - `DATE_FORMAT(F_ExpenseDate, '%Y%m') = @Month`(月份,YYYYMM格式) + - `F_IsEffective = 1`(有效记录) + +**业务流程**: +1. 从报销表导出本月审核通过的报销表明细 + - 表:`lq_reimbursement_application`(报销申请表) + - 关联表:`lq_purchase_records`(购买记录表) + - 条件: + - `F_ApprovalStatus = '已通过'`(审批状态) + - `F_ApplicationStoreId = @StoreId`(门店ID) + - `DATE_FORMAT(F_ApplicationTime, '%Y%m') = @Month`(月份) +2. 线下重新整理明细(可能调整金额、分类等) +3. 导入到店内支出表 `lq_store_expense` + +**表结构设计**(参考购买记录表结构): +```sql +CREATE TABLE IF NOT EXISTS `lq_store_expense` ( + `F_Id` VARCHAR(50) NOT NULL COMMENT '主键ID', + `F_StoreId` VARCHAR(50) NOT NULL COMMENT '门店ID(关联lq_mdxx.F_Id)', + `F_StoreName` VARCHAR(200) NULL COMMENT '门店名称', + `F_ExpenseCategoryId` VARCHAR(50) NULL COMMENT '支出分类ID(关联lq_reimbursement_category.F_Id)', + `F_ExpenseCategoryName` VARCHAR(200) NULL COMMENT '支出分类名称', + `F_ExpenseDate` DATETIME NOT NULL COMMENT '支出日期', + `F_UnitPrice` DECIMAL(18,2) DEFAULT 0.00 COMMENT '单价', + `F_Quantity` INT DEFAULT 0 COMMENT '数量', + `F_Amount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '金额', + `F_Memo` VARCHAR(1000) NULL COMMENT '备注说明', + `F_Attachment` TEXT NULL COMMENT '附件(JSON格式)', + `F_RelatedReimbursementId` VARCHAR(50) NULL COMMENT '关联报销申请ID(可选)', + `F_RelatedPurchaseRecordId` VARCHAR(50) NULL COMMENT '关联购买记录ID(可选)', + `F_IsEffective` INT DEFAULT 1 COMMENT '是否有效(1:有效 0:无效)', + `F_CreateUser` VARCHAR(50) NULL COMMENT '创建人ID', + `F_CreateTime` DATETIME NULL COMMENT '创建时间', + `F_UpdateUser` VARCHAR(50) NULL COMMENT '更新人ID', + `F_UpdateTime` DATETIME NULL COMMENT '更新时间', + PRIMARY KEY (`F_Id`), + KEY `idx_store_date` (`F_StoreId`, `F_ExpenseDate`), + KEY `idx_expense_date` (`F_ExpenseDate`), + KEY `idx_category` (`F_ExpenseCategoryId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店内支出表'; +``` + +**功能要求**: +- 支持按门店、月份查询 +- 支持导出报销表明细(用于线下整理) +- 支持导入功能(Excel导入) +- 支持增删改查操作 + +--- + +### 5. 洗毛巾 + +**计算公式**: +``` +洗毛巾 = 送洗记录总费用 +``` + +**数据来源**: +- 表:`lq_laundry_flow`(清洗流水表) +- 字段:`F_TotalPrice`(总费用) +- 条件: + - `F_FlowType = 0`(流水类型:送出) + - `F_StoreId = @StoreId`(门店ID) + - `DATE_FORMAT(F_CreateTime, '%Y%m') = @Month`(月份,YYYYMM格式) + - `F_IsEffective = 1`(有效记录) + +**重要说明**: +- 只要送出就产生金额(不管是否送回) +- 只统计 `F_FlowType = 0`(送出)的记录 + +**SQL示例**: +```sql +SELECT COALESCE(SUM(F_TotalPrice), 0) as LaundryCost +FROM lq_laundry_flow +WHERE F_StoreId = @StoreId + AND F_FlowType = 0 + AND DATE_FORMAT(F_CreateTime, '%Y%m') = @Month + AND F_IsEffective = 1 +``` + +--- + +## 🔍 其他重要规则 + +### 1. 旗舰店判断 + +**判断方式**: +- 表:`lq_mdxx`(门店信息表) +- 字段:`F_StoreType`(门店类型) +- 枚举值: + - `1` = 门店200平(StoreTypeEnum.门店200平) + - `2` = 旗舰店(StoreTypeEnum.旗舰店) + +**代码实现**: +```csharp +// 判断是否为旗舰店 +bool isFlagshipStore = store.StoreType == (int)StoreTypeEnum.旗舰店; +``` + +**特殊规则**: +- 旗舰店类型门店需要扣除负奖励800元 + +--- + +### 2. 新店店长 + +**重要说明**: +- 新店店长所有阶段都有负奖励 +- 新店判断逻辑:需要根据门店开业时间或新店保护期判断(参考其他工资计算服务) + +--- + +### 3. 店长岗位名称 + +**系统标识**: +- 岗位名称:`"店长"` +- 查询条件:`BASE_USER.F_ZW = "店长"` 或 `BASE_USER.F_GW = "店长"` + +--- + +## 📋 数据来源总结 + +### 核心数据表 + +1. **门店信息表** (`lq_mdxx`) + - `F_Id`:门店ID + - `F_StoreType`:门店类型(判断旗舰店) + - `F_StoreCategory`:门店分类(A、B、C类) + +2. **门店目标表** (`lq_md_target`) + - `F_StoreId`:门店ID + - `F_Month`:月份(YYYYMM格式) + - `F_StoreLifeline`:门店生命线 + - `F_HeadcountTarget`:目标人头数 + - `F_ConsumeTarget`:目标消耗 + +3. **开单记录表** (`lq_kd_kdjlb`) + - `sfyj`:实付业绩(开单业绩) + - `Djmd`:门店ID + - `Kdrq`:开单日期 + +4. **退卡记录表** (`lq_hytk_hytk`) + - `F_ActualRefundAmount`:实际退款金额(退款业绩) + - `md`:门店ID + - `tksj`:退卡时间 + +5. **库存使用记录表** (`lq_inventory_usage`) + - `F_TotalAmount`:合计金额(产品物料) + - `F_StoreId`:门店ID + - `F_UsageTime`:使用时间 + +6. **合作成本表** (`lq_cooperation_cost`) **需要新建** + - `F_TotalAmount`:合计金额 + - `F_StoreId`:门店ID + - `F_Year`:年份 + - `F_Month`:月份 + +7. **店内支出表** (`lq_store_expense`) **需要新建** + - `F_Amount`:金额 + - `F_StoreId`:门店ID + - `F_ExpenseDate`:支出日期 + +8. **清洗流水表** (`lq_laundry_flow`) + - `F_TotalPrice`:总费用(洗毛巾) + - `F_StoreId`:门店ID + - `F_FlowType`:流水类型(0=送出) + +9. **耗卡记录表** (`lq_xh_hyhk`) + - 用于统计进店消耗人数 + +10. **系统用户表** (`BASE_USER`) + - `F_REALNAME`:姓名 + - `F_ZW`:职位(店长) + - `F_MDID`:门店ID + +--- + +## 🚀 需要先实现的功能 + +### 第一阶段:基础数据表 + +#### 1. 合作成本表 (`lq_cooperation_cost`) + +**功能清单**: +- [ ] 创建表结构(SQL) +- [ ] 创建实体类 (`LqCooperationCostEntity`) +- [ ] 创建DTO类(输入/输出) +- [ ] 创建Service类 (`LqCooperationCostService`) +- [ ] 实现CRUD接口 +- [ ] 实现导入功能(Excel导入) +- [ ] 实现查询接口(按门店、年份、月份) + +**优先级**:高(毛利计算必需) + +--- + +#### 2. 店内支出表 (`lq_store_expense`) + +**功能清单**: +- [ ] 创建表结构(SQL) +- [ ] 创建实体类 (`LqStoreExpenseEntity`) +- [ ] 创建DTO类(输入/输出) +- [ ] 创建Service类 (`LqStoreExpenseService`) +- [ ] 实现CRUD接口 +- [ ] 实现导出功能(导出报销表明细) +- [ ] 实现导入功能(Excel导入) +- [ ] 实现查询接口(按门店、月份) + +**优先级**:高(毛利计算必需) + +--- + +### 第二阶段:数据查询接口 + +#### 3. 毛利计算接口 + +**功能清单**: +- [ ] 实现销售业绩查询(开单业绩 - 退款业绩) +- [ ] 实现产品物料查询(仓库领用金额,特殊规则:11月算10月) +- [ ] 实现合作项目成本查询 +- [ ] 实现店内支出查询 +- [ ] 实现洗毛巾查询 +- [ ] 实现毛利汇总计算接口 + +**优先级**:高(工资计算必需) + +--- + +### 第三阶段:工资计算 + +#### 4. 店长工资计算服务 + +**功能清单**: +- [ ] 实现底薪计算(老店/新店,旗舰店特殊规则) +- [ ] 实现提成计算(基于毛利,老店/新店不同规则) +- [ ] 实现考核指标判断(业绩、人头、消耗) +- [ ] 实现新店判断逻辑 +- [ ] 实现旗舰店判断逻辑 +- [ ] 实现完整工资计算接口 + +**优先级**:中(依赖第一阶段和第二阶段) + +--- + +## 📝 实现顺序建议 + +### 步骤1:创建合作成本表 +1. 编写SQL创建表结构 +2. 创建实体类、DTO、Service +3. 实现CRUD接口 +4. 实现导入功能 + +### 步骤2:创建店内支出表 +1. 编写SQL创建表结构 +2. 创建实体类、DTO、Service +3. 实现CRUD接口 +4. 实现导出报销表明细功能 +5. 实现导入功能 + +### 步骤3:实现毛利计算接口 +1. 实现销售业绩查询 +2. 实现产品物料查询(注意11月特殊规则) +3. 实现合作项目成本查询 +4. 实现店内支出查询 +5. 实现洗毛巾查询 +6. 实现毛利汇总计算 + +### 步骤4:实现店长工资计算 +1. 实现底薪计算逻辑 +2. 实现提成计算逻辑 +3. 实现考核指标判断 +4. 实现完整工资计算接口 +5. 编写测试用例验证 + +--- + +## ⚠️ 注意事项 + +1. **产品物料特殊规则**: + - 核算11月工资时,算的是10月份的仓库领用 + - 需要在查询时特殊处理 + +2. **店内支出流程**: + - 必须先导出报销表明细 + - 线下整理后再导入 + - 不是直接从报销表计算 + +3. **洗毛巾计算**: + - 只要送出就产生金额 + - 只统计 `F_FlowType = 0`(送出)的记录 + +4. **旗舰店判断**: + - 使用 `F_StoreType = 2` 判断 + - 需要扣除负奖励800元 + +5. **新店店长**: + - 所有阶段都有负奖励 + - 需要确认新店判断逻辑 + +6. **数据一致性**: + - 所有金额计算保留2位小数 + - 确保月份格式统一(YYYYMM) + +--- + +## 📌 待确认问题 + +1. **新店判断逻辑**: + - 如何判断门店是否为新店? + - 是否有新店保护期字段? + - 参考其他工资计算服务的实现 + +2. **合作成本导入格式**: + - Excel导入的字段格式要求 + - 是否需要校验门店ID、年份、月份 + +3. **店内支出导入格式**: + - Excel导入的字段格式要求 + - 是否需要关联报销申请ID + +4. **毛利计算精度**: + - 各组成部分的精度要求 + - 最终毛利的精度要求 + +--- + +## 🔗 相关代码位置 + +- **门店信息查询**:`LqMdxxService.cs` +- **门店分类枚举**:`StoreCategoryEnum.cs` +- **门店类型枚举**:`StoreTypeEnum.cs` +- **门店实体类**:`LqMdxxEntity.cs` +- **库存使用服务**:`LqInventoryUsageService.cs` +- **清洗流水服务**:`LqLaundryFlowService.cs` +- **报销申请服务**:`LqReimbursementApplicationService.cs` +- **购买记录服务**:`LqPurchaseRecordsService.cs` +- **其他工资计算服务**:`LqAssistantSalaryService.cs`、`LqDirectorSalaryService.cs`(参考新店判断逻辑) + diff --git a/测试脚本-创建店长工资测试数据.sh b/测试脚本-创建店长工资测试数据.sh new file mode 100755 index 0000000..bac3da7 --- /dev/null +++ b/测试脚本-创建店长工资测试数据.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# 店长工资计算测试数据创建脚本 +# 门店ID:1649328471923847172(绿纤华润店) +# 员工ID:13881949169(易春梅) + +TOKEN="Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUzNDA4ODYsIm5iZiI6MTc2NTM0MDg4NiwiZXhwIjoxNzY1Mzk0ODg2LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.s6IjmTLVCF_Dmho3rXWiL1FE-wimA4ZTUYt_W8h4URk" + +STORE_ID="1649328471923847172" +STORE_NAME="绿纤华润店" +APPROVER_ID="13032810387" +LAUNDRY_SUPPLIER_ID="764256388720362757" +EXPENSE_CATEGORY_ID="11156146041171400" + +echo "=== 1. 创建合作成本数据(2025年11月)===" +curl -s -X POST "http://localhost:2011/api/Extend/LqCooperationCost" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"storeId\": \"${STORE_ID}\", + \"storeName\": \"${STORE_NAME}\", + \"year\": 2025, + \"month\": \"202511\", + \"totalAmount\": 3000.00, + \"remarks\": \"测试数据-合作成本\" + }" | jq . + +echo "" +echo "=== 2. 创建店内支出数据(2025年11月)===" +curl -s -X POST "http://localhost:2011/api/Extend/LqStoreExpense" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"storeId\": \"${STORE_ID}\", + \"storeName\": \"${STORE_NAME}\", + \"expenseCategoryId\": \"${EXPENSE_CATEGORY_ID}\", + \"expenseCategoryName\": \"日常支出\", + \"expenseDate\": \"2025-11-15T00:00:00\", + \"unitPrice\": 2000.00, + \"quantity\": 1, + \"amount\": 2000.00, + \"memo\": \"测试数据-店内支出\" + }" | jq . + +echo "" +echo "=== 3. 创建洗毛巾费用数据(2025年11月,送出)===" +curl -s -X POST "http://localhost:2011/api/Extend/LqLaundryFlow/Send" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"storeId\": \"${STORE_ID}\", + \"productType\": \"毛巾\", + \"laundrySupplierId\": \"${LAUNDRY_SUPPLIER_ID}\", + \"quantity\": 100, + \"remark\": \"测试数据-洗毛巾\" + }" | jq . + +echo "" +echo "=== 4. 创建产品物料数据(2025年10月,用于11月工资计算)===" +# 先查询一个产品ID +PRODUCT_ID=$(curl -s -X GET "http://localhost:2011/api/Extend/LqProduct?currentPage=1&pageSize=1" \ + -H "Authorization: ${TOKEN}" | jq -r '.data.list[0].id // empty') + +if [ -z "$PRODUCT_ID" ]; then + echo "警告:未找到产品,跳过产品物料数据创建" +else + echo "使用产品ID: ${PRODUCT_ID}" + curl -s -X POST "http://localhost:2011/api/Extend/LqInventoryUsage/BatchCreate" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"approverId\": \"${APPROVER_ID}\", + \"applicationStoreId\": \"${STORE_ID}\", + \"remarks\": \"测试数据-产品物料(10月)\", + \"usageItems\": [ + { + \"productId\": \"${PRODUCT_ID}\", + \"storeId\": \"${STORE_ID}\", + \"usageTime\": \"2025-10-15T00:00:00\", + \"usageQuantity\": 10 + } + ] + }" | jq . +fi + +echo "" +echo "=== 5. 重新计算2025年11月店长工资 ===" +curl -s -X POST "http://localhost:2011/api/Extend/LqStoreManagerSalary/calculate/store-manager?year=2025&month=11" \ + -H "Authorization: ${TOKEN}" | jq . + +echo "" +echo "=== 6. 查询计算结果 ===" +curl -s -X GET "http://localhost:2011/api/Extend/LqStoreManagerSalary/store-manager?Year=2025&Month=11¤tPage=1&pageSize=5" \ + -H "Authorization: ${TOKEN}" | jq '.data.list[0] | { + StoreName, + EmployeeName, + SalesPerformance, + ProductMaterial, + CooperationCost, + StoreExpense, + LaundryCost, + GrossProfit, + BaseSalary, + ActualBaseSalary, + CommissionRate, + CommissionAmount, + GrossSalary, + IsNewStore, + StoreCategory, + StoreType + }' + diff --git a/测试脚本-合作成本表和店内支出表.sh b/测试脚本-合作成本表和店内支出表.sh new file mode 100755 index 0000000..2bbe52f --- /dev/null +++ b/测试脚本-合作成本表和店内支出表.sh @@ -0,0 +1,200 @@ +#!/bin/bash + +# 测试脚本 - 合作成本表和店内支出表 +# 使用方法:./测试脚本-合作成本表和店内支出表.sh +# 示例:./测试脚本-合作成本表和店内支出表.sh "Bearer xxx" "http://localhost:2011" + +TOKEN=${1:-"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJhZG1pbiIsIkFjY291bnQiOiJhZG1pbiIsIlVzZXJOYW1lIjoi566h55CG5ZGYIiwiQWRtaW5pc3RyYXRvciI6MSwiVGVuYW50SWQiOiJkYiIsIlRlbmFudERiTmFtZSI6ImxxZXJwX3Rlc3QiLCJpYXQiOjE3NjUyOTAwNTQsIm5iZiI6MTc2NTI5MDA1NCwiZXhwIjoxNzY1MzQ0MDU0LCJpc3MiOiJ5aW5tYWlzb2Z0IiwiYXVkIjoieWlubWFpc29mdCJ9.d68JfuAnxusEWSzloJu4uFPd5uK-Tf8_AA2gBPui-k4"} +BASE_URL=${2:-"http://localhost:2011"} + +echo "==========================================" +echo "开始测试 - 合作成本表和店内支出表" +echo "==========================================" +echo "" + +# 测试门店ID +STORE_ID_1="1649328471923847168" # 绿纤总部 +STORE_ID_2="1649328471923847169" # 绿纤紫荆店 +STORE_ID_3="1649328471923847170" # 绿纤龙湖店 + +# 颜色输出 +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 测试函数 +test_api() { + local name=$1 + local method=$2 + local url=$3 + local data=$4 + + echo -e "${YELLOW}测试: ${name}${NC}" + echo "请求: ${method} ${url}" + if [ -n "$data" ]; then + echo "数据: ${data}" + response=$(curl -s -w "\n%{http_code}" -X ${method} "${BASE_URL}${url}" \ + -H "Authorization: ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "${data}") + else + response=$(curl -s -w "\n%{http_code}" -X ${method} "${BASE_URL}${url}" \ + -H "Authorization: ${TOKEN}") + fi + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" -eq 200 ] || [ "$http_code" -eq 201 ]; then + echo -e "${GREEN}✓ 成功 (HTTP ${http_code})${NC}" + echo "响应: ${body}" | jq . 2>/dev/null || echo "响应: ${body}" + echo "" + echo "$body" + else + echo -e "${RED}✗ 失败 (HTTP ${http_code})${NC}" + echo "响应: ${body}" + echo "" + echo "" + fi +} + +# ========================================== +# 第一阶段:合作成本表 CRUD 测试 +# ========================================== +echo "==========================================" +echo "第一阶段:合作成本表 CRUD 测试" +echo "==========================================" +echo "" + +# 1.1 创建合作成本记录 +echo "1.1 创建合作成本记录" +COOPERATION_COST_DATA='{"storeId":"'${STORE_ID_1}'","storeName":"绿纤总部","year":2025,"month":"202501","totalAmount":5000.00,"remarks":"CRUD测试-创建"}' +CREATE_RESPONSE=$(test_api "创建合作成本记录" "POST" "/api/Extend/LqCooperationCost" "${COOPERATION_COST_DATA}") +COOPERATION_COST_ID=$(echo "$CREATE_RESPONSE" | jq -r '.id // empty' 2>/dev/null) + +if [ -z "$COOPERATION_COST_ID" ]; then + echo "无法获取创建的记录ID,尝试从响应中提取..." + COOPERATION_COST_ID=$(echo "$CREATE_RESPONSE" | grep -oP '"id"\s*:\s*"[^"]*"' | head -1 | cut -d'"' -f4) +fi + +echo "创建的记录ID: ${COOPERATION_COST_ID}" +echo "" + +# 1.2 查询合作成本列表 +echo "1.2 查询合作成本列表" +test_api "查询合作成本列表" "GET" "/api/Extend/LqCooperationCost?currentPage=1&pageSize=10&storeId=${STORE_ID_1}" "" +echo "" + +# 1.3 查询合作成本详情 +if [ -n "$COOPERATION_COST_ID" ]; then + echo "1.3 查询合作成本详情" + test_api "查询合作成本详情" "GET" "/api/Extend/LqCooperationCost/${COOPERATION_COST_ID}" "" + echo "" + + # 1.4 更新合作成本记录 + echo "1.4 更新合作成本记录" + UPDATE_DATA='{"id":"'${COOPERATION_COST_ID}'","storeId":"'${STORE_ID_1}'","storeName":"绿纤总部","year":2025,"month":"202501","totalAmount":6000.00,"remarks":"CRUD测试-更新"}' + test_api "更新合作成本记录" "PUT" "/api/Extend/LqCooperationCost/${COOPERATION_COST_ID}" "${UPDATE_DATA}" + echo "" +fi + +# ========================================== +# 第二阶段:店内支出表 CRUD 测试 +# ========================================== +echo "==========================================" +echo "第二阶段:店内支出表 CRUD 测试" +echo "==========================================" +echo "" + +# 2.1 创建店内支出记录 +echo "2.1 创建店内支出记录" +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":""}' +CREATE_EXPENSE_RESPONSE=$(test_api "创建店内支出记录" "POST" "/api/Extend/LqStoreExpense" "${STORE_EXPENSE_DATA}") +STORE_EXPENSE_ID=$(echo "$CREATE_EXPENSE_RESPONSE" | jq -r '.id // empty' 2>/dev/null) + +if [ -z "$STORE_EXPENSE_ID" ]; then + echo "无法获取创建的记录ID,尝试从响应中提取..." + STORE_EXPENSE_ID=$(echo "$CREATE_EXPENSE_RESPONSE" | grep -oP '"id"\s*:\s*"[^"]*"' | head -1 | cut -d'"' -f4) +fi + +echo "创建的记录ID: ${STORE_EXPENSE_ID}" +echo "" + +# 2.2 查询店内支出列表 +echo "2.2 查询店内支出列表" +test_api "查询店内支出列表" "GET" "/api/Extend/LqStoreExpense?currentPage=1&pageSize=10&storeId=${STORE_ID_1}" "" +echo "" + +# 2.3 查询店内支出详情 +if [ -n "$STORE_EXPENSE_ID" ]; then + echo "2.3 查询店内支出详情" + test_api "查询店内支出详情" "GET" "/api/Extend/LqStoreExpense/${STORE_EXPENSE_ID}" "" + echo "" + + # 2.4 更新店内支出记录 + echo "2.4 更新店内支出记录" + 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":""}' + test_api "更新店内支出记录" "PUT" "/api/Extend/LqStoreExpense/${STORE_EXPENSE_ID}" "${UPDATE_EXPENSE_DATA}" + echo "" +fi + +# ========================================== +# 第三阶段:Excel导入测试 +# ========================================== +echo "==========================================" +echo "第三阶段:Excel导入测试" +echo "==========================================" +echo "" + +# 3.1 导入合作成本表 +echo "3.1 导入合作成本表" +echo "注意:需要手动执行以下命令(需要提供Excel文件路径)" +echo "curl -X POST \"${BASE_URL}/api/Extend/LqCooperationCost/Actions/Import\" \\" +echo " -H \"Authorization: ${TOKEN}\" \\" +echo " -F \"file=@excel/合作成本表.xlsx\"" +echo "" + +# 3.2 导入店内支出表 +echo "3.2 导入店内支出表" +echo "注意:需要手动执行以下命令(需要提供Excel文件路径)" +echo "curl -X POST \"${BASE_URL}/api/Extend/LqStoreExpense/Actions/Import\" \\" +echo " -H \"Authorization: ${TOKEN}\" \\" +echo " -F \"file=@excel/店内支出表.xlsx\"" +echo "" + +# ========================================== +# 第四阶段:Excel导出测试 +# ========================================== +echo "==========================================" +echo "第四阶段:Excel导出测试" +echo "==========================================" +echo "" + +# 4.1 导出合作成本表 +echo "4.1 导出合作成本表" +test_api "导出合作成本表" "GET" "/api/Extend/LqCooperationCost/Actions/Export?dataType=1&storeId=${STORE_ID_1}&year=2025&month=202501" "" +echo "" + +# 4.2 导出店内支出表 +echo "4.2 导出店内支出表" +test_api "导出店内支出表" "GET" "/api/Extend/LqStoreExpense/Actions/Export?dataType=1&storeId=${STORE_ID_1}" "" +echo "" + +# ========================================== +# 第五阶段:报销表导出测试 +# ========================================== +echo "==========================================" +echo "第五阶段:报销表导出测试" +echo "==========================================" +echo "" + +# 5.1 导出报销表明细 +echo "5.1 导出报销表明细(本月已审核通过的)" +test_api "导出报销表明细" "GET" "/api/Extend/LqReimbursementApplication/Actions/ExportApprovedDetails?year=2025&month=01" "" +echo "" + +echo "==========================================" +echo "测试完成" +echo "==========================================" + diff --git a/测试计划-合作成本表和店内支出表.md b/测试计划-合作成本表和店内支出表.md new file mode 100644 index 0000000..a4b672a --- /dev/null +++ b/测试计划-合作成本表和店内支出表.md @@ -0,0 +1,350 @@ +# 测试计划 - 合作成本表和店内支出表 + +## 📋 测试范围 + +1. **合作成本表** + - CRUD操作(创建、查询、更新、删除) + - Excel导入功能 + - Excel导出功能 + +2. **店内支出表** + - CRUD操作(创建、查询、更新、删除) + - Excel导入功能 + - Excel导出功能 + +3. **报销表导出功能** + - 导出本月已审核通过的报销表明细 + +--- + +## 🧪 测试步骤 + +### 第一阶段:基础功能测试 + +#### 1. 合作成本表 - CRUD测试 + +**1.1 创建合作成本记录** +```bash +POST /api/Extend/LqCooperationCost +Content-Type: application/json + +{ + "storeId": "门店ID", + "storeName": "测试门店", + "year": 2025, + "month": "202501", + "totalAmount": 5000.00, + "remarks": "测试数据" +} +``` + +**预期结果**:创建成功,返回记录ID + +**1.2 查询合作成本列表** +```bash +GET /api/Extend/LqCooperationCost?currentPage=1&pageSize=10&storeId=门店ID +``` + +**预期结果**:返回分页列表,包含刚创建的记录 + +**1.3 查询合作成本详情** +```bash +GET /api/Extend/LqCooperationCost/{id} +``` + +**预期结果**:返回记录详情 + +**1.4 更新合作成本记录** +```bash +PUT /api/Extend/LqCooperationCost/{id} +Content-Type: application/json + +{ + "id": "记录ID", + "storeId": "门店ID", + "storeName": "测试门店", + "year": 2025, + "month": "202501", + "totalAmount": 6000.00, + "remarks": "更新后的测试数据" +} +``` + +**预期结果**:更新成功 + +**1.5 删除合作成本记录** +```bash +DELETE /api/Extend/LqCooperationCost/{id} +``` + +**预期结果**:逻辑删除成功(IsEffective = 0) + +--- + +#### 2. 店内支出表 - CRUD测试 + +**2.1 创建店内支出记录** +```bash +POST /api/Extend/LqStoreExpense +Content-Type: application/json + +{ + "storeId": "门店ID", + "storeName": "测试门店", + "expenseCategoryId": "", + "expenseCategoryName": "办公用品", + "expenseDate": "2025-01-15T00:00:00", + "unitPrice": 10.50, + "quantity": 5, + "amount": 52.50, + "memo": "测试支出", + "attachment": [], + "relatedReimbursementId": "", + "relatedPurchaseRecordId": "" +} +``` + +**预期结果**:创建成功 + +**2.2 查询店内支出列表** +```bash +GET /api/Extend/LqStoreExpense?currentPage=1&pageSize=10&storeId=门店ID +``` + +**预期结果**:返回分页列表 + +**2.3 查询店内支出详情** +```bash +GET /api/Extend/LqStoreExpense/{id} +``` + +**预期结果**:返回记录详情 + +**2.4 更新店内支出记录** +```bash +PUT /api/Extend/LqStoreExpense/{id} +Content-Type: application/json + +{ + "id": "记录ID", + "storeId": "门店ID", + "storeName": "测试门店", + "expenseCategoryId": "", + "expenseCategoryName": "水电费", + "expenseDate": "2025-01-20T00:00:00", + "unitPrice": 0, + "quantity": 0, + "amount": 500.00, + "memo": "更新后的测试支出", + "attachment": [], + "relatedReimbursementId": "", + "relatedPurchaseRecordId": "" +} +``` + +**预期结果**:更新成功 + +**2.5 删除店内支出记录** +```bash +DELETE /api/Extend/LqStoreExpense/{id} +``` + +**预期结果**:逻辑删除成功 + +--- + +### 第二阶段:Excel导入测试 + +#### 3. 合作成本表 - Excel导入测试 + +**3.1 准备Excel文件** +- 按照 `Excel导入格式说明.md` 中的格式创建Excel文件 +- 包含至少3条测试数据 +- 包含1条错误数据(用于测试错误处理) + +**3.2 执行导入** +```bash +POST /api/Extend/LqCooperationCost/Actions/Import +Content-Type: multipart/form-data + +file: Excel文件 +``` + +**预期结果**: +- 成功导入有效数据 +- 错误数据被跳过并返回错误信息 +- 返回导入结果统计 + +**3.3 验证导入数据** +```bash +GET /api/Extend/LqCooperationCost?storeId=门店ID&year=2025&month=202501 +``` + +**预期结果**:查询到导入的数据 + +--- + +#### 4. 店内支出表 - Excel导入测试 + +**4.1 准备Excel文件** +- 按照 `Excel导入格式说明.md` 中的格式创建Excel文件 +- 包含至少3条测试数据 +- 包含1条错误数据(用于测试错误处理) + +**4.2 执行导入** +```bash +POST /api/Extend/LqStoreExpense/Actions/Import +Content-Type: multipart/form-data + +file: Excel文件 +``` + +**预期结果**: +- 成功导入有效数据 +- 错误数据被跳过并返回错误信息 +- 返回导入结果统计 + +**4.3 验证导入数据** +```bash +GET /api/Extend/LqStoreExpense?storeId=门店ID +``` + +**预期结果**:查询到导入的数据 + +--- + +### 第三阶段:Excel导出测试 + +#### 5. 合作成本表 - Excel导出测试 + +**5.1 执行导出** +```bash +GET /api/Extend/LqCooperationCost/Actions/Export?dataType=1&storeId=门店ID&year=2025&month=202501 +``` + +**预期结果**: +- 返回Excel文件下载链接 +- 下载的文件包含所有查询到的数据 +- Excel格式正确 + +--- + +#### 6. 店内支出表 - Excel导出测试 + +**6.1 执行导出** +```bash +GET /api/Extend/LqStoreExpense/Actions/Export?dataType=1&storeId=门店ID +``` + +**预期结果**: +- 返回Excel文件下载链接 +- 下载的文件包含所有查询到的数据 +- Excel格式正确 + +--- + +### 第四阶段:报销表导出测试 + +#### 7. 报销表导出测试 + +**7.1 准备测试数据** +- 确保有至少1条已审核通过的报销申请(本月) +- 该报销申请关联了购买记录 + +**7.2 执行导出** +```bash +GET /api/Extend/LqReimbursementApplication/Actions/ExportApprovedDetails?year=2025&month=01 +``` + +**预期结果**: +- 返回Excel文件下载链接 +- 下载的文件包含: + - 报销申请基本信息(申请ID、申请人、门店、申请时间、申请金额) + - 关联的购买记录明细(每条购买记录一行) +- Excel格式正确 + +--- + +## ✅ 测试检查清单 + +### 合作成本表 +- [ ] 创建记录成功 +- [ ] 查询列表成功(分页) +- [ ] 查询详情成功 +- [ ] 更新记录成功 +- [ ] 删除记录成功(逻辑删除) +- [ ] Excel导入成功(有效数据) +- [ ] Excel导入错误处理正确(无效数据被跳过) +- [ ] Excel导出成功 +- [ ] 导出文件格式正确 + +### 店内支出表 +- [ ] 创建记录成功 +- [ ] 查询列表成功(分页) +- [ ] 查询详情成功 +- [ ] 更新记录成功 +- [ ] 删除记录成功(逻辑删除) +- [ ] Excel导入成功(有效数据) +- [ ] Excel导入错误处理正确(无效数据被跳过) +- [ ] Excel导出成功 +- [ ] 导出文件格式正确 + +### 报销表导出 +- [ ] 导出本月已审核通过的报销申请 +- [ ] 包含购买记录明细 +- [ ] 包含门店、金额等信息 +- [ ] Excel格式正确 + +--- + +## 🐛 常见问题处理 + +### 1. 导入失败 - 门店ID不存在 +**问题**:导入时提示门店ID不存在 +**解决**:检查门店ID是否正确,可以通过门店列表接口获取正确的门店ID + +### 2. 导入失败 - 月份格式错误 +**问题**:提示月份格式错误 +**解决**:月份必须是YYYYMM格式(6位字符串),如:202501 + +### 3. 导入失败 - 日期格式错误 +**问题**:店内支出表导入时提示日期格式错误 +**解决**:日期必须使用标准格式,推荐:YYYY-MM-DD,如:2025-01-15 + +### 4. 导入失败 - 重复数据 +**问题**:合作成本表导入时提示记录已存在 +**解决**:系统会检查相同门店、年份、月份是否已存在,如果存在会跳过该条记录 + +--- + +## 📝 测试数据准备 + +### 需要准备的数据 + +1. **门店ID**:至少2个有效的门店ID(用于测试) +2. **Excel文件**: + - 合作成本表导入文件(至少3条有效数据 + 1条错误数据) + - 店内支出表导入文件(至少3条有效数据 + 1条错误数据) +3. **报销申请**:至少1条本月已审核通过的报销申请(关联购买记录) + +### 获取门店ID的方法 + +```bash +GET /api/Extend/LqMdxx/Selector +``` + +返回结果中包含门店ID和名称。 + +--- + +## 🚀 开始测试 + +请按照以下步骤进行: + +1. **准备Excel文件**:根据 `Excel导入格式说明.md` 创建Excel文件 +2. **填写测试数据**:我会在您创建的Excel文件中填写测试数据 +3. **执行测试**:按照测试步骤逐一执行测试 +4. **验证结果**:检查每个测试点的预期结果 + +准备好Excel文件后,请告诉我,我会填写测试数据并开始测试。 +