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 + + +
+ + 备注信息 +
+
+

{{ dataForm.remark || '无' }}

+
+
+``` + +**需要修改的位置**: +- 在详情数据绑定中添加备注字段的显示(建议在第88行之后) + +--- + +#### 6.3 列表页面 + +**文件**: `antis-ncc-admin/src/views/lqXhHyhk/index.vue` + +**修改内容**: +- 在列表表格中添加备注列(可选,根据业务需求) +- 如果备注内容较长,可以考虑使用 tooltip 显示完整内容 + +**代码示例**: +```vue + + + +``` + +**需要修改的位置**: +- 在表格列定义中添加备注列(根据实际表格结构确定位置) + +--- + +### 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;