diff --git a/docs/lq_xh_hyhk备注字段-接口说明.md b/docs/lq_xh_hyhk备注字段-接口说明.md
new file mode 100644
index 0000000..b5b48a6
--- /dev/null
+++ b/docs/lq_xh_hyhk备注字段-接口说明.md
@@ -0,0 +1,25 @@
+# lq_xh_hyhk 表备注字段 - 接口说明
+
+## 📡 修改的接口列表
+
+### 1. 创建耗卡记录
+**接口地址**: `POST /api/Extend/LqXhHyhk`
+**接口说明**: 创建新的耗卡记录,支持传入备注信息(可选,最大2000字符)
+### 2. 更新耗卡记录
+**接口地址**: `PUT /api/Extend/LqXhHyhk/{id}`
+**接口说明**: 更新耗卡记录信息,支持修改备注(可选,最大2000字符)
+### 3. 更新耗卡备注
+**接口地址**: `PUT /api/Extend/LqXhHyhk/{id}/UpdateRemark`
+**接口说明**: 专门用于更新耗卡记录的备注信息
+### 4. 查询耗卡详情
+**接口地址**: `GET /api/Extend/LqXhHyhk/{id}`
+**接口说明**: 根据ID查询耗卡记录详情,返回包含备注信息
+### 5. 查询耗卡列表
+**接口地址**: `GET /api/Extend/LqXhHyhk`
+**接口说明**: 分页查询耗卡记录列表,返回包含备注信息
+### 6. 按健康师ID查询耗卡列表
+**接口地址**: `GET /api/Extend/LqXhHyhk/GetListByJksId`
+**接口说明**: 根据健康师ID查询耗卡记录列表,返回包含备注信息
+### 7. 按科技部老师ID查询耗卡列表
+**接口地址**: `GET /api/Extend/LqXhHyhk/GetListByKjbId`
+**接口说明**: 根据科技部老师ID查询耗卡记录列表,返回包含备注信息
\ No newline at end of file
diff --git a/docs/lq_xh_hyhk表添加备注字段-修改清单.md b/docs/lq_xh_hyhk表添加备注字段-修改清单.md
new file mode 100644
index 0000000..020409e
--- /dev/null
+++ b/docs/lq_xh_hyhk表添加备注字段-修改清单.md
@@ -0,0 +1,412 @@
+# lq_xh_hyhk 表添加备注字段 - 修改清单
+
+## 📋 概述
+
+在 `lq_xh_hyhk`(会员耗卡)表中添加备注字段,用于记录耗卡相关的备注信息。
+
+## 🗄️ 数据库修改
+
+### 1. 数据库表结构修改
+
+**表名**: `lq_xh_hyhk`
+
+**新增字段**:
+- **字段名**: `F_Remark`
+- **数据类型**: `VARCHAR(500)` 或 `TEXT`(根据备注长度需求)
+- **是否可空**: `YES`
+- **默认值**: `NULL`
+- **字段注释**: `备注`
+
+**SQL语句**:
+```sql
+ALTER TABLE lq_xh_hyhk
+ADD COLUMN F_Remark VARCHAR(500) NULL COMMENT '备注' AFTER F_CancelRemark;
+```
+
+## 💻 后端代码修改
+
+### 2. 实体类 (Entity)
+
+**文件**: `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_hyhk/LqXhHyhkEntity.cs`
+
+**修改内容**: 在 `LqXhHyhkEntity` 类中添加备注属性
+
+```csharp
+///
+/// 备注
+///
+[SugarColumn(ColumnName = "F_Remark")]
+public string Remark { get; set; }
+```
+
+**位置**: 建议添加在 `CancelRemark` 字段之后(第128行之后)
+
+---
+
+### 3. DTO 类修改
+
+#### 3.1 创建输入DTO
+
+**文件**: `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkCrInput.cs`
+
+**修改内容**: 添加备注字段
+
+```csharp
+///
+/// 备注
+///
+public string remark { get; set; }
+```
+
+**位置**: 建议添加在 `appointmentId` 字段之后(第90行之后)
+
+---
+
+#### 3.2 详情输出DTO
+
+**文件**: `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkInfoOutput.cs`
+
+**修改内容**: 添加备注字段
+
+```csharp
+///
+/// 备注
+///
+public string remark { get; set; }
+```
+
+**位置**: 建议添加在 `cancelRemark` 字段之后(第113行之后)
+
+---
+
+#### 3.3 列表输出DTO
+
+**文件**: `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs`
+
+**修改内容**: 添加备注字段
+
+```csharp
+///
+/// 备注
+///
+public string remark { get; set; }
+```
+
+**位置**: 建议添加在 `signatureFile` 字段之后(第95行之后)
+
+---
+
+**注意**: `LqXhHyhkUpInput` 继承自 `LqXhHyhkCrInput`,会自动包含备注字段,无需单独修改。
+
+---
+
+### 4. Service 方法修改
+
+**文件**: `netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs`
+
+#### 4.1 GetInfo 方法(查询详情)
+
+**位置**: 第101行 `GetInfo(string id)`
+
+**修改说明**:
+- 使用 `entity.Adapt()` 自动映射,无需手动修改
+- 但需要确保 `LqXhHyhkInfoOutput` 已包含 `remark` 字段
+
+**影响**: ✅ 自动支持(通过 Adapt 映射)
+
+---
+
+#### 4.2 GetList 方法(查询列表)
+
+**位置**: 第293行 `GetList([FromQuery] LqXhHyhkListQueryInput input)`
+
+**修改内容**: 在多个 `Select` 语句中添加 `remark` 字段
+
+**需要修改的位置**:
+
+1. **第342-370行**: 基础查询的 Select 语句
+ ```csharp
+ remark = it.Remark,
+ ```
+
+2. **第590-611行**: 按健康师ID查询的 Select 语句(GetListByJksId)
+ ```csharp
+ remark = hyhk.Remark,
+ ```
+
+3. **第720-741行**: 按科技部老师ID查询的 Select 语句(GetListByKjbId)
+ ```csharp
+ remark = hyhk.Remark,
+ ```
+
+4. **第1142-1165行**: 其他查询条件的 Select 语句
+ ```csharp
+ remark = it.Remark,
+ ```
+
+---
+
+#### 4.3 Create 方法(创建记录)
+
+**位置**: 第875行 `Create([FromBody] LqXhHyhkCrInput input)`
+
+**修改说明**:
+- 使用 `input.Adapt()` 自动映射,无需手动修改
+- 但需要确保 `LqXhHyhkCrInput` 已包含 `remark` 字段
+
+**影响**: ✅ 自动支持(通过 Adapt 映射)
+
+---
+
+#### 4.4 Update 方法(更新记录)
+
+**位置**: 第1259行 `Update(string id, [FromBody] LqXhHyhkUpInput input)`
+
+**修改说明**:
+- 使用 `input.Adapt()` 自动映射,无需手动修改
+- 但需要确保 `LqXhHyhkUpInput`(继承自 `LqXhHyhkCrInput`)已包含 `remark` 字段
+
+**影响**: ✅ 自动支持(通过 Adapt 映射)
+
+---
+
+#### 4.5 其他方法检查
+
+以下方法可能也需要检查,但根据代码分析,它们主要使用实体类,会自动支持:
+
+- `Delete` 方法(第1504行)- 删除操作,无需修改
+- `Cancel` 方法(第1618行)- 作废操作,可能需要显示备注,但不需要修改逻辑
+- `ExportConsumeItemDetailList` 方法(第2340行)- 导出功能,如果导出包含备注,需要修改
+
+---
+
+### 5. 其他 Service 检查
+
+以下 Service 可能使用了 `LqXhHyhkEntity`,需要检查是否需要修改:
+
+1. **LqStatisticsService.cs** - 统计服务
+ - 检查是否有统计查询使用备注字段
+ - 通常统计不需要备注,可能无需修改
+
+2. **LqTechDepartmentDashboardService.cs** - 科技部驾驶舱
+ - 检查是否有查询使用备注字段
+ - 通常驾驶舱不需要备注,可能无需修改
+
+3. **LqStoreDashboardService.cs** - 门店驾驶舱
+ - 检查是否有查询使用备注字段
+ - 通常驾驶舱不需要备注,可能无需修改
+
+4. **LqTechTeacherSalaryService.cs** - 科技部老师工资
+ - 检查是否有查询使用备注字段
+ - 通常工资计算不需要备注,可能无需修改
+
+5. **LqKhxxService.cs** - 客户资料服务
+ - 检查是否有关联查询使用备注字段
+
+6. **LqTkjlbService.cs** - 拓客记录服务
+ - 检查是否有关联查询使用备注字段
+
+7. **LqBusinessUnitDashboardService.cs** - 事业部驾驶舱
+ - 检查是否有查询使用备注字段
+
+8. **LqReportService.cs** - 报表服务
+ - 检查是否有报表使用备注字段
+
+9. **MemberPortraitService.cs** - 会员画像服务
+ - 检查是否有查询使用备注字段
+
+10. **LqXmzlService.cs** - 项目资料服务
+ - 检查是否有关联查询使用备注字段
+
+11. **LqXhFeedbackService.cs** - 耗卡反馈服务
+ - 检查是否有关联查询使用备注字段
+
+**建议**: 这些服务通常不需要备注字段,但需要根据实际业务需求确认。
+
+---
+
+## 🎨 前端代码修改
+
+### 6. 前端页面修改
+
+#### 6.1 表单页面(新建/编辑)
+
+**文件**: `antis-ncc-admin/src/views/lqXhHyhk/Form.vue`
+
+**修改内容**:
+- 在表单中添加备注输入框
+- 位置建议:在"费用信息"区域之后,"品项明细"之前
+
+**代码示例**:
+```vue
+
+
+
+
+
+
+```
+
+**需要修改的位置**:
+- 在 `dataForm` 对象中添加 `remark: ''`
+- 在表单模板中添加备注输入框(建议在第86行之后,第94行之前)
+
+---
+
+#### 6.2 详情页面
+
+**文件**: `antis-ncc-admin/src/views/lqXhHyhk/detail.vue`
+
+**修改内容**:
+- 在详情页面中显示备注信息
+- 位置建议:在"费用信息"卡片之后,或单独一个"备注信息"卡片
+
+**代码示例**:
+```vue
+
+
+
+
+
+
+
+```
+
+**需要修改的位置**:
+- 在详情数据绑定中添加备注字段的显示(建议在第88行之后)
+
+---
+
+#### 6.3 列表页面
+
+**文件**: `antis-ncc-admin/src/views/lqXhHyhk/index.vue`
+
+**修改内容**:
+- 在列表表格中添加备注列(可选,根据业务需求)
+- 如果备注内容较长,可以考虑使用 tooltip 显示完整内容
+
+**代码示例**:
+```vue
+
+
+ {{ scope.row.remark || '无' }}
+
+
+```
+
+**需要修改的位置**:
+- 在表格列定义中添加备注列(根据实际表格结构确定位置)
+
+---
+
+### 7. 导出功能检查
+
+**文件**: `netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs`
+
+**方法**: `ExportConsumeItemDetailList`(第2340行)
+
+**修改说明**:
+- 如果导出功能需要包含备注字段,需要在导出DTO和Excel配置中添加
+- 需要检查 `ConsumeItemDetailExportOutput` DTO 是否包含备注字段
+- 需要在 Excel 配置的 `paramList` 中添加备注列
+
+**影响**: ⚠️ 需要根据业务需求确认是否在导出中包含备注
+
+---
+
+## 📝 修改优先级
+
+### 高优先级(必须修改)
+
+1. ✅ 数据库表结构修改
+2. ✅ 实体类 `LqXhHyhkEntity.cs`
+3. ✅ DTO类:
+ - `LqXhHyhkCrInput.cs`
+ - `LqXhHyhkInfoOutput.cs`
+ - `LqXhHyhkListOutput.cs`
+4. ✅ Service `GetList` 方法中的 Select 语句
+5. ✅ 前端表单页面 `Form.vue`
+6. ✅ 前端详情页面 `detail.vue`
+
+### 中优先级(建议修改)
+
+7. ⚠️ 前端列表页面 `index.vue`(根据业务需求)
+8. ⚠️ 导出功能(根据业务需求)
+
+### 低优先级(可选修改)
+
+9. ⚠️ 其他 Service 检查(根据实际使用情况)
+
+---
+
+## 🔍 测试检查清单
+
+修改完成后,需要测试以下功能:
+
+- [ ] 创建耗卡记录时,可以输入备注
+- [ ] 更新耗卡记录时,可以修改备注
+- [ ] 查询耗卡详情时,可以显示备注
+- [ ] 查询耗卡列表时,可以显示备注(如果列表包含)
+- [ ] 备注字段可以为空
+- [ ] 备注字段长度限制正确(500字符)
+- [ ] 前端表单验证正常
+- [ ] 数据库字段添加成功
+
+---
+
+## 📌 注意事项
+
+1. **字段命名**: 使用 `F_Remark` 作为数据库字段名,`Remark` 作为实体类属性名,`remark` 作为 DTO 属性名(小写开头,符合现有命名规范)
+
+2. **字段长度**: 建议使用 `VARCHAR(500)`,如果可能超过500字符,可以使用 `TEXT` 类型
+
+3. **兼容性**: 新增字段为可空字段,不会影响现有数据
+
+4. **自动映射**: Create 和 Update 方法使用 `Adapt` 自动映射,只要 DTO 包含字段即可自动支持
+
+5. **列表查询**: GetList 方法中有多个 Select 语句,需要全部添加备注字段
+
+6. **前端显示**: 备注内容可能较长,建议使用 `textarea` 输入,使用 `show-overflow-tooltip` 或单独卡片显示
+
+---
+
+## 📚 相关文件清单
+
+### 后端文件
+1. `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_hyhk/LqXhHyhkEntity.cs`
+2. `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkCrInput.cs`
+3. `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkInfoOutput.cs`
+4. `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs`
+5. `netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs`
+
+### 前端文件
+1. `antis-ncc-admin/src/views/lqXhHyhk/Form.vue`
+2. `antis-ncc-admin/src/views/lqXhHyhk/detail.vue`
+3. `antis-ncc-admin/src/views/lqXhHyhk/index.vue`(可选)
+
+### 数据库
+1. `lq_xh_hyhk` 表结构修改
+
+---
+
+## ✅ 总结
+
+本次修改涉及:
+- **数据库**: 1个表
+- **后端实体类**: 1个
+- **后端DTO**: 3个
+- **后端Service**: 1个(多个方法)
+- **前端页面**: 2-3个
+
+预计修改工作量:**中等**(主要是重复性的字段添加和映射)
diff --git a/docs/全员战报明细到店数统计问题修复.md b/docs/全员战报明细到店数统计问题修复.md
new file mode 100644
index 0000000..f31b78f
--- /dev/null
+++ b/docs/全员战报明细到店数统计问题修复.md
@@ -0,0 +1,135 @@
+# 全员战报明细到店数统计问题修复
+
+## 📋 问题描述
+
+在全员战报明细统计中,发现有的数据的到店数比拓客数还多,不符合业务逻辑。
+
+**业务规则**:
+- 一个人多次到店的话在这里就算一次
+- 到店数应该小于等于拓客数
+
+## 🔍 问题分析
+
+### 问题位置
+**文件**: `netcore/src/Modularity/Extend/NCC.Extend/LqTkDashboardService.cs`
+**方法**: `GetEmployeeParticipationStatistics`
+**行数**: 第575-588行(修复前)
+
+### 问题原因
+
+**修复前的SQL逻辑**:
+```sql
+LEFT JOIN (
+ -- 到店人数统计
+ SELECT
+ tk.F_ExpansionUserId as EmployeeId,
+ COUNT(DISTINCT tk.F_MemberId) as VisitCount
+ FROM lq_tkjlb tk
+ INNER JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1
+ WHERE {eventFilter}
+ AND tk.F_ExpansionTime >= '{startTimeStr}'
+ AND tk.F_ExpansionTime <= '{endTimeStr}'
+ AND xh.hksj >= '{startTimeStr}'
+ AND xh.hksj <= '{endTimeStr}'
+ GROUP BY tk.F_ExpansionUserId
+) visit ON emp.EmployeeId = visit.EmployeeId
+```
+
+**问题点**:
+1. **缺少时间关联约束**:没有限制到店时间必须在拓客时间之后,可能统计到拓客之前的到店记录
+2. **JOIN导致重复计数**:当同一个会员被同一个员工多次拓客,且该会员多次到店时,`INNER JOIN` 会产生多行记录
+3. **虽然使用了 DISTINCT,但 JOIN 的逻辑可能导致计数不准确**
+
+### 问题场景示例
+
+假设:
+- 员工A在1月1日拓客了会员B
+- 员工A在1月5日又拓客了会员B(重复拓客)
+- 会员B在1月3日到店1次
+- 会员B在1月6日到店1次
+
+**修复前的统计结果**:
+- 拓客数:1(COUNT(DISTINCT tk.F_MemberId))
+- 到店数:2(因为 JOIN 产生了2条记录:1月1日拓客+1月3日到店,1月5日拓客+1月6日到店)
+
+**问题**:到店数(2)> 拓客数(1),不符合逻辑
+
+## ✅ 修复方案
+
+### 修复后的SQL逻辑(最终版本)
+
+```sql
+LEFT JOIN (
+ -- 到店人数统计(一个人多次到店只算一次)
+ -- 先找出每个员工拓客的所有会员(去重),然后统计这些会员中有到店记录的会员数
+ SELECT
+ emp_members.EmployeeId,
+ COUNT(DISTINCT CASE WHEN xh.hy IS NOT NULL THEN emp_members.MemberId END) as VisitCount
+ FROM (
+ -- 先获取每个员工拓客的所有会员(去重),并记录首次拓客时间
+ SELECT DISTINCT
+ tk.F_ExpansionUserId as EmployeeId,
+ tk.F_MemberId as MemberId,
+ MIN(tk.F_ExpansionTime) as FirstExpansionTime
+ FROM lq_tkjlb tk
+ WHERE {eventFilter}
+ AND tk.F_ExpansionTime >= '{startTimeStr}'
+ AND tk.F_ExpansionTime <= '{endTimeStr}'
+ AND tk.F_MemberId IS NOT NULL
+ GROUP BY tk.F_ExpansionUserId, tk.F_MemberId
+ ) emp_members
+ LEFT JOIN lq_xh_hyhk xh ON emp_members.MemberId = xh.hy
+ AND xh.F_IsEffective = 1
+ AND xh.hksj >= emp_members.FirstExpansionTime
+ AND xh.hksj >= '{startTimeStr}'
+ AND xh.hksj <= '{endTimeStr}'
+ GROUP BY emp_members.EmployeeId
+) visit ON emp.EmployeeId = visit.EmployeeId
+```
+
+### 修复要点
+
+1. **先获取拓客会员列表(去重)**:
+ - 使用子查询先找出每个员工拓客的所有会员(去重)
+ - 记录每个会员的首次拓客时间(`MIN(tk.F_ExpansionTime)`)
+ - 确保同一个会员只出现一次,即使被多次拓客
+
+2. **统计有到店记录的会员数**:
+ - 在去重后的会员列表中,LEFT JOIN 耗卡表
+ - 使用 `COUNT(DISTINCT CASE WHEN xh.hy IS NOT NULL THEN emp_members.MemberId END)` 统计有到店记录的会员数
+ - 确保到店时间在首次拓客时间之后(`xh.hksj >= emp_members.FirstExpansionTime`)
+
+3. **双重去重保障**:
+ - 第一层:在子查询中使用 `GROUP BY tk.F_ExpansionUserId, tk.F_MemberId` 确保每个员工-会员组合只出现一次
+ - 第二层:使用 `COUNT(DISTINCT ...)` 确保最终统计时每个会员只算一次
+
+### 修复后的统计结果(使用上面的示例)
+
+- 拓客数:1(COUNT(DISTINCT tk.F_MemberId))
+- 到店数:1(会员B有到店记录,只算一次)
+
+**结果**:到店数(1)≤ 拓客数(1),符合逻辑 ✅
+
+## 📝 修改文件
+
+- **文件**: `netcore/src/Modularity/Extend/NCC.Extend/LqTkDashboardService.cs`
+- **方法**: `GetEmployeeParticipationStatistics`
+- **修改位置**: 第575-590行
+
+## ✅ 验证建议
+
+1. **验证到店数 ≤ 拓客数**:检查所有员工的统计数据,确保到店数不超过拓客数
+2. **验证去重逻辑**:选择一个多次到店的会员,验证其只被统计一次
+3. **验证时间约束**:选择一个拓客前有到店记录的会员,验证不会被统计到
+
+## 🔗 相关接口
+
+**接口地址**: `POST /api/Extend/LqTkDashboard/GetEmployeeParticipationStatistics`
+
+**接口说明**: 获取员工参与统计(全员战报明细)
+
+**返回字段**:
+- `ExpansionCount`: 拓客数
+- `VisitCount`: 到店数(修复后)
+- `BillingCount`: 开单数
+- `BillingAmount`: 开单金额
diff --git a/docs/全部门店开单记录汇总导出接口说明.md b/docs/全部门店开单记录汇总导出接口说明.md
new file mode 100644
index 0000000..15baf6a
--- /dev/null
+++ b/docs/全部门店开单记录汇总导出接口说明.md
@@ -0,0 +1,225 @@
+# 全部门店开单记录汇总导出接口
+
+## 接口地址
+
+```
+GET /api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores
+```
+
+## 接口说明
+
+按照Excel格式导出全部门店的开单记录汇总,每个品项每个健康师一行。如果某个品项有多个健康师,会展开为多行数据。
+
+## 请求参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| StartTime | DateTime | 是 | 开始时间(格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss) |
+| EndTime | DateTime | 是 | 结束时间(格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss) |
+| MemberId | string | 否 | 客户ID(会员ID),筛选指定客户 |
+| ItemCategory | string | 否 | 品项分类,筛选指定分类 |
+| ItemId | string | 否 | 品项ID,筛选指定品项 |
+| HealthCoachId | string | 否 | 健康师ID,筛选指定健康师 |
+
+## 请求示例
+
+### 基础请求(仅时间范围)
+
+```javascript
+// Axios 示例
+axios.get('/api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores', {
+ params: {
+ StartTime: '2026-01-01',
+ EndTime: '2026-01-31'
+ },
+ headers: {
+ 'Authorization': 'Bearer ' + token
+ },
+ responseType: 'blob' // 重要:设置为 blob 以接收文件
+}).then(response => {
+ // 创建下载链接
+ const url = window.URL.createObjectURL(new Blob([response.data]));
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', '开单记录汇总.xls');
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+});
+```
+
+### 完整请求(包含筛选条件)
+
+```javascript
+axios.get('/api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores', {
+ params: {
+ StartTime: '2026-01-01',
+ EndTime: '2026-01-31',
+ MemberId: '123456789', // 可选
+ ItemCategory: '分类名称', // 可选
+ ItemId: '987654321', // 可选
+ HealthCoachId: '111222333' // 可选
+ },
+ headers: {
+ 'Authorization': 'Bearer ' + token
+ },
+ responseType: 'blob'
+}).then(response => {
+ // 处理文件下载
+ const url = window.URL.createObjectURL(new Blob([response.data]));
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', '开单记录汇总.xls');
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+});
+```
+
+## 响应格式
+
+### 成功响应(200)
+
+```json
+{
+ "code": 200,
+ "msg": "操作成功",
+ "data": {
+ "name": "开单记录汇总_2026-01-22_152816.xls",
+ "url": "/api/File/Download?encryption=..."
+ }
+}
+```
+
+### 错误响应(400/500)
+
+```json
+{
+ "code": 400,
+ "msg": "开始时间和结束时间不能为空"
+}
+```
+
+## Excel 列说明
+
+导出的Excel文件包含以下列(按顺序):
+
+1. **开单日期** - 开单记录的日期
+2. **单据门店** - 开单门店的中文名称
+3. **客户姓名** - 客户姓名
+4. **客户电话** - 客户电话
+5. **金三角** - 金三角信息
+6. **客户类型** - 客户类型
+7. **健康师** - 健康师姓名
+8. **业绩金额** - 健康师业绩金额(格式:¥0.00)
+9. **相关品项** - 品项名称
+10. **分类** - 品项分类
+11. **类型** - 品项类型(购买/体验/赠送等)
+12. **单价** - 品项单价(格式:¥0.00)
+13. **次数** - 项目次数
+14. **品项金额** - 品项总金额(格式:¥0.00)
+15. **已付金额** - 已付金额(格式:¥0.00)
+16. **欠款金额** - 欠款金额(格式:¥0.00)
+17. **总金额** - 总金额(格式:¥0.00)
+18. **支付方式** - 支付方式
+19. **合作机构** - 合作机构名称
+20. **结算机构** - 结算机构名称
+21. **活动名称** - 活动名称
+22. **备注** - 备注信息
+23. **开单时间** - 开单时间(格式:yyyy/MM/dd HH:mm:ss)
+
+## 数据展开规则
+
+- 如果一个开单记录有多个品项,每个品项一行
+- 如果一个品项有多个健康师,每个健康师一行
+- 如果一个品项没有健康师,健康师相关字段显示"无",业绩金额显示"¥0.00"
+
+## 注意事项
+
+1. **时间格式**:建议使用 `yyyy-MM-dd` 格式,系统会自动处理时间范围
+2. **文件下载**:前端需要设置 `responseType: 'blob'` 来接收文件
+3. **文件格式**:导出文件为 `.xls` 格式(Excel 97-2003)
+4. **数据量**:如果数据量很大,导出可能需要较长时间,建议前端显示加载提示
+5. **权限**:需要登录并携带有效的 Authorization token
+
+## Vue 2 + Element UI 完整示例
+
+```vue
+
+
+
+ 导出全部门店开单记录汇总
+
+
+
+
+
+```
+
+## API 封装示例(api/billing.js)
+
+```javascript
+import request from '@/utils/request'
+
+/**
+ * 导出全部门店开单记录汇总
+ * @param {Object} params 查询参数
+ * @param {string} params.StartTime 开始时间
+ * @param {string} params.EndTime 结束时间
+ * @param {string} [params.MemberId] 客户ID
+ * @param {string} [params.ItemCategory] 品项分类
+ * @param {string} [params.ItemId] 品项ID
+ * @param {string} [params.HealthCoachId] 健康师ID
+ * @returns {Promise}
+ */
+export function exportBillingRecordSummaryAllStores(params) {
+ return request({
+ url: '/api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores',
+ method: 'get',
+ params: params
+ })
+}
+```
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryExportAllStoresInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryExportAllStoresInput.cs
new file mode 100644
index 0000000..04982c1
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryExportAllStoresInput.cs
@@ -0,0 +1,48 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using NCC.Common.Filter;
+
+namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
+{
+ ///
+ /// 全部门店开单记录汇总导出查询输入
+ ///
+ public class BillingRecordSummaryExportAllStoresInput : PageInputBase
+ {
+ ///
+ /// 开始时间
+ ///
+ [Display(Name = "开始时间", Description = "查询开单记录的开始时间")]
+ public DateTime? StartTime { get; set; }
+
+ ///
+ /// 结束时间
+ ///
+ [Display(Name = "结束时间", Description = "查询开单记录的结束时间")]
+ public DateTime? EndTime { get; set; }
+
+ ///
+ /// 品项分类
+ ///
+ [Display(Name = "品项分类", Description = "筛选品项分类")]
+ public string ItemCategory { get; set; }
+
+ ///
+ /// 品项ID
+ ///
+ [Display(Name = "品项ID", Description = "筛选品项ID")]
+ public string ItemId { get; set; }
+
+ ///
+ /// 健康师ID
+ ///
+ [Display(Name = "健康师ID", Description = "筛选健康师ID")]
+ public string HealthCoachId { get; set; }
+
+ ///
+ /// 客户ID(会员ID)
+ ///
+ [Display(Name = "客户ID", Description = "筛选客户ID")]
+ public string MemberId { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryExportOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryExportOutput.cs
new file mode 100644
index 0000000..09b865e
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryExportOutput.cs
@@ -0,0 +1,125 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
+{
+ ///
+ /// 开单记录汇总导出输出
+ ///
+ public class BillingRecordSummaryExportOutput
+ {
+ ///
+ /// 开单日期
+ ///
+ public string BillingDate { get; set; }
+
+ ///
+ /// 单据门店
+ ///
+ public string StoreName { get; set; }
+
+ ///
+ /// 客户姓名
+ ///
+ public string CustomerName { get; set; }
+
+ ///
+ /// 客户电话
+ ///
+ public string CustomerPhone { get; set; }
+
+ ///
+ /// 金三角
+ ///
+ public string GoldTriangle { get; set; }
+
+ ///
+ /// 客户类型
+ ///
+ public string CustomerType { get; set; }
+
+ ///
+ /// 健康师
+ ///
+ public string HealthCoach { get; set; }
+
+ ///
+ /// 业绩金额
+ ///
+ public string PerformanceAmount { get; set; }
+
+ ///
+ /// 相关品项
+ ///
+ public string RelatedItem { get; set; }
+
+ ///
+ /// 分类
+ ///
+ public string Category { get; set; }
+
+ ///
+ /// 类型
+ ///
+ public string Type { get; set; }
+
+ ///
+ /// 单价
+ ///
+ public string UnitPrice { get; set; }
+
+ ///
+ /// 次数
+ ///
+ public string ProjectNumber { get; set; }
+
+ ///
+ /// 品项金额
+ ///
+ public string ItemAmount { get; set; }
+
+ ///
+ /// 已付金额
+ ///
+ public string PaidAmount { get; set; }
+
+ ///
+ /// 欠款金额
+ ///
+ public string DebtAmount { get; set; }
+
+ ///
+ /// 总金额
+ ///
+ public string TotalAmount { get; set; }
+
+ ///
+ /// 支付方式
+ ///
+ public string PaymentMethod { get; set; }
+
+ ///
+ /// 合作机构
+ ///
+ public string CooperationInstitution { get; set; }
+
+ ///
+ /// 结算机构
+ ///
+ public string SettlementInstitution { get; set; }
+
+ ///
+ /// 活动名称
+ ///
+ public string ActivityName { get; set; }
+
+ ///
+ /// 备注
+ ///
+ public string Remark { get; set; }
+
+ ///
+ /// 开单时间
+ ///
+ public string CreateTime { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkCrInput.cs
index 87fa62f..39f13c3 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkCrInput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkCrInput.cs
@@ -90,6 +90,11 @@ namespace NCC.Extend.Entitys.Dto.LqXhHyhk
public string appointmentId { get; set; }
///
+ /// 备注
+ ///
+ public string remark { get; set; }
+
+ ///
/// 耗卡_品项明细
///
public List lqXhPxmxList { get; set; }
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkInfoOutput.cs
index 8d8060b..1b5b765 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkInfoOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkInfoOutput.cs
@@ -113,6 +113,11 @@ namespace NCC.Extend.Entitys.Dto.LqXhHyhk
public string cancelRemark { get; set; }
///
+ /// 备注
+ ///
+ public string remark { get; set; }
+
+ ///
/// 加班系数(NULL或0表示非加班单,大于0表示加班单,如 0.5、1、1.5)
///
public decimal? overtimeCoefficient { get; set; }
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs
index 55b6fb4..36b04e5 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs
@@ -95,6 +95,11 @@ namespace NCC.Extend.Entitys.Dto.LqXhHyhk
public string signatureFile { get; set; }
///
+ /// 备注
+ ///
+ public string remark { get; set; }
+
+ ///
/// 是否有效
///
public int isEffective { get; set; }
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkUpdateRemarkInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkUpdateRemarkInput.cs
new file mode 100644
index 0000000..7983f88
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkUpdateRemarkInput.cs
@@ -0,0 +1,13 @@
+namespace NCC.Extend.Entitys.Dto.LqXhHyhk
+{
+ ///
+ /// 更新耗卡备注输入参数
+ ///
+ public class LqXhHyhkUpdateRemarkInput
+ {
+ ///
+ /// 备注
+ ///
+ public string remark { get; set; }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_hyhk/LqXhHyhkEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_hyhk/LqXhHyhkEntity.cs
index d6e383c..b9d958f 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_hyhk/LqXhHyhkEntity.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_hyhk/LqXhHyhkEntity.cs
@@ -128,6 +128,12 @@ namespace NCC.Extend.Entitys.lq_xh_hyhk
public string CancelRemark { get; set; }
///
+ /// 备注
+ ///
+ [SugarColumn(ColumnName = "F_Remark")]
+ public string Remark { get; set; }
+
+ ///
/// 加班系数(NULL或0表示非加班单,大于0表示加班单,如 0.5、1、1.5)
///
[SugarColumn(ColumnName = "F_OvertimeCoefficient")]
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
index eac544e..537b1cb 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
@@ -96,6 +97,431 @@ namespace NCC.Extend.LqKdKdjlb
_dailyReportService = dailyReportService;
}
+ #region 导出全部门店开单记录汇总
+ ///
+ /// 导出全部门店开单记录汇总
+ ///
+ ///
+ /// 按照Excel格式导出全部门店的开单记录汇总,每个品项每个健康师一行
+ ///
+ /// 示例请求:
+ /// GET /api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores?StartTime=2026-01-01&EndTime=2026-01-31
+ ///
+ /// 参数说明:
+ /// - StartTime: 开始时间(必填)
+ /// - EndTime: 结束时间(必填)
+ /// - MemberId: 客户ID(可选)
+ /// - ItemCategory: 品项分类(可选)
+ /// - ItemId: 品项ID(可选)
+ /// - HealthCoachId: 健康师ID(可选)
+ ///
+ /// 查询参数
+ /// Excel文件下载链接
+ /// 导出成功
+ /// 参数错误
+ /// 服务器错误
+ [HttpGet("billing-record-summary-export-all-stores"), NonUnify]
+ public async Task ExportBillingRecordSummaryAllStores([FromQuery] BillingRecordSummaryExportAllStoresInput input)
+ {
+ try
+ {
+ // 如果开始时间和结束时间为空,就默认是当月
+ if (input.StartTime == null || input.EndTime == null)
+ {
+ input.StartTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+ input.EndTime = DateTime.Now.AddDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month));
+ }
+
+ // 确保时间不为null
+ if (input.StartTime == null || input.EndTime == null)
+ {
+ throw NCCException.Oh("开始时间和结束时间不能为空");
+ }
+
+ _logger.LogInformation($"开始导出全部门店开单记录汇总,时间范围: {input.StartTime} 到 {input.EndTime}");
+
+ // 构建开单记录查询条件(全部门店)
+ var startTime = input.StartTime.Value;
+ var endTime = input.EndTime.Value;
+ var billingQuery = _db.Queryable()
+ .Where(w => w.Kdrq.HasValue && w.Kdrq.Value >= startTime && w.Kdrq.Value <= endTime && w.IsEffective == StatusEnum.有效.GetHashCode());
+
+ // 客户筛选
+ if (!string.IsNullOrEmpty(input.MemberId))
+ {
+ billingQuery = billingQuery.Where(w => w.Kdhy == input.MemberId);
+ }
+
+ // 查询所有开单记录
+ var billingRecords = await billingQuery
+ .Select(it => new
+ {
+ id = it.Id,
+ djmd = it.Djmd,
+ djmdName = SqlFunc.Subqueryable().Where(x => x.Id == it.Djmd).Select(x => x.Dm),
+ jsj = it.Jsj,
+ kdrq = it.Kdrq,
+ gjlx = it.Gjlx,
+ zdyj = it.Zdyj,
+ sfyj = it.Sfyj,
+ qk = it.Qk,
+ hgjg = it.Hgjg,
+ hgjgName = SqlFunc.Subqueryable().Where(x => x.Id == it.Hgjg).Select(x => x.Hzmc),
+ kdhyc = SqlFunc.Subqueryable().Where(x => x.Id == it.Kdhy).Select(x => x.Khmc),
+ kdhysjh = SqlFunc.Subqueryable().Where(x => x.Id == it.Kdhy).Select(x => x.Sjh),
+ fkfs = it.Fkfs,
+ fkyy = it.Fkyy,
+ fkyyName = SqlFunc.Subqueryable().Where(x => x.Id == it.Fkyy).Select(x => x.Hzmc),
+ activityId = it.ActivityId,
+ activityName = SqlFunc.Subqueryable().Where(x => x.Id == it.ActivityId).Select(x => x.ActivityName),
+ khly = it.Khly,
+ bz = it.Bz,
+ createTime = it.CreateTime
+ })
+ .ToListAsync();
+
+ if (!billingRecords.Any())
+ {
+ throw NCCException.Oh(ErrorCode.COM1005, "该时间段内无开单记录");
+ }
+
+ _logger.LogInformation($"查询到 {billingRecords.Count} 条开单记录");
+
+ if (billingRecords == null || !billingRecords.Any())
+ {
+ _logger.LogWarning("开单记录为空,无法继续导出");
+ throw NCCException.Oh(ErrorCode.COM1005, "该时间段内无开单记录");
+ }
+
+ var billingIds = billingRecords.Select(x => x.id).ToList();
+ _logger.LogInformation($"提取到 {billingIds.Count} 个开单记录ID");
+
+ // 构建品项明细查询条件
+ var itemDetailsQuery = _db.Queryable()
+ .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode());
+
+ // 品项分类筛选
+ if (!string.IsNullOrEmpty(input.ItemCategory))
+ {
+ itemDetailsQuery = itemDetailsQuery.Where(w => w.ItemCategory == input.ItemCategory);
+ }
+
+ // 品项筛选
+ if (!string.IsNullOrEmpty(input.ItemId))
+ {
+ itemDetailsQuery = itemDetailsQuery.Where(w => w.Px == input.ItemId);
+ }
+
+ // 查询品项明细
+ _logger.LogInformation($"开始查询品项明细,开单记录ID数量: {billingIds.Count}");
+ var itemDetails = await itemDetailsQuery
+ .Select(it => new
+ {
+ id = it.Id,
+ glkdbh = it.Glkdbh,
+ px = it.Px,
+ pxmc = it.Pxmc,
+ pxjg = it.Pxjg,
+ sourceType = it.SourceType,
+ totalPrice = it.TotalPrice,
+ actualPrice = it.ActualPrice,
+ projectNumber = it.ProjectNumber,
+ remark = it.Remark,
+ itemCategory = it.ItemCategory,
+ })
+ .ToListAsync();
+ _logger.LogInformation($"查询到 {itemDetails?.Count ?? 0} 条品项明细");
+
+ // 构建健康师业绩查询条件
+ var healthTeacherQuery = _db.Queryable()
+ .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode());
+
+ // 健康师筛选
+ if (!string.IsNullOrEmpty(input.HealthCoachId))
+ {
+ healthTeacherQuery = healthTeacherQuery.Where(w => w.Jks == input.HealthCoachId || w.Jkszh == input.HealthCoachId);
+ }
+
+ // 品项分类筛选(健康师业绩表)
+ if (!string.IsNullOrEmpty(input.ItemCategory))
+ {
+ healthTeacherQuery = healthTeacherQuery.Where(w => w.ItemCategory == input.ItemCategory);
+ }
+
+ // 品项筛选(健康师业绩表)
+ if (!string.IsNullOrEmpty(input.ItemId))
+ {
+ healthTeacherQuery = healthTeacherQuery.Where(w => w.ItemId == input.ItemId);
+ }
+
+ // 查询健康师业绩数据
+ _logger.LogInformation($"开始查询健康师业绩数据,开单记录ID数量: {billingIds.Count}");
+ var healthTeacherData = await healthTeacherQuery
+ .Select(it => new
+ {
+ id = it.Id,
+ glkdbh = it.Glkdbh,
+ jks = it.Jks,
+ jksxm = it.Jksxm,
+ jkszh = it.Jkszh,
+ jksyj = it.Jksyj,
+ kdpxid = it.Kdpxid,
+ yjsj = it.Yjsj,
+ jsj_id = it.Jsj_id,
+ itemCategory = it.ItemCategory,
+ storeId = it.StoreId,
+ itemId = it.ItemId,
+ itemName = it.ItemName,
+ })
+ .ToListAsync();
+ _logger.LogInformation($"查询到 {healthTeacherData?.Count ?? 0} 条健康师业绩数据");
+
+ // 批量查询金三角信息
+ var goldTriangleDict = new Dictionary();
+ var jsjIds = billingRecords.Where(x => !string.IsNullOrEmpty(x.jsj)).Select(x => x.jsj).Distinct().ToList();
+ if (jsjIds.Any())
+ {
+ var jsjList = await _db.Queryable()
+ .Where(x => jsjIds.Contains(x.Id))
+ .Select(x => new { x.Id, x.UserName })
+ .ToListAsync();
+ goldTriangleDict = jsjList.ToDictionary(x => x.Id, x => x.UserName ?? "");
+ }
+
+ // 按品项明细ID分组健康师业绩
+ var healthTeacherDict = healthTeacherData
+ .Where(x => !string.IsNullOrEmpty(x.kdpxid))
+ .GroupBy(x => x.kdpxid)
+ .ToDictionary(g => g.Key, g => g.ToList());
+
+ // 展开数据:每个品项每个健康师一行
+ var exportData = new List();
+ int processedCount = 0;
+
+ foreach (var itemDetail in itemDetails)
+ {
+ var billing = billingRecords.FirstOrDefault(b => b.id == itemDetail.glkdbh);
+ if (billing == null) continue;
+
+ // 获取该品项的健康师业绩列表
+ List healthTeachers;
+ if (healthTeacherDict.ContainsKey(itemDetail.id))
+ {
+ healthTeachers = healthTeacherDict[itemDetail.id].Cast().ToList();
+ }
+ else
+ {
+ healthTeachers = new List();
+ }
+
+ // 格式化金额
+ string FormatAmount(decimal? amount)
+ {
+ if (amount == null || amount == 0) return "¥0.00";
+ return $"¥{amount.Value:F2}";
+ }
+
+ // 格式化金额(非可空类型)
+ string FormatAmountNonNull(decimal amount)
+ {
+ if (amount == 0) return "¥0.00";
+ return $"¥{amount:F2}";
+ }
+
+ // 格式化业绩金额(字符串类型)
+ string FormatPerformanceAmount(string amount)
+ {
+ if (string.IsNullOrEmpty(amount)) return "¥0.00";
+ if (decimal.TryParse(amount, out decimal decAmount))
+ {
+ return $"¥{decAmount:F2}";
+ }
+ return "¥0.00";
+ }
+
+ // 格式化日期
+ string FormatDate(DateTime? date)
+ {
+ if (date == null) return "无";
+ return date.Value.ToString("yyyy-MM-dd");
+ }
+
+ // 格式化日期时间
+ string FormatDateTime(DateTime? dateTime)
+ {
+ if (dateTime == null) return "无";
+ return dateTime.Value.ToString("yyyy/MM/dd HH:mm:ss");
+ }
+
+ // 获取金三角名称
+ string GetGoldTriangleName(string jsjId)
+ {
+ if (string.IsNullOrEmpty(jsjId)) return "无";
+ return goldTriangleDict.ContainsKey(jsjId) ? goldTriangleDict[jsjId] : "无";
+ }
+
+ // 如果没有健康师,也要导出一行(健康师信息为空)
+ if (healthTeachers == null || !healthTeachers.Any())
+ {
+ exportData.Add(new BillingRecordSummaryExportOutput
+ {
+ BillingDate = FormatDate(billing.kdrq),
+ StoreName = billing.djmdName ?? "无",
+ CustomerName = billing.kdhyc ?? "无",
+ CustomerPhone = billing.kdhysjh ?? "无",
+ GoldTriangle = GetGoldTriangleName(billing.jsj),
+ CustomerType = billing.gjlx?.ToString() ?? "0",
+ HealthCoach = "无",
+ PerformanceAmount = "¥0.00",
+ RelatedItem = itemDetail.pxmc ?? "无",
+ Category = itemDetail.itemCategory ?? "无",
+ Type = itemDetail.sourceType ?? "无",
+ UnitPrice = FormatAmountNonNull(itemDetail.pxjg),
+ ProjectNumber = itemDetail.projectNumber.ToString(),
+ ItemAmount = FormatAmountNonNull(itemDetail.totalPrice),
+ PaidAmount = FormatAmount(billing.sfyj),
+ DebtAmount = FormatAmount(billing.qk),
+ TotalAmount = FormatAmount(billing.zdyj),
+ PaymentMethod = billing.fkfs ?? "无",
+ CooperationInstitution = billing.fkyyName ?? "无",
+ SettlementInstitution = billing.hgjgName ?? "无",
+ ActivityName = billing.activityName ?? "无",
+ Remark = billing.bz ?? "无",
+ CreateTime = FormatDateTime(billing.createTime)
+ });
+ }
+ else
+ {
+ // 每个健康师一行
+ foreach (var healthTeacher in healthTeachers)
+ {
+ exportData.Add(new BillingRecordSummaryExportOutput
+ {
+ BillingDate = FormatDate(billing.kdrq),
+ StoreName = billing.djmdName ?? "无",
+ CustomerName = billing.kdhyc ?? "无",
+ CustomerPhone = billing.kdhysjh ?? "无",
+ GoldTriangle = GetGoldTriangleName(billing.jsj),
+ CustomerType = billing.gjlx?.ToString() ?? "0",
+ HealthCoach = healthTeacher.jksxm ?? "无",
+ PerformanceAmount = FormatPerformanceAmount(healthTeacher.jksyj),
+ RelatedItem = itemDetail.pxmc ?? "无",
+ Category = itemDetail.itemCategory ?? "无",
+ Type = itemDetail.sourceType ?? "无",
+ UnitPrice = FormatAmountNonNull(itemDetail.pxjg),
+ ProjectNumber = itemDetail.projectNumber.ToString(),
+ ItemAmount = FormatAmountNonNull(itemDetail.totalPrice),
+ PaidAmount = FormatAmount(billing.sfyj),
+ DebtAmount = FormatAmount(billing.qk),
+ TotalAmount = FormatAmount(billing.zdyj),
+ PaymentMethod = billing.fkfs ?? "无",
+ CooperationInstitution = billing.fkyyName ?? "无",
+ SettlementInstitution = billing.hgjgName ?? "无",
+ ActivityName = billing.activityName ?? "无",
+ Remark = billing.bz ?? "无",
+ CreateTime = FormatDateTime(billing.createTime)
+ });
+ }
+ }
+
+ processedCount++;
+ if (processedCount % 1000 == 0)
+ {
+ _logger.LogInformation($"已处理 {processedCount}/{itemDetails.Count} 条品项明细,当前导出数据行数: {exportData.Count}");
+ }
+ }
+
+ _logger.LogInformation($"数据展开完成,处理品项明细数量: {processedCount}, 导出数据行数: {exportData.Count}");
+
+ if (!exportData.Any())
+ {
+ throw NCCException.Oh(ErrorCode.COM1005, "没有符合条件的数据可导出");
+ }
+
+ // 配置Excel导出
+ List paramList =
+ "[{\"value\":\"开单日期\",\"field\":\"BillingDate\"},{\"value\":\"单据门店\",\"field\":\"StoreName\"},{\"value\":\"客户姓名\",\"field\":\"CustomerName\"},{\"value\":\"客户电话\",\"field\":\"CustomerPhone\"},{\"value\":\"金三角\",\"field\":\"GoldTriangle\"},{\"value\":\"客户类型\",\"field\":\"CustomerType\"},{\"value\":\"健康师\",\"field\":\"HealthCoach\"},{\"value\":\"业绩金额\",\"field\":\"PerformanceAmount\"},{\"value\":\"相关品项\",\"field\":\"RelatedItem\"},{\"value\":\"分类\",\"field\":\"Category\"},{\"value\":\"类型\",\"field\":\"Type\"},{\"value\":\"单价\",\"field\":\"UnitPrice\"},{\"value\":\"次数\",\"field\":\"ProjectNumber\"},{\"value\":\"品项金额\",\"field\":\"ItemAmount\"},{\"value\":\"已付金额\",\"field\":\"PaidAmount\"},{\"value\":\"欠款金额\",\"field\":\"DebtAmount\"},{\"value\":\"总金额\",\"field\":\"TotalAmount\"},{\"value\":\"支付方式\",\"field\":\"PaymentMethod\"},{\"value\":\"合作机构\",\"field\":\"CooperationInstitution\"},{\"value\":\"结算机构\",\"field\":\"SettlementInstitution\"},{\"value\":\"活动名称\",\"field\":\"ActivityName\"},{\"value\":\"备注\",\"field\":\"Remark\"},{\"value\":\"开单时间\",\"field\":\"CreateTime\"}]".ToList();
+
+ ExcelConfig excelconfig = new ExcelConfig();
+ excelconfig.FileName = "开单记录汇总_" + DateTime.Now.ToString("yyyy-MM-dd_HHmmss") + ".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 });
+ }
+
+ // 查找项目根目录并创建ExportFiles文件夹
+ var baseDir = AppContext.BaseDirectory;
+ var projectRoot = baseDir;
+ var dir = new DirectoryInfo(baseDir);
+ while (dir != null && dir.Parent != null)
+ {
+ try
+ {
+ if (dir.GetDirectories(".git").Any() || dir.GetFiles("*.sln").Any())
+ {
+ projectRoot = dir.FullName;
+ break;
+ }
+ }
+ catch
+ {
+ // 忽略访问错误,继续向上查找
+ }
+ dir = dir.Parent;
+ }
+
+ if (projectRoot == baseDir)
+ {
+ dir = new DirectoryInfo(baseDir);
+ while (dir != null && dir.Parent != null)
+ {
+ try
+ {
+ if (dir.GetFiles("*.sln").Any())
+ {
+ projectRoot = dir.FullName;
+ break;
+ }
+ }
+ catch
+ {
+ // 忽略访问错误,继续向上查找
+ }
+ dir = dir.Parent;
+ }
+ }
+
+ var exportFilesPath = Path.Combine(projectRoot, "ExportFiles");
+ if (!Directory.Exists(exportFilesPath))
+ {
+ Directory.CreateDirectory(exportFilesPath);
+ }
+
+ var addPath = Path.Combine(exportFilesPath, excelconfig.FileName);
+ ExcelExportHelper.Export(exportData, excelconfig, addPath);
+
+ var fileName = _userManager.UserId + "|" + addPath + "|xls";
+ var downloadUrl = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC");
+ var output = new { name = excelconfig.FileName, url = downloadUrl };
+
+ _logger.LogInformation($"导出完成,文件路径: {addPath}, 下载地址: {downloadUrl}");
+
+ // 返回统一格式
+ return new { code = 200, msg = "导出成功", data = output };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"导出全部门店开单记录汇总失败,异常详情: {ex.ToString()}");
+ throw NCCException.Oh($"导出全部门店开单记录汇总失败:{ex.Message}");
+ }
+ }
+ #endregion
+
#region 获取开单记录表
///
/// 获取开单记录表
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTkDashboardService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTkDashboardService.cs
index bc86c4e..ea1ef53 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqTkDashboardService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTkDashboardService.cs
@@ -552,15 +552,15 @@ namespace NCC.Extend.LqTkDashboard
COALESCE(bigOrder.BigOrderCount, 0) as BigOrderCount,
COALESCE(bigOrder.BigOrderAmount, 0) as BigOrderAmount
FROM (
- -- 基础员工拓客数据
+ -- 基础员工拓客数据(按员工统计,不按TeamName分组)
SELECT
tk.F_ExpansionUserId as EmployeeId,
COALESCE(u.F_REALNAME, '') as EmployeeName,
COALESCE(org.F_FullName, '') as DepartmentName,
COALESCE(u.F_GW, '') as Position,
- COALESCE(tk.F_StoreId, '') as StoreId,
- COALESCE(md.dm, '') as StoreName,
- COALESCE(tk.F_TeamName, '') as TeamName,
+ COALESCE(MAX(tk.F_StoreId), '') as StoreId,
+ COALESCE(MAX(md.dm), '') as StoreName,
+ COALESCE(MAX(tk.F_TeamName), '') as TeamName,
COUNT(DISTINCT tk.F_MemberId) as ExpansionCount,
COALESCE(SUM(CAST(tk.F_BuyNumber AS SIGNED)), 0) as ExpansionCardCount
FROM lq_tkjlb tk
@@ -570,21 +570,34 @@ namespace NCC.Extend.LqTkDashboard
WHERE {eventFilter}
AND tk.F_ExpansionTime >= '{startTimeStr}'
AND tk.F_ExpansionTime <= '{endTimeStr}'
- GROUP BY tk.F_ExpansionUserId, u.F_REALNAME, org.F_FullName, u.F_GW, tk.F_StoreId, md.dm, tk.F_TeamName
+ GROUP BY tk.F_ExpansionUserId, u.F_REALNAME, org.F_FullName, u.F_GW
) emp
LEFT JOIN (
- -- 到店人数统计
+ -- 到店人数统计(一个人多次到店只算一次)
+ -- 先找出该员工拓客的所有会员(去重),然后统计这些会员中有到店记录的会员数
+ -- 确保到店时间在首次拓客时间之后
SELECT
- tk.F_ExpansionUserId as EmployeeId,
- COUNT(DISTINCT tk.F_MemberId) as VisitCount
- FROM lq_tkjlb tk
- INNER JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1
- WHERE {eventFilter}
- AND tk.F_ExpansionTime >= '{startTimeStr}'
- AND tk.F_ExpansionTime <= '{endTimeStr}'
+ emp_members.EmployeeId,
+ COUNT(DISTINCT CASE WHEN xh.hy IS NOT NULL THEN emp_members.MemberId END) as VisitCount
+ FROM (
+ -- 先获取该员工拓客的所有会员(去重),并记录每个会员的首次拓客时间
+ SELECT
+ tk.F_ExpansionUserId as EmployeeId,
+ tk.F_MemberId as MemberId,
+ MIN(tk.F_ExpansionTime) as FirstExpansionTime
+ FROM lq_tkjlb tk
+ WHERE {eventFilter}
+ AND tk.F_ExpansionTime >= '{startTimeStr}'
+ AND tk.F_ExpansionTime <= '{endTimeStr}'
+ AND tk.F_MemberId IS NOT NULL
+ GROUP BY tk.F_ExpansionUserId, tk.F_MemberId
+ ) emp_members
+ LEFT JOIN lq_xh_hyhk xh ON emp_members.MemberId = xh.hy
+ AND xh.F_IsEffective = 1
+ AND xh.hksj >= emp_members.FirstExpansionTime
AND xh.hksj >= '{startTimeStr}'
AND xh.hksj <= '{endTimeStr}'
- GROUP BY tk.F_ExpansionUserId
+ GROUP BY emp_members.EmployeeId
) visit ON emp.EmployeeId = visit.EmployeeId
LEFT JOIN (
-- 开单人数和金额统计
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
index a4a0066..720dc32 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
@@ -370,6 +370,7 @@ namespace NCC.Extend.LqXhHyhk
memberPhone = SqlFunc.Subqueryable().Where(w => w.Id == it.Hy).Select(w => w.Sjh),
isEffective = it.IsEffective,
signatureFile = it.SignatureFile,
+ remark = it.Remark,
overtimeCoefficient = it.OvertimeCoefficient,
originalSgfy = it.OriginalSgfy,
overtimeSgfy = it.OvertimeSgfy,
@@ -605,6 +606,7 @@ namespace NCC.Extend.LqXhHyhk
memberPhone = SqlFunc.Subqueryable().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
isEffective = hyhk.IsEffective,
signatureFile = hyhk.SignatureFile,
+ remark = hyhk.Remark,
overtimeCoefficient = hyhk.OvertimeCoefficient,
originalSgfy = hyhk.OriginalSgfy,
overtimeSgfy = hyhk.OvertimeSgfy,
@@ -769,6 +771,7 @@ namespace NCC.Extend.LqXhHyhk
memberPhone = SqlFunc.Subqueryable().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
isEffective = hyhk.IsEffective,
signatureFile = hyhk.SignatureFile,
+ remark = hyhk.Remark,
overtimeCoefficient = hyhk.OvertimeCoefficient,
originalSgfy = hyhk.OriginalSgfy,
overtimeSgfy = hyhk.OvertimeSgfy,
@@ -1155,6 +1158,7 @@ namespace NCC.Extend.LqXhHyhk
sfykjb = it.Sfykjb,
hksj = it.Hksj,
czry = it.Czry,
+ remark = it.Remark,
})
.MergeTable()
.OrderBy(sidx + " " + input.sort)
@@ -1495,6 +1499,64 @@ namespace NCC.Extend.LqXhHyhk
}
#endregion
+ #region 更新耗卡备注
+ ///
+ /// 更新耗卡备注
+ ///
+ ///
+ /// 专门用于更新耗卡记录的备注信息
+ ///
+ /// 示例请求:
+ /// PUT /api/Extend/LqXhHyhk/{id}/UpdateRemark
+ ///
+ /// 请求体:
+ /// ```json
+ /// {
+ /// "remark": "备注信息(最大2000字符)"
+ /// }
+ /// ```
+ ///
+ /// 耗卡记录ID
+ /// 备注信息
+ /// 无返回值
+ /// 更新成功
+ /// 参数错误
+ /// 耗卡记录不存在
+ /// 服务器错误
+ [HttpPut("{id}/UpdateRemark")]
+ public async Task UpdateRemark(string id, [FromBody] LqXhHyhkUpdateRemarkInput input)
+ {
+ try
+ {
+ // 查询记录
+ var entity = await _db.Queryable()
+ .Where(p => p.Id == id && p.IsEffective == StatusEnum.有效.GetHashCode())
+ .FirstAsync();
+
+ if (entity == null)
+ {
+ throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在或已作废");
+ }
+
+ // 更新备注
+ entity.Remark = input.remark;
+ entity.UpdateTime = DateTime.Now;
+
+ // 保存更新
+ await _db.Updateable(entity)
+ .UpdateColumns(it => new { it.Remark, it.UpdateTime })
+ .ExecuteCommandAsync();
+
+ _logger.LogInformation($"更新耗卡备注成功,ID:{id}");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "更新耗卡备注失败,ID:{Id}", id);
+ throw;
+ }
+ }
+ #endregion
+
#region 删除会员耗卡
///
/// 删除会员耗卡
diff --git a/sql/添加lq_xh_hyhk表备注字段.sql b/sql/添加lq_xh_hyhk表备注字段.sql
new file mode 100644
index 0000000..4de2721
--- /dev/null
+++ b/sql/添加lq_xh_hyhk表备注字段.sql
@@ -0,0 +1,7 @@
+-- 在 lq_xh_hyhk 表中添加备注字段
+-- 字段名: F_Remark
+-- 数据类型: VARCHAR(2000)
+-- 位置: 在 F_CancelRemark 字段之后
+
+ALTER TABLE lq_xh_hyhk
+ADD COLUMN F_Remark VARCHAR(2000) NULL COMMENT '备注' AFTER F_CancelRemark;