Commit 55bd8d57359a331eb51d2e359fcea545f79e80ba

Authored by “wangming”
1 parent fd65ae16

fix: 修复全员战报明细到店数统计和导出接口返回格式

- 修复全员战报明细到店数统计问题:确保到店数不超过拓客数,同一会员多次到店只统计一次
- 修复全部门店开单记录汇总导出接口返回格式:使用统一的响应格式(code, msg, data)
- 添加 lq_xh_hyhk 表备注字段功能:支持备注字段的创建、更新、查询和导出
- 优化到店数统计SQL逻辑:先获取拓客会员列表(去重),再统计有到店记录的会员数
docs/lq_xh_hyhk备注字段-接口说明.md 0 → 100644
  1 +# lq_xh_hyhk 表备注字段 - 接口说明
  2 +
  3 +## 📡 修改的接口列表
  4 +
  5 +### 1. 创建耗卡记录
  6 +**接口地址**: `POST /api/Extend/LqXhHyhk`
  7 +**接口说明**: 创建新的耗卡记录,支持传入备注信息(可选,最大2000字符)
  8 +### 2. 更新耗卡记录
  9 +**接口地址**: `PUT /api/Extend/LqXhHyhk/{id}`
  10 +**接口说明**: 更新耗卡记录信息,支持修改备注(可选,最大2000字符)
  11 +### 3. 更新耗卡备注
  12 +**接口地址**: `PUT /api/Extend/LqXhHyhk/{id}/UpdateRemark`
  13 +**接口说明**: 专门用于更新耗卡记录的备注信息
  14 +### 4. 查询耗卡详情
  15 +**接口地址**: `GET /api/Extend/LqXhHyhk/{id}`
  16 +**接口说明**: 根据ID查询耗卡记录详情,返回包含备注信息
  17 +### 5. 查询耗卡列表
  18 +**接口地址**: `GET /api/Extend/LqXhHyhk`
  19 +**接口说明**: 分页查询耗卡记录列表,返回包含备注信息
  20 +### 6. 按健康师ID查询耗卡列表
  21 +**接口地址**: `GET /api/Extend/LqXhHyhk/GetListByJksId`
  22 +**接口说明**: 根据健康师ID查询耗卡记录列表,返回包含备注信息
  23 +### 7. 按科技部老师ID查询耗卡列表
  24 +**接口地址**: `GET /api/Extend/LqXhHyhk/GetListByKjbId`
  25 +**接口说明**: 根据科技部老师ID查询耗卡记录列表,返回包含备注信息
0 26 \ No newline at end of file
... ...
docs/lq_xh_hyhk表添加备注字段-修改清单.md 0 → 100644
  1 +# lq_xh_hyhk 表添加备注字段 - 修改清单
  2 +
  3 +## 📋 概述
  4 +
  5 +在 `lq_xh_hyhk`(会员耗卡)表中添加备注字段,用于记录耗卡相关的备注信息。
  6 +
  7 +## 🗄️ 数据库修改
  8 +
  9 +### 1. 数据库表结构修改
  10 +
  11 +**表名**: `lq_xh_hyhk`
  12 +
  13 +**新增字段**:
  14 +- **字段名**: `F_Remark`
  15 +- **数据类型**: `VARCHAR(500)` 或 `TEXT`(根据备注长度需求)
  16 +- **是否可空**: `YES`
  17 +- **默认值**: `NULL`
  18 +- **字段注释**: `备注`
  19 +
  20 +**SQL语句**:
  21 +```sql
  22 +ALTER TABLE lq_xh_hyhk
  23 +ADD COLUMN F_Remark VARCHAR(500) NULL COMMENT '备注' AFTER F_CancelRemark;
  24 +```
  25 +
  26 +## 💻 后端代码修改
  27 +
  28 +### 2. 实体类 (Entity)
  29 +
  30 +**文件**: `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_hyhk/LqXhHyhkEntity.cs`
  31 +
  32 +**修改内容**: 在 `LqXhHyhkEntity` 类中添加备注属性
  33 +
  34 +```csharp
  35 +/// <summary>
  36 +/// 备注
  37 +/// </summary>
  38 +[SugarColumn(ColumnName = "F_Remark")]
  39 +public string Remark { get; set; }
  40 +```
  41 +
  42 +**位置**: 建议添加在 `CancelRemark` 字段之后(第128行之后)
  43 +
  44 +---
  45 +
  46 +### 3. DTO 类修改
  47 +
  48 +#### 3.1 创建输入DTO
  49 +
  50 +**文件**: `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkCrInput.cs`
  51 +
  52 +**修改内容**: 添加备注字段
  53 +
  54 +```csharp
  55 +/// <summary>
  56 +/// 备注
  57 +/// </summary>
  58 +public string remark { get; set; }
  59 +```
  60 +
  61 +**位置**: 建议添加在 `appointmentId` 字段之后(第90行之后)
  62 +
  63 +---
  64 +
  65 +#### 3.2 详情输出DTO
  66 +
  67 +**文件**: `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkInfoOutput.cs`
  68 +
  69 +**修改内容**: 添加备注字段
  70 +
  71 +```csharp
  72 +/// <summary>
  73 +/// 备注
  74 +/// </summary>
  75 +public string remark { get; set; }
  76 +```
  77 +
  78 +**位置**: 建议添加在 `cancelRemark` 字段之后(第113行之后)
  79 +
  80 +---
  81 +
  82 +#### 3.3 列表输出DTO
  83 +
  84 +**文件**: `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs`
  85 +
  86 +**修改内容**: 添加备注字段
  87 +
  88 +```csharp
  89 +/// <summary>
  90 +/// 备注
  91 +/// </summary>
  92 +public string remark { get; set; }
  93 +```
  94 +
  95 +**位置**: 建议添加在 `signatureFile` 字段之后(第95行之后)
  96 +
  97 +---
  98 +
  99 +**注意**: `LqXhHyhkUpInput` 继承自 `LqXhHyhkCrInput`,会自动包含备注字段,无需单独修改。
  100 +
  101 +---
  102 +
  103 +### 4. Service 方法修改
  104 +
  105 +**文件**: `netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs`
  106 +
  107 +#### 4.1 GetInfo 方法(查询详情)
  108 +
  109 +**位置**: 第101行 `GetInfo(string id)`
  110 +
  111 +**修改说明**:
  112 +- 使用 `entity.Adapt<LqXhHyhkInfoOutput>()` 自动映射,无需手动修改
  113 +- 但需要确保 `LqXhHyhkInfoOutput` 已包含 `remark` 字段
  114 +
  115 +**影响**: ✅ 自动支持(通过 Adapt 映射)
  116 +
  117 +---
  118 +
  119 +#### 4.2 GetList 方法(查询列表)
  120 +
  121 +**位置**: 第293行 `GetList([FromQuery] LqXhHyhkListQueryInput input)`
  122 +
  123 +**修改内容**: 在多个 `Select` 语句中添加 `remark` 字段
  124 +
  125 +**需要修改的位置**:
  126 +
  127 +1. **第342-370行**: 基础查询的 Select 语句
  128 + ```csharp
  129 + remark = it.Remark,
  130 + ```
  131 +
  132 +2. **第590-611行**: 按健康师ID查询的 Select 语句(GetListByJksId)
  133 + ```csharp
  134 + remark = hyhk.Remark,
  135 + ```
  136 +
  137 +3. **第720-741行**: 按科技部老师ID查询的 Select 语句(GetListByKjbId)
  138 + ```csharp
  139 + remark = hyhk.Remark,
  140 + ```
  141 +
  142 +4. **第1142-1165行**: 其他查询条件的 Select 语句
  143 + ```csharp
  144 + remark = it.Remark,
  145 + ```
  146 +
  147 +---
  148 +
  149 +#### 4.3 Create 方法(创建记录)
  150 +
  151 +**位置**: 第875行 `Create([FromBody] LqXhHyhkCrInput input)`
  152 +
  153 +**修改说明**:
  154 +- 使用 `input.Adapt<LqXhHyhkEntity>()` 自动映射,无需手动修改
  155 +- 但需要确保 `LqXhHyhkCrInput` 已包含 `remark` 字段
  156 +
  157 +**影响**: ✅ 自动支持(通过 Adapt 映射)
  158 +
  159 +---
  160 +
  161 +#### 4.4 Update 方法(更新记录)
  162 +
  163 +**位置**: 第1259行 `Update(string id, [FromBody] LqXhHyhkUpInput input)`
  164 +
  165 +**修改说明**:
  166 +- 使用 `input.Adapt<LqXhHyhkEntity>()` 自动映射,无需手动修改
  167 +- 但需要确保 `LqXhHyhkUpInput`(继承自 `LqXhHyhkCrInput`)已包含 `remark` 字段
  168 +
  169 +**影响**: ✅ 自动支持(通过 Adapt 映射)
  170 +
  171 +---
  172 +
  173 +#### 4.5 其他方法检查
  174 +
  175 +以下方法可能也需要检查,但根据代码分析,它们主要使用实体类,会自动支持:
  176 +
  177 +- `Delete` 方法(第1504行)- 删除操作,无需修改
  178 +- `Cancel` 方法(第1618行)- 作废操作,可能需要显示备注,但不需要修改逻辑
  179 +- `ExportConsumeItemDetailList` 方法(第2340行)- 导出功能,如果导出包含备注,需要修改
  180 +
  181 +---
  182 +
  183 +### 5. 其他 Service 检查
  184 +
  185 +以下 Service 可能使用了 `LqXhHyhkEntity`,需要检查是否需要修改:
  186 +
  187 +1. **LqStatisticsService.cs** - 统计服务
  188 + - 检查是否有统计查询使用备注字段
  189 + - 通常统计不需要备注,可能无需修改
  190 +
  191 +2. **LqTechDepartmentDashboardService.cs** - 科技部驾驶舱
  192 + - 检查是否有查询使用备注字段
  193 + - 通常驾驶舱不需要备注,可能无需修改
  194 +
  195 +3. **LqStoreDashboardService.cs** - 门店驾驶舱
  196 + - 检查是否有查询使用备注字段
  197 + - 通常驾驶舱不需要备注,可能无需修改
  198 +
  199 +4. **LqTechTeacherSalaryService.cs** - 科技部老师工资
  200 + - 检查是否有查询使用备注字段
  201 + - 通常工资计算不需要备注,可能无需修改
  202 +
  203 +5. **LqKhxxService.cs** - 客户资料服务
  204 + - 检查是否有关联查询使用备注字段
  205 +
  206 +6. **LqTkjlbService.cs** - 拓客记录服务
  207 + - 检查是否有关联查询使用备注字段
  208 +
  209 +7. **LqBusinessUnitDashboardService.cs** - 事业部驾驶舱
  210 + - 检查是否有查询使用备注字段
  211 +
  212 +8. **LqReportService.cs** - 报表服务
  213 + - 检查是否有报表使用备注字段
  214 +
  215 +9. **MemberPortraitService.cs** - 会员画像服务
  216 + - 检查是否有查询使用备注字段
  217 +
  218 +10. **LqXmzlService.cs** - 项目资料服务
  219 + - 检查是否有关联查询使用备注字段
  220 +
  221 +11. **LqXhFeedbackService.cs** - 耗卡反馈服务
  222 + - 检查是否有关联查询使用备注字段
  223 +
  224 +**建议**: 这些服务通常不需要备注字段,但需要根据实际业务需求确认。
  225 +
  226 +---
  227 +
  228 +## 🎨 前端代码修改
  229 +
  230 +### 6. 前端页面修改
  231 +
  232 +#### 6.1 表单页面(新建/编辑)
  233 +
  234 +**文件**: `antis-ncc-admin/src/views/lqXhHyhk/Form.vue`
  235 +
  236 +**修改内容**:
  237 +- 在表单中添加备注输入框
  238 +- 位置建议:在"费用信息"区域之后,"品项明细"之前
  239 +
  240 +**代码示例**:
  241 +```vue
  242 +<el-col :span="24">
  243 + <el-form-item label="备注" prop="remark">
  244 + <el-input
  245 + v-model="dataForm.remark"
  246 + type="textarea"
  247 + :rows="3"
  248 + placeholder="请输入备注信息"
  249 + clearable
  250 + :style='{"width":"100%"}'
  251 + maxlength="500"
  252 + show-word-limit>
  253 + </el-input>
  254 + </el-form-item>
  255 +</el-col>
  256 +```
  257 +
  258 +**需要修改的位置**:
  259 +- 在 `dataForm` 对象中添加 `remark: ''`
  260 +- 在表单模板中添加备注输入框(建议在第86行之后,第94行之前)
  261 +
  262 +---
  263 +
  264 +#### 6.2 详情页面
  265 +
  266 +**文件**: `antis-ncc-admin/src/views/lqXhHyhk/detail.vue`
  267 +
  268 +**修改内容**:
  269 +- 在详情页面中显示备注信息
  270 +- 位置建议:在"费用信息"卡片之后,或单独一个"备注信息"卡片
  271 +
  272 +**代码示例**:
  273 +```vue
  274 +<!-- 备注信息卡片 -->
  275 +<el-card class="info-card" shadow="hover" v-if="dataForm.remark">
  276 + <div slot="header" class="card-header">
  277 + <i class="el-icon-edit-outline"></i>
  278 + <span class="card-title">备注信息</span>
  279 + </div>
  280 + <div class="info-item">
  281 + <p class="remark-content">{{ dataForm.remark || '无' }}</p>
  282 + </div>
  283 +</el-card>
  284 +```
  285 +
  286 +**需要修改的位置**:
  287 +- 在详情数据绑定中添加备注字段的显示(建议在第88行之后)
  288 +
  289 +---
  290 +
  291 +#### 6.3 列表页面
  292 +
  293 +**文件**: `antis-ncc-admin/src/views/lqXhHyhk/index.vue`
  294 +
  295 +**修改内容**:
  296 +- 在列表表格中添加备注列(可选,根据业务需求)
  297 +- 如果备注内容较长,可以考虑使用 tooltip 显示完整内容
  298 +
  299 +**代码示例**:
  300 +```vue
  301 +<el-table-column prop="remark" label="备注" width="200" show-overflow-tooltip>
  302 + <template slot-scope="scope">
  303 + <span>{{ scope.row.remark || '无' }}</span>
  304 + </template>
  305 +</el-table-column>
  306 +```
  307 +
  308 +**需要修改的位置**:
  309 +- 在表格列定义中添加备注列(根据实际表格结构确定位置)
  310 +
  311 +---
  312 +
  313 +### 7. 导出功能检查
  314 +
  315 +**文件**: `netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs`
  316 +
  317 +**方法**: `ExportConsumeItemDetailList`(第2340行)
  318 +
  319 +**修改说明**:
  320 +- 如果导出功能需要包含备注字段,需要在导出DTO和Excel配置中添加
  321 +- 需要检查 `ConsumeItemDetailExportOutput` DTO 是否包含备注字段
  322 +- 需要在 Excel 配置的 `paramList` 中添加备注列
  323 +
  324 +**影响**: ⚠️ 需要根据业务需求确认是否在导出中包含备注
  325 +
  326 +---
  327 +
  328 +## 📝 修改优先级
  329 +
  330 +### 高优先级(必须修改)
  331 +
  332 +1. ✅ 数据库表结构修改
  333 +2. ✅ 实体类 `LqXhHyhkEntity.cs`
  334 +3. ✅ DTO类:
  335 + - `LqXhHyhkCrInput.cs`
  336 + - `LqXhHyhkInfoOutput.cs`
  337 + - `LqXhHyhkListOutput.cs`
  338 +4. ✅ Service `GetList` 方法中的 Select 语句
  339 +5. ✅ 前端表单页面 `Form.vue`
  340 +6. ✅ 前端详情页面 `detail.vue`
  341 +
  342 +### 中优先级(建议修改)
  343 +
  344 +7. ⚠️ 前端列表页面 `index.vue`(根据业务需求)
  345 +8. ⚠️ 导出功能(根据业务需求)
  346 +
  347 +### 低优先级(可选修改)
  348 +
  349 +9. ⚠️ 其他 Service 检查(根据实际使用情况)
  350 +
  351 +---
  352 +
  353 +## 🔍 测试检查清单
  354 +
  355 +修改完成后,需要测试以下功能:
  356 +
  357 +- [ ] 创建耗卡记录时,可以输入备注
  358 +- [ ] 更新耗卡记录时,可以修改备注
  359 +- [ ] 查询耗卡详情时,可以显示备注
  360 +- [ ] 查询耗卡列表时,可以显示备注(如果列表包含)
  361 +- [ ] 备注字段可以为空
  362 +- [ ] 备注字段长度限制正确(500字符)
  363 +- [ ] 前端表单验证正常
  364 +- [ ] 数据库字段添加成功
  365 +
  366 +---
  367 +
  368 +## 📌 注意事项
  369 +
  370 +1. **字段命名**: 使用 `F_Remark` 作为数据库字段名,`Remark` 作为实体类属性名,`remark` 作为 DTO 属性名(小写开头,符合现有命名规范)
  371 +
  372 +2. **字段长度**: 建议使用 `VARCHAR(500)`,如果可能超过500字符,可以使用 `TEXT` 类型
  373 +
  374 +3. **兼容性**: 新增字段为可空字段,不会影响现有数据
  375 +
  376 +4. **自动映射**: Create 和 Update 方法使用 `Adapt` 自动映射,只要 DTO 包含字段即可自动支持
  377 +
  378 +5. **列表查询**: GetList 方法中有多个 Select 语句,需要全部添加备注字段
  379 +
  380 +6. **前端显示**: 备注内容可能较长,建议使用 `textarea` 输入,使用 `show-overflow-tooltip` 或单独卡片显示
  381 +
  382 +---
  383 +
  384 +## 📚 相关文件清单
  385 +
  386 +### 后端文件
  387 +1. `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_hyhk/LqXhHyhkEntity.cs`
  388 +2. `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkCrInput.cs`
  389 +3. `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkInfoOutput.cs`
  390 +4. `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs`
  391 +5. `netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs`
  392 +
  393 +### 前端文件
  394 +1. `antis-ncc-admin/src/views/lqXhHyhk/Form.vue`
  395 +2. `antis-ncc-admin/src/views/lqXhHyhk/detail.vue`
  396 +3. `antis-ncc-admin/src/views/lqXhHyhk/index.vue`(可选)
  397 +
  398 +### 数据库
  399 +1. `lq_xh_hyhk` 表结构修改
  400 +
  401 +---
  402 +
  403 +## ✅ 总结
  404 +
  405 +本次修改涉及:
  406 +- **数据库**: 1个表
  407 +- **后端实体类**: 1个
  408 +- **后端DTO**: 3个
  409 +- **后端Service**: 1个(多个方法)
  410 +- **前端页面**: 2-3个
  411 +
  412 +预计修改工作量:**中等**(主要是重复性的字段添加和映射)
... ...
docs/全员战报明细到店数统计问题修复.md 0 → 100644
  1 +# 全员战报明细到店数统计问题修复
  2 +
  3 +## 📋 问题描述
  4 +
  5 +在全员战报明细统计中,发现有的数据的到店数比拓客数还多,不符合业务逻辑。
  6 +
  7 +**业务规则**:
  8 +- 一个人多次到店的话在这里就算一次
  9 +- 到店数应该小于等于拓客数
  10 +
  11 +## 🔍 问题分析
  12 +
  13 +### 问题位置
  14 +**文件**: `netcore/src/Modularity/Extend/NCC.Extend/LqTkDashboardService.cs`
  15 +**方法**: `GetEmployeeParticipationStatistics`
  16 +**行数**: 第575-588行(修复前)
  17 +
  18 +### 问题原因
  19 +
  20 +**修复前的SQL逻辑**:
  21 +```sql
  22 +LEFT JOIN (
  23 + -- 到店人数统计
  24 + SELECT
  25 + tk.F_ExpansionUserId as EmployeeId,
  26 + COUNT(DISTINCT tk.F_MemberId) as VisitCount
  27 + FROM lq_tkjlb tk
  28 + INNER JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1
  29 + WHERE {eventFilter}
  30 + AND tk.F_ExpansionTime >= '{startTimeStr}'
  31 + AND tk.F_ExpansionTime <= '{endTimeStr}'
  32 + AND xh.hksj >= '{startTimeStr}'
  33 + AND xh.hksj <= '{endTimeStr}'
  34 + GROUP BY tk.F_ExpansionUserId
  35 +) visit ON emp.EmployeeId = visit.EmployeeId
  36 +```
  37 +
  38 +**问题点**:
  39 +1. **缺少时间关联约束**:没有限制到店时间必须在拓客时间之后,可能统计到拓客之前的到店记录
  40 +2. **JOIN导致重复计数**:当同一个会员被同一个员工多次拓客,且该会员多次到店时,`INNER JOIN` 会产生多行记录
  41 +3. **虽然使用了 DISTINCT,但 JOIN 的逻辑可能导致计数不准确**
  42 +
  43 +### 问题场景示例
  44 +
  45 +假设:
  46 +- 员工A在1月1日拓客了会员B
  47 +- 员工A在1月5日又拓客了会员B(重复拓客)
  48 +- 会员B在1月3日到店1次
  49 +- 会员B在1月6日到店1次
  50 +
  51 +**修复前的统计结果**:
  52 +- 拓客数:1(COUNT(DISTINCT tk.F_MemberId))
  53 +- 到店数:2(因为 JOIN 产生了2条记录:1月1日拓客+1月3日到店,1月5日拓客+1月6日到店)
  54 +
  55 +**问题**:到店数(2)> 拓客数(1),不符合逻辑
  56 +
  57 +## ✅ 修复方案
  58 +
  59 +### 修复后的SQL逻辑(最终版本)
  60 +
  61 +```sql
  62 +LEFT JOIN (
  63 + -- 到店人数统计(一个人多次到店只算一次)
  64 + -- 先找出每个员工拓客的所有会员(去重),然后统计这些会员中有到店记录的会员数
  65 + SELECT
  66 + emp_members.EmployeeId,
  67 + COUNT(DISTINCT CASE WHEN xh.hy IS NOT NULL THEN emp_members.MemberId END) as VisitCount
  68 + FROM (
  69 + -- 先获取每个员工拓客的所有会员(去重),并记录首次拓客时间
  70 + SELECT DISTINCT
  71 + tk.F_ExpansionUserId as EmployeeId,
  72 + tk.F_MemberId as MemberId,
  73 + MIN(tk.F_ExpansionTime) as FirstExpansionTime
  74 + FROM lq_tkjlb tk
  75 + WHERE {eventFilter}
  76 + AND tk.F_ExpansionTime >= '{startTimeStr}'
  77 + AND tk.F_ExpansionTime <= '{endTimeStr}'
  78 + AND tk.F_MemberId IS NOT NULL
  79 + GROUP BY tk.F_ExpansionUserId, tk.F_MemberId
  80 + ) emp_members
  81 + LEFT JOIN lq_xh_hyhk xh ON emp_members.MemberId = xh.hy
  82 + AND xh.F_IsEffective = 1
  83 + AND xh.hksj >= emp_members.FirstExpansionTime
  84 + AND xh.hksj >= '{startTimeStr}'
  85 + AND xh.hksj <= '{endTimeStr}'
  86 + GROUP BY emp_members.EmployeeId
  87 +) visit ON emp.EmployeeId = visit.EmployeeId
  88 +```
  89 +
  90 +### 修复要点
  91 +
  92 +1. **先获取拓客会员列表(去重)**:
  93 + - 使用子查询先找出每个员工拓客的所有会员(去重)
  94 + - 记录每个会员的首次拓客时间(`MIN(tk.F_ExpansionTime)`)
  95 + - 确保同一个会员只出现一次,即使被多次拓客
  96 +
  97 +2. **统计有到店记录的会员数**:
  98 + - 在去重后的会员列表中,LEFT JOIN 耗卡表
  99 + - 使用 `COUNT(DISTINCT CASE WHEN xh.hy IS NOT NULL THEN emp_members.MemberId END)` 统计有到店记录的会员数
  100 + - 确保到店时间在首次拓客时间之后(`xh.hksj >= emp_members.FirstExpansionTime`)
  101 +
  102 +3. **双重去重保障**:
  103 + - 第一层:在子查询中使用 `GROUP BY tk.F_ExpansionUserId, tk.F_MemberId` 确保每个员工-会员组合只出现一次
  104 + - 第二层:使用 `COUNT(DISTINCT ...)` 确保最终统计时每个会员只算一次
  105 +
  106 +### 修复后的统计结果(使用上面的示例)
  107 +
  108 +- 拓客数:1(COUNT(DISTINCT tk.F_MemberId))
  109 +- 到店数:1(会员B有到店记录,只算一次)
  110 +
  111 +**结果**:到店数(1)≤ 拓客数(1),符合逻辑 ✅
  112 +
  113 +## 📝 修改文件
  114 +
  115 +- **文件**: `netcore/src/Modularity/Extend/NCC.Extend/LqTkDashboardService.cs`
  116 +- **方法**: `GetEmployeeParticipationStatistics`
  117 +- **修改位置**: 第575-590行
  118 +
  119 +## ✅ 验证建议
  120 +
  121 +1. **验证到店数 ≤ 拓客数**:检查所有员工的统计数据,确保到店数不超过拓客数
  122 +2. **验证去重逻辑**:选择一个多次到店的会员,验证其只被统计一次
  123 +3. **验证时间约束**:选择一个拓客前有到店记录的会员,验证不会被统计到
  124 +
  125 +## 🔗 相关接口
  126 +
  127 +**接口地址**: `POST /api/Extend/LqTkDashboard/GetEmployeeParticipationStatistics`
  128 +
  129 +**接口说明**: 获取员工参与统计(全员战报明细)
  130 +
  131 +**返回字段**:
  132 +- `ExpansionCount`: 拓客数
  133 +- `VisitCount`: 到店数(修复后)
  134 +- `BillingCount`: 开单数
  135 +- `BillingAmount`: 开单金额
... ...
docs/全部门店开单记录汇总导出接口说明.md 0 → 100644
  1 +# 全部门店开单记录汇总导出接口
  2 +
  3 +## 接口地址
  4 +
  5 +```
  6 +GET /api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores
  7 +```
  8 +
  9 +## 接口说明
  10 +
  11 +按照Excel格式导出全部门店的开单记录汇总,每个品项每个健康师一行。如果某个品项有多个健康师,会展开为多行数据。
  12 +
  13 +## 请求参数
  14 +
  15 +| 参数名 | 类型 | 必填 | 说明 |
  16 +|--------|------|------|------|
  17 +| StartTime | DateTime | 是 | 开始时间(格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss) |
  18 +| EndTime | DateTime | 是 | 结束时间(格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss) |
  19 +| MemberId | string | 否 | 客户ID(会员ID),筛选指定客户 |
  20 +| ItemCategory | string | 否 | 品项分类,筛选指定分类 |
  21 +| ItemId | string | 否 | 品项ID,筛选指定品项 |
  22 +| HealthCoachId | string | 否 | 健康师ID,筛选指定健康师 |
  23 +
  24 +## 请求示例
  25 +
  26 +### 基础请求(仅时间范围)
  27 +
  28 +```javascript
  29 +// Axios 示例
  30 +axios.get('/api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores', {
  31 + params: {
  32 + StartTime: '2026-01-01',
  33 + EndTime: '2026-01-31'
  34 + },
  35 + headers: {
  36 + 'Authorization': 'Bearer ' + token
  37 + },
  38 + responseType: 'blob' // 重要:设置为 blob 以接收文件
  39 +}).then(response => {
  40 + // 创建下载链接
  41 + const url = window.URL.createObjectURL(new Blob([response.data]));
  42 + const link = document.createElement('a');
  43 + link.href = url;
  44 + link.setAttribute('download', '开单记录汇总.xls');
  45 + document.body.appendChild(link);
  46 + link.click();
  47 + link.remove();
  48 +});
  49 +```
  50 +
  51 +### 完整请求(包含筛选条件)
  52 +
  53 +```javascript
  54 +axios.get('/api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores', {
  55 + params: {
  56 + StartTime: '2026-01-01',
  57 + EndTime: '2026-01-31',
  58 + MemberId: '123456789', // 可选
  59 + ItemCategory: '分类名称', // 可选
  60 + ItemId: '987654321', // 可选
  61 + HealthCoachId: '111222333' // 可选
  62 + },
  63 + headers: {
  64 + 'Authorization': 'Bearer ' + token
  65 + },
  66 + responseType: 'blob'
  67 +}).then(response => {
  68 + // 处理文件下载
  69 + const url = window.URL.createObjectURL(new Blob([response.data]));
  70 + const link = document.createElement('a');
  71 + link.href = url;
  72 + link.setAttribute('download', '开单记录汇总.xls');
  73 + document.body.appendChild(link);
  74 + link.click();
  75 + link.remove();
  76 +});
  77 +```
  78 +
  79 +## 响应格式
  80 +
  81 +### 成功响应(200)
  82 +
  83 +```json
  84 +{
  85 + "code": 200,
  86 + "msg": "操作成功",
  87 + "data": {
  88 + "name": "开单记录汇总_2026-01-22_152816.xls",
  89 + "url": "/api/File/Download?encryption=..."
  90 + }
  91 +}
  92 +```
  93 +
  94 +### 错误响应(400/500)
  95 +
  96 +```json
  97 +{
  98 + "code": 400,
  99 + "msg": "开始时间和结束时间不能为空"
  100 +}
  101 +```
  102 +
  103 +## Excel 列说明
  104 +
  105 +导出的Excel文件包含以下列(按顺序):
  106 +
  107 +1. **开单日期** - 开单记录的日期
  108 +2. **单据门店** - 开单门店的中文名称
  109 +3. **客户姓名** - 客户姓名
  110 +4. **客户电话** - 客户电话
  111 +5. **金三角** - 金三角信息
  112 +6. **客户类型** - 客户类型
  113 +7. **健康师** - 健康师姓名
  114 +8. **业绩金额** - 健康师业绩金额(格式:¥0.00)
  115 +9. **相关品项** - 品项名称
  116 +10. **分类** - 品项分类
  117 +11. **类型** - 品项类型(购买/体验/赠送等)
  118 +12. **单价** - 品项单价(格式:¥0.00)
  119 +13. **次数** - 项目次数
  120 +14. **品项金额** - 品项总金额(格式:¥0.00)
  121 +15. **已付金额** - 已付金额(格式:¥0.00)
  122 +16. **欠款金额** - 欠款金额(格式:¥0.00)
  123 +17. **总金额** - 总金额(格式:¥0.00)
  124 +18. **支付方式** - 支付方式
  125 +19. **合作机构** - 合作机构名称
  126 +20. **结算机构** - 结算机构名称
  127 +21. **活动名称** - 活动名称
  128 +22. **备注** - 备注信息
  129 +23. **开单时间** - 开单时间(格式:yyyy/MM/dd HH:mm:ss)
  130 +
  131 +## 数据展开规则
  132 +
  133 +- 如果一个开单记录有多个品项,每个品项一行
  134 +- 如果一个品项有多个健康师,每个健康师一行
  135 +- 如果一个品项没有健康师,健康师相关字段显示"无",业绩金额显示"¥0.00"
  136 +
  137 +## 注意事项
  138 +
  139 +1. **时间格式**:建议使用 `yyyy-MM-dd` 格式,系统会自动处理时间范围
  140 +2. **文件下载**:前端需要设置 `responseType: 'blob'` 来接收文件
  141 +3. **文件格式**:导出文件为 `.xls` 格式(Excel 97-2003)
  142 +4. **数据量**:如果数据量很大,导出可能需要较长时间,建议前端显示加载提示
  143 +5. **权限**:需要登录并携带有效的 Authorization token
  144 +
  145 +## Vue 2 + Element UI 完整示例
  146 +
  147 +```vue
  148 +<template>
  149 + <div>
  150 + <el-button
  151 + type="primary"
  152 + :loading="exporting"
  153 + @click="handleExport"
  154 + >
  155 + 导出全部门店开单记录汇总
  156 + </el-button>
  157 + </div>
  158 +</template>
  159 +
  160 +<script>
  161 +import { exportBillingRecordSummaryAllStores } from '@/api/billing'
  162 +
  163 +export default {
  164 + data() {
  165 + return {
  166 + exporting: false,
  167 + queryParams: {
  168 + StartTime: '2026-01-01',
  169 + EndTime: '2026-01-31'
  170 + }
  171 + }
  172 + },
  173 + methods: {
  174 + async handleExport() {
  175 + try {
  176 + this.exporting = true
  177 + this.$message.info('正在导出,请稍候...')
  178 +
  179 + const response = await exportBillingRecordSummaryAllStores(this.queryParams)
  180 +
  181 + if (response.code === 200 && response.data) {
  182 + // 使用返回的下载URL
  183 + const downloadUrl = this.$store.getters.baseUrl + response.data.url
  184 + window.open(downloadUrl, '_blank')
  185 +
  186 + this.$message.success('导出成功')
  187 + } else {
  188 + this.$message.error(response.msg || '导出失败')
  189 + }
  190 + } catch (error) {
  191 + console.error('导出失败:', error)
  192 + this.$message.error('导出失败,请稍后重试')
  193 + } finally {
  194 + this.exporting = false
  195 + }
  196 + }
  197 + }
  198 +}
  199 +</script>
  200 +```
  201 +
  202 +## API 封装示例(api/billing.js)
  203 +
  204 +```javascript
  205 +import request from '@/utils/request'
  206 +
  207 +/**
  208 + * 导出全部门店开单记录汇总
  209 + * @param {Object} params 查询参数
  210 + * @param {string} params.StartTime 开始时间
  211 + * @param {string} params.EndTime 结束时间
  212 + * @param {string} [params.MemberId] 客户ID
  213 + * @param {string} [params.ItemCategory] 品项分类
  214 + * @param {string} [params.ItemId] 品项ID
  215 + * @param {string} [params.HealthCoachId] 健康师ID
  216 + * @returns {Promise}
  217 + */
  218 +export function exportBillingRecordSummaryAllStores(params) {
  219 + return request({
  220 + url: '/api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores',
  221 + method: 'get',
  222 + params: params
  223 + })
  224 +}
  225 +```
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryExportAllStoresInput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +using NCC.Common.Filter;
  4 +
  5 +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
  6 +{
  7 + /// <summary>
  8 + /// 全部门店开单记录汇总导出查询输入
  9 + /// </summary>
  10 + public class BillingRecordSummaryExportAllStoresInput : PageInputBase
  11 + {
  12 + /// <summary>
  13 + /// 开始时间
  14 + /// </summary>
  15 + [Display(Name = "开始时间", Description = "查询开单记录的开始时间")]
  16 + public DateTime? StartTime { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 结束时间
  20 + /// </summary>
  21 + [Display(Name = "结束时间", Description = "查询开单记录的结束时间")]
  22 + public DateTime? EndTime { get; set; }
  23 +
  24 + /// <summary>
  25 + /// 品项分类
  26 + /// </summary>
  27 + [Display(Name = "品项分类", Description = "筛选品项分类")]
  28 + public string ItemCategory { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 品项ID
  32 + /// </summary>
  33 + [Display(Name = "品项ID", Description = "筛选品项ID")]
  34 + public string ItemId { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 健康师ID
  38 + /// </summary>
  39 + [Display(Name = "健康师ID", Description = "筛选健康师ID")]
  40 + public string HealthCoachId { get; set; }
  41 +
  42 + /// <summary>
  43 + /// 客户ID(会员ID)
  44 + /// </summary>
  45 + [Display(Name = "客户ID", Description = "筛选客户ID")]
  46 + public string MemberId { get; set; }
  47 + }
  48 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryExportOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
  4 +{
  5 + /// <summary>
  6 + /// 开单记录汇总导出输出
  7 + /// </summary>
  8 + public class BillingRecordSummaryExportOutput
  9 + {
  10 + /// <summary>
  11 + /// 开单日期
  12 + /// </summary>
  13 + public string BillingDate { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 单据门店
  17 + /// </summary>
  18 + public string StoreName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 客户姓名
  22 + /// </summary>
  23 + public string CustomerName { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 客户电话
  27 + /// </summary>
  28 + public string CustomerPhone { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 金三角
  32 + /// </summary>
  33 + public string GoldTriangle { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 客户类型
  37 + /// </summary>
  38 + public string CustomerType { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 健康师
  42 + /// </summary>
  43 + public string HealthCoach { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 业绩金额
  47 + /// </summary>
  48 + public string PerformanceAmount { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 相关品项
  52 + /// </summary>
  53 + public string RelatedItem { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 分类
  57 + /// </summary>
  58 + public string Category { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 类型
  62 + /// </summary>
  63 + public string Type { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 单价
  67 + /// </summary>
  68 + public string UnitPrice { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 次数
  72 + /// </summary>
  73 + public string ProjectNumber { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 品项金额
  77 + /// </summary>
  78 + public string ItemAmount { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 已付金额
  82 + /// </summary>
  83 + public string PaidAmount { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 欠款金额
  87 + /// </summary>
  88 + public string DebtAmount { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 总金额
  92 + /// </summary>
  93 + public string TotalAmount { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 支付方式
  97 + /// </summary>
  98 + public string PaymentMethod { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 合作机构
  102 + /// </summary>
  103 + public string CooperationInstitution { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 结算机构
  107 + /// </summary>
  108 + public string SettlementInstitution { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 活动名称
  112 + /// </summary>
  113 + public string ActivityName { get; set; }
  114 +
  115 + /// <summary>
  116 + /// 备注
  117 + /// </summary>
  118 + public string Remark { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 开单时间
  122 + /// </summary>
  123 + public string CreateTime { get; set; }
  124 + }
  125 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkCrInput.cs
... ... @@ -90,6 +90,11 @@ namespace NCC.Extend.Entitys.Dto.LqXhHyhk
90 90 public string appointmentId { get; set; }
91 91  
92 92 /// <summary>
  93 + /// 备注
  94 + /// </summary>
  95 + public string remark { get; set; }
  96 +
  97 + /// <summary>
93 98 /// 耗卡_品项明细
94 99 /// </summary>
95 100 public List<LqXhPxmxCrInput> lqXhPxmxList { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkInfoOutput.cs
... ... @@ -113,6 +113,11 @@ namespace NCC.Extend.Entitys.Dto.LqXhHyhk
113 113 public string cancelRemark { get; set; }
114 114  
115 115 /// <summary>
  116 + /// 备注
  117 + /// </summary>
  118 + public string remark { get; set; }
  119 +
  120 + /// <summary>
116 121 /// 加班系数(NULL或0表示非加班单,大于0表示加班单,如 0.5、1、1.5)
117 122 /// </summary>
118 123 public decimal? overtimeCoefficient { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs
... ... @@ -95,6 +95,11 @@ namespace NCC.Extend.Entitys.Dto.LqXhHyhk
95 95 public string signatureFile { get; set; }
96 96  
97 97 /// <summary>
  98 + /// 备注
  99 + /// </summary>
  100 + public string remark { get; set; }
  101 +
  102 + /// <summary>
98 103 /// 是否有效
99 104 /// </summary>
100 105 public int isEffective { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkUpdateRemarkInput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqXhHyhk
  2 +{
  3 + /// <summary>
  4 + /// 更新耗卡备注输入参数
  5 + /// </summary>
  6 + public class LqXhHyhkUpdateRemarkInput
  7 + {
  8 + /// <summary>
  9 + /// 备注
  10 + /// </summary>
  11 + public string remark { get; set; }
  12 + }
  13 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_hyhk/LqXhHyhkEntity.cs
... ... @@ -128,6 +128,12 @@ namespace NCC.Extend.Entitys.lq_xh_hyhk
128 128 public string CancelRemark { get; set; }
129 129  
130 130 /// <summary>
  131 + /// 备注
  132 + /// </summary>
  133 + [SugarColumn(ColumnName = "F_Remark")]
  134 + public string Remark { get; set; }
  135 +
  136 + /// <summary>
131 137 /// 加班系数(NULL或0表示非加班单,大于0表示加班单,如 0.5、1、1.5)
132 138 /// </summary>
133 139 [SugarColumn(ColumnName = "F_OvertimeCoefficient")]
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
1 1 using System;
2 2 using System.Collections.Generic;
  3 +using System.IO;
3 4 using System.Linq;
4 5 using System.Net.Http;
5 6 using System.Threading.Tasks;
... ... @@ -96,6 +97,431 @@ namespace NCC.Extend.LqKdKdjlb
96 97 _dailyReportService = dailyReportService;
97 98 }
98 99  
  100 + #region 导出全部门店开单记录汇总
  101 + /// <summary>
  102 + /// 导出全部门店开单记录汇总
  103 + /// </summary>
  104 + /// <remarks>
  105 + /// 按照Excel格式导出全部门店的开单记录汇总,每个品项每个健康师一行
  106 + ///
  107 + /// 示例请求:
  108 + /// GET /api/Extend/lqkdkdjlb/billing-record-summary-export-all-stores?StartTime=2026-01-01&amp;EndTime=2026-01-31
  109 + ///
  110 + /// 参数说明:
  111 + /// - StartTime: 开始时间(必填)
  112 + /// - EndTime: 结束时间(必填)
  113 + /// - MemberId: 客户ID(可选)
  114 + /// - ItemCategory: 品项分类(可选)
  115 + /// - ItemId: 品项ID(可选)
  116 + /// - HealthCoachId: 健康师ID(可选)
  117 + /// </remarks>
  118 + /// <param name="input">查询参数</param>
  119 + /// <returns>Excel文件下载链接</returns>
  120 + /// <response code="200">导出成功</response>
  121 + /// <response code="400">参数错误</response>
  122 + /// <response code="500">服务器错误</response>
  123 + [HttpGet("billing-record-summary-export-all-stores"), NonUnify]
  124 + public async Task<dynamic> ExportBillingRecordSummaryAllStores([FromQuery] BillingRecordSummaryExportAllStoresInput input)
  125 + {
  126 + try
  127 + {
  128 + // 如果开始时间和结束时间为空,就默认是当月
  129 + if (input.StartTime == null || input.EndTime == null)
  130 + {
  131 + input.StartTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  132 + input.EndTime = DateTime.Now.AddDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month));
  133 + }
  134 +
  135 + // 确保时间不为null
  136 + if (input.StartTime == null || input.EndTime == null)
  137 + {
  138 + throw NCCException.Oh("开始时间和结束时间不能为空");
  139 + }
  140 +
  141 + _logger.LogInformation($"开始导出全部门店开单记录汇总,时间范围: {input.StartTime} 到 {input.EndTime}");
  142 +
  143 + // 构建开单记录查询条件(全部门店)
  144 + var startTime = input.StartTime.Value;
  145 + var endTime = input.EndTime.Value;
  146 + var billingQuery = _db.Queryable<LqKdKdjlbEntity>()
  147 + .Where(w => w.Kdrq.HasValue && w.Kdrq.Value >= startTime && w.Kdrq.Value <= endTime && w.IsEffective == StatusEnum.有效.GetHashCode());
  148 +
  149 + // 客户筛选
  150 + if (!string.IsNullOrEmpty(input.MemberId))
  151 + {
  152 + billingQuery = billingQuery.Where(w => w.Kdhy == input.MemberId);
  153 + }
  154 +
  155 + // 查询所有开单记录
  156 + var billingRecords = await billingQuery
  157 + .Select(it => new
  158 + {
  159 + id = it.Id,
  160 + djmd = it.Djmd,
  161 + djmdName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(x => x.Id == it.Djmd).Select(x => x.Dm),
  162 + jsj = it.Jsj,
  163 + kdrq = it.Kdrq,
  164 + gjlx = it.Gjlx,
  165 + zdyj = it.Zdyj,
  166 + sfyj = it.Sfyj,
  167 + qk = it.Qk,
  168 + hgjg = it.Hgjg,
  169 + hgjgName = SqlFunc.Subqueryable<LqHzfEntity>().Where(x => x.Id == it.Hgjg).Select(x => x.Hzmc),
  170 + kdhyc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Khmc),
  171 + kdhysjh = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Sjh),
  172 + fkfs = it.Fkfs,
  173 + fkyy = it.Fkyy,
  174 + fkyyName = SqlFunc.Subqueryable<LqHzfEntity>().Where(x => x.Id == it.Fkyy).Select(x => x.Hzmc),
  175 + activityId = it.ActivityId,
  176 + activityName = SqlFunc.Subqueryable<LqPackageInfoEntity>().Where(x => x.Id == it.ActivityId).Select(x => x.ActivityName),
  177 + khly = it.Khly,
  178 + bz = it.Bz,
  179 + createTime = it.CreateTime
  180 + })
  181 + .ToListAsync();
  182 +
  183 + if (!billingRecords.Any())
  184 + {
  185 + throw NCCException.Oh(ErrorCode.COM1005, "该时间段内无开单记录");
  186 + }
  187 +
  188 + _logger.LogInformation($"查询到 {billingRecords.Count} 条开单记录");
  189 +
  190 + if (billingRecords == null || !billingRecords.Any())
  191 + {
  192 + _logger.LogWarning("开单记录为空,无法继续导出");
  193 + throw NCCException.Oh(ErrorCode.COM1005, "该时间段内无开单记录");
  194 + }
  195 +
  196 + var billingIds = billingRecords.Select(x => x.id).ToList();
  197 + _logger.LogInformation($"提取到 {billingIds.Count} 个开单记录ID");
  198 +
  199 + // 构建品项明细查询条件
  200 + var itemDetailsQuery = _db.Queryable<LqKdPxmxEntity>()
  201 + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode());
  202 +
  203 + // 品项分类筛选
  204 + if (!string.IsNullOrEmpty(input.ItemCategory))
  205 + {
  206 + itemDetailsQuery = itemDetailsQuery.Where(w => w.ItemCategory == input.ItemCategory);
  207 + }
  208 +
  209 + // 品项筛选
  210 + if (!string.IsNullOrEmpty(input.ItemId))
  211 + {
  212 + itemDetailsQuery = itemDetailsQuery.Where(w => w.Px == input.ItemId);
  213 + }
  214 +
  215 + // 查询品项明细
  216 + _logger.LogInformation($"开始查询品项明细,开单记录ID数量: {billingIds.Count}");
  217 + var itemDetails = await itemDetailsQuery
  218 + .Select(it => new
  219 + {
  220 + id = it.Id,
  221 + glkdbh = it.Glkdbh,
  222 + px = it.Px,
  223 + pxmc = it.Pxmc,
  224 + pxjg = it.Pxjg,
  225 + sourceType = it.SourceType,
  226 + totalPrice = it.TotalPrice,
  227 + actualPrice = it.ActualPrice,
  228 + projectNumber = it.ProjectNumber,
  229 + remark = it.Remark,
  230 + itemCategory = it.ItemCategory,
  231 + })
  232 + .ToListAsync();
  233 + _logger.LogInformation($"查询到 {itemDetails?.Count ?? 0} 条品项明细");
  234 +
  235 + // 构建健康师业绩查询条件
  236 + var healthTeacherQuery = _db.Queryable<LqKdJksyjEntity>()
  237 + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode());
  238 +
  239 + // 健康师筛选
  240 + if (!string.IsNullOrEmpty(input.HealthCoachId))
  241 + {
  242 + healthTeacherQuery = healthTeacherQuery.Where(w => w.Jks == input.HealthCoachId || w.Jkszh == input.HealthCoachId);
  243 + }
  244 +
  245 + // 品项分类筛选(健康师业绩表)
  246 + if (!string.IsNullOrEmpty(input.ItemCategory))
  247 + {
  248 + healthTeacherQuery = healthTeacherQuery.Where(w => w.ItemCategory == input.ItemCategory);
  249 + }
  250 +
  251 + // 品项筛选(健康师业绩表)
  252 + if (!string.IsNullOrEmpty(input.ItemId))
  253 + {
  254 + healthTeacherQuery = healthTeacherQuery.Where(w => w.ItemId == input.ItemId);
  255 + }
  256 +
  257 + // 查询健康师业绩数据
  258 + _logger.LogInformation($"开始查询健康师业绩数据,开单记录ID数量: {billingIds.Count}");
  259 + var healthTeacherData = await healthTeacherQuery
  260 + .Select(it => new
  261 + {
  262 + id = it.Id,
  263 + glkdbh = it.Glkdbh,
  264 + jks = it.Jks,
  265 + jksxm = it.Jksxm,
  266 + jkszh = it.Jkszh,
  267 + jksyj = it.Jksyj,
  268 + kdpxid = it.Kdpxid,
  269 + yjsj = it.Yjsj,
  270 + jsj_id = it.Jsj_id,
  271 + itemCategory = it.ItemCategory,
  272 + storeId = it.StoreId,
  273 + itemId = it.ItemId,
  274 + itemName = it.ItemName,
  275 + })
  276 + .ToListAsync();
  277 + _logger.LogInformation($"查询到 {healthTeacherData?.Count ?? 0} 条健康师业绩数据");
  278 +
  279 + // 批量查询金三角信息
  280 + var goldTriangleDict = new Dictionary<string, string>();
  281 + var jsjIds = billingRecords.Where(x => !string.IsNullOrEmpty(x.jsj)).Select(x => x.jsj).Distinct().ToList();
  282 + if (jsjIds.Any())
  283 + {
  284 + var jsjList = await _db.Queryable<LqJinsanjiaoUserEntity>()
  285 + .Where(x => jsjIds.Contains(x.Id))
  286 + .Select(x => new { x.Id, x.UserName })
  287 + .ToListAsync();
  288 + goldTriangleDict = jsjList.ToDictionary(x => x.Id, x => x.UserName ?? "");
  289 + }
  290 +
  291 + // 按品项明细ID分组健康师业绩
  292 + var healthTeacherDict = healthTeacherData
  293 + .Where(x => !string.IsNullOrEmpty(x.kdpxid))
  294 + .GroupBy(x => x.kdpxid)
  295 + .ToDictionary(g => g.Key, g => g.ToList());
  296 +
  297 + // 展开数据:每个品项每个健康师一行
  298 + var exportData = new List<BillingRecordSummaryExportOutput>();
  299 + int processedCount = 0;
  300 +
  301 + foreach (var itemDetail in itemDetails)
  302 + {
  303 + var billing = billingRecords.FirstOrDefault(b => b.id == itemDetail.glkdbh);
  304 + if (billing == null) continue;
  305 +
  306 + // 获取该品项的健康师业绩列表
  307 + List<dynamic> healthTeachers;
  308 + if (healthTeacherDict.ContainsKey(itemDetail.id))
  309 + {
  310 + healthTeachers = healthTeacherDict[itemDetail.id].Cast<dynamic>().ToList();
  311 + }
  312 + else
  313 + {
  314 + healthTeachers = new List<dynamic>();
  315 + }
  316 +
  317 + // 格式化金额
  318 + string FormatAmount(decimal? amount)
  319 + {
  320 + if (amount == null || amount == 0) return "¥0.00";
  321 + return $"¥{amount.Value:F2}";
  322 + }
  323 +
  324 + // 格式化金额(非可空类型)
  325 + string FormatAmountNonNull(decimal amount)
  326 + {
  327 + if (amount == 0) return "¥0.00";
  328 + return $"¥{amount:F2}";
  329 + }
  330 +
  331 + // 格式化业绩金额(字符串类型)
  332 + string FormatPerformanceAmount(string amount)
  333 + {
  334 + if (string.IsNullOrEmpty(amount)) return "¥0.00";
  335 + if (decimal.TryParse(amount, out decimal decAmount))
  336 + {
  337 + return $"¥{decAmount:F2}";
  338 + }
  339 + return "¥0.00";
  340 + }
  341 +
  342 + // 格式化日期
  343 + string FormatDate(DateTime? date)
  344 + {
  345 + if (date == null) return "无";
  346 + return date.Value.ToString("yyyy-MM-dd");
  347 + }
  348 +
  349 + // 格式化日期时间
  350 + string FormatDateTime(DateTime? dateTime)
  351 + {
  352 + if (dateTime == null) return "无";
  353 + return dateTime.Value.ToString("yyyy/MM/dd HH:mm:ss");
  354 + }
  355 +
  356 + // 获取金三角名称
  357 + string GetGoldTriangleName(string jsjId)
  358 + {
  359 + if (string.IsNullOrEmpty(jsjId)) return "无";
  360 + return goldTriangleDict.ContainsKey(jsjId) ? goldTriangleDict[jsjId] : "无";
  361 + }
  362 +
  363 + // 如果没有健康师,也要导出一行(健康师信息为空)
  364 + if (healthTeachers == null || !healthTeachers.Any())
  365 + {
  366 + exportData.Add(new BillingRecordSummaryExportOutput
  367 + {
  368 + BillingDate = FormatDate(billing.kdrq),
  369 + StoreName = billing.djmdName ?? "无",
  370 + CustomerName = billing.kdhyc ?? "无",
  371 + CustomerPhone = billing.kdhysjh ?? "无",
  372 + GoldTriangle = GetGoldTriangleName(billing.jsj),
  373 + CustomerType = billing.gjlx?.ToString() ?? "0",
  374 + HealthCoach = "无",
  375 + PerformanceAmount = "¥0.00",
  376 + RelatedItem = itemDetail.pxmc ?? "无",
  377 + Category = itemDetail.itemCategory ?? "无",
  378 + Type = itemDetail.sourceType ?? "无",
  379 + UnitPrice = FormatAmountNonNull(itemDetail.pxjg),
  380 + ProjectNumber = itemDetail.projectNumber.ToString(),
  381 + ItemAmount = FormatAmountNonNull(itemDetail.totalPrice),
  382 + PaidAmount = FormatAmount(billing.sfyj),
  383 + DebtAmount = FormatAmount(billing.qk),
  384 + TotalAmount = FormatAmount(billing.zdyj),
  385 + PaymentMethod = billing.fkfs ?? "无",
  386 + CooperationInstitution = billing.fkyyName ?? "无",
  387 + SettlementInstitution = billing.hgjgName ?? "无",
  388 + ActivityName = billing.activityName ?? "无",
  389 + Remark = billing.bz ?? "无",
  390 + CreateTime = FormatDateTime(billing.createTime)
  391 + });
  392 + }
  393 + else
  394 + {
  395 + // 每个健康师一行
  396 + foreach (var healthTeacher in healthTeachers)
  397 + {
  398 + exportData.Add(new BillingRecordSummaryExportOutput
  399 + {
  400 + BillingDate = FormatDate(billing.kdrq),
  401 + StoreName = billing.djmdName ?? "无",
  402 + CustomerName = billing.kdhyc ?? "无",
  403 + CustomerPhone = billing.kdhysjh ?? "无",
  404 + GoldTriangle = GetGoldTriangleName(billing.jsj),
  405 + CustomerType = billing.gjlx?.ToString() ?? "0",
  406 + HealthCoach = healthTeacher.jksxm ?? "无",
  407 + PerformanceAmount = FormatPerformanceAmount(healthTeacher.jksyj),
  408 + RelatedItem = itemDetail.pxmc ?? "无",
  409 + Category = itemDetail.itemCategory ?? "无",
  410 + Type = itemDetail.sourceType ?? "无",
  411 + UnitPrice = FormatAmountNonNull(itemDetail.pxjg),
  412 + ProjectNumber = itemDetail.projectNumber.ToString(),
  413 + ItemAmount = FormatAmountNonNull(itemDetail.totalPrice),
  414 + PaidAmount = FormatAmount(billing.sfyj),
  415 + DebtAmount = FormatAmount(billing.qk),
  416 + TotalAmount = FormatAmount(billing.zdyj),
  417 + PaymentMethod = billing.fkfs ?? "无",
  418 + CooperationInstitution = billing.fkyyName ?? "无",
  419 + SettlementInstitution = billing.hgjgName ?? "无",
  420 + ActivityName = billing.activityName ?? "无",
  421 + Remark = billing.bz ?? "无",
  422 + CreateTime = FormatDateTime(billing.createTime)
  423 + });
  424 + }
  425 + }
  426 +
  427 + processedCount++;
  428 + if (processedCount % 1000 == 0)
  429 + {
  430 + _logger.LogInformation($"已处理 {processedCount}/{itemDetails.Count} 条品项明细,当前导出数据行数: {exportData.Count}");
  431 + }
  432 + }
  433 +
  434 + _logger.LogInformation($"数据展开完成,处理品项明细数量: {processedCount}, 导出数据行数: {exportData.Count}");
  435 +
  436 + if (!exportData.Any())
  437 + {
  438 + throw NCCException.Oh(ErrorCode.COM1005, "没有符合条件的数据可导出");
  439 + }
  440 +
  441 + // 配置Excel导出
  442 + List<ParamsModel> paramList =
  443 + "[{\"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<ParamsModel>();
  444 +
  445 + ExcelConfig excelconfig = new ExcelConfig();
  446 + excelconfig.FileName = "开单记录汇总_" + DateTime.Now.ToString("yyyy-MM-dd_HHmmss") + ".xls";
  447 + excelconfig.HeadFont = "微软雅黑";
  448 + excelconfig.HeadPoint = 10;
  449 + excelconfig.IsAllSizeColumn = true;
  450 + excelconfig.ColumnModel = new List<ExcelColumnModel>();
  451 +
  452 + foreach (var param in paramList)
  453 + {
  454 + excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = param.field, ExcelColumn = param.value });
  455 + }
  456 +
  457 + // 查找项目根目录并创建ExportFiles文件夹
  458 + var baseDir = AppContext.BaseDirectory;
  459 + var projectRoot = baseDir;
  460 + var dir = new DirectoryInfo(baseDir);
  461 + while (dir != null && dir.Parent != null)
  462 + {
  463 + try
  464 + {
  465 + if (dir.GetDirectories(".git").Any() || dir.GetFiles("*.sln").Any())
  466 + {
  467 + projectRoot = dir.FullName;
  468 + break;
  469 + }
  470 + }
  471 + catch
  472 + {
  473 + // 忽略访问错误,继续向上查找
  474 + }
  475 + dir = dir.Parent;
  476 + }
  477 +
  478 + if (projectRoot == baseDir)
  479 + {
  480 + dir = new DirectoryInfo(baseDir);
  481 + while (dir != null && dir.Parent != null)
  482 + {
  483 + try
  484 + {
  485 + if (dir.GetFiles("*.sln").Any())
  486 + {
  487 + projectRoot = dir.FullName;
  488 + break;
  489 + }
  490 + }
  491 + catch
  492 + {
  493 + // 忽略访问错误,继续向上查找
  494 + }
  495 + dir = dir.Parent;
  496 + }
  497 + }
  498 +
  499 + var exportFilesPath = Path.Combine(projectRoot, "ExportFiles");
  500 + if (!Directory.Exists(exportFilesPath))
  501 + {
  502 + Directory.CreateDirectory(exportFilesPath);
  503 + }
  504 +
  505 + var addPath = Path.Combine(exportFilesPath, excelconfig.FileName);
  506 + ExcelExportHelper<BillingRecordSummaryExportOutput>.Export(exportData, excelconfig, addPath);
  507 +
  508 + var fileName = _userManager.UserId + "|" + addPath + "|xls";
  509 + var downloadUrl = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC");
  510 + var output = new { name = excelconfig.FileName, url = downloadUrl };
  511 +
  512 + _logger.LogInformation($"导出完成,文件路径: {addPath}, 下载地址: {downloadUrl}");
  513 +
  514 + // 返回统一格式
  515 + return new { code = 200, msg = "导出成功", data = output };
  516 + }
  517 + catch (Exception ex)
  518 + {
  519 + _logger.LogError(ex, $"导出全部门店开单记录汇总失败,异常详情: {ex.ToString()}");
  520 + throw NCCException.Oh($"导出全部门店开单记录汇总失败:{ex.Message}");
  521 + }
  522 + }
  523 + #endregion
  524 +
99 525 #region 获取开单记录表
100 526 /// <summary>
101 527 /// 获取开单记录表
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTkDashboardService.cs
... ... @@ -552,15 +552,15 @@ namespace NCC.Extend.LqTkDashboard
552 552 COALESCE(bigOrder.BigOrderCount, 0) as BigOrderCount,
553 553 COALESCE(bigOrder.BigOrderAmount, 0) as BigOrderAmount
554 554 FROM (
555   - -- 基础员工拓客数据
  555 + -- 基础员工拓客数据(按员工统计,不按TeamName分组)
556 556 SELECT
557 557 tk.F_ExpansionUserId as EmployeeId,
558 558 COALESCE(u.F_REALNAME, '') as EmployeeName,
559 559 COALESCE(org.F_FullName, '') as DepartmentName,
560 560 COALESCE(u.F_GW, '') as Position,
561   - COALESCE(tk.F_StoreId, '') as StoreId,
562   - COALESCE(md.dm, '') as StoreName,
563   - COALESCE(tk.F_TeamName, '') as TeamName,
  561 + COALESCE(MAX(tk.F_StoreId), '') as StoreId,
  562 + COALESCE(MAX(md.dm), '') as StoreName,
  563 + COALESCE(MAX(tk.F_TeamName), '') as TeamName,
564 564 COUNT(DISTINCT tk.F_MemberId) as ExpansionCount,
565 565 COALESCE(SUM(CAST(tk.F_BuyNumber AS SIGNED)), 0) as ExpansionCardCount
566 566 FROM lq_tkjlb tk
... ... @@ -570,21 +570,34 @@ namespace NCC.Extend.LqTkDashboard
570 570 WHERE {eventFilter}
571 571 AND tk.F_ExpansionTime >= '{startTimeStr}'
572 572 AND tk.F_ExpansionTime <= '{endTimeStr}'
573   - GROUP BY tk.F_ExpansionUserId, u.F_REALNAME, org.F_FullName, u.F_GW, tk.F_StoreId, md.dm, tk.F_TeamName
  573 + GROUP BY tk.F_ExpansionUserId, u.F_REALNAME, org.F_FullName, u.F_GW
574 574 ) emp
575 575 LEFT JOIN (
576   - -- 到店人数统计
  576 + -- 到店人数统计(一个人多次到店只算一次)
  577 + -- 先找出该员工拓客的所有会员(去重),然后统计这些会员中有到店记录的会员数
  578 + -- 确保到店时间在首次拓客时间之后
577 579 SELECT
578   - tk.F_ExpansionUserId as EmployeeId,
579   - COUNT(DISTINCT tk.F_MemberId) as VisitCount
580   - FROM lq_tkjlb tk
581   - INNER JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1
582   - WHERE {eventFilter}
583   - AND tk.F_ExpansionTime >= '{startTimeStr}'
584   - AND tk.F_ExpansionTime <= '{endTimeStr}'
  580 + emp_members.EmployeeId,
  581 + COUNT(DISTINCT CASE WHEN xh.hy IS NOT NULL THEN emp_members.MemberId END) as VisitCount
  582 + FROM (
  583 + -- 先获取该员工拓客的所有会员(去重),并记录每个会员的首次拓客时间
  584 + SELECT
  585 + tk.F_ExpansionUserId as EmployeeId,
  586 + tk.F_MemberId as MemberId,
  587 + MIN(tk.F_ExpansionTime) as FirstExpansionTime
  588 + FROM lq_tkjlb tk
  589 + WHERE {eventFilter}
  590 + AND tk.F_ExpansionTime >= '{startTimeStr}'
  591 + AND tk.F_ExpansionTime <= '{endTimeStr}'
  592 + AND tk.F_MemberId IS NOT NULL
  593 + GROUP BY tk.F_ExpansionUserId, tk.F_MemberId
  594 + ) emp_members
  595 + LEFT JOIN lq_xh_hyhk xh ON emp_members.MemberId = xh.hy
  596 + AND xh.F_IsEffective = 1
  597 + AND xh.hksj >= emp_members.FirstExpansionTime
585 598 AND xh.hksj >= '{startTimeStr}'
586 599 AND xh.hksj <= '{endTimeStr}'
587   - GROUP BY tk.F_ExpansionUserId
  600 + GROUP BY emp_members.EmployeeId
588 601 ) visit ON emp.EmployeeId = visit.EmployeeId
589 602 LEFT JOIN (
590 603 -- 开单人数和金额统计
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -370,6 +370,7 @@ namespace NCC.Extend.LqXhHyhk
370 370 memberPhone = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == it.Hy).Select(w => w.Sjh),
371 371 isEffective = it.IsEffective,
372 372 signatureFile = it.SignatureFile,
  373 + remark = it.Remark,
373 374 overtimeCoefficient = it.OvertimeCoefficient,
374 375 originalSgfy = it.OriginalSgfy,
375 376 overtimeSgfy = it.OvertimeSgfy,
... ... @@ -605,6 +606,7 @@ namespace NCC.Extend.LqXhHyhk
605 606 memberPhone = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
606 607 isEffective = hyhk.IsEffective,
607 608 signatureFile = hyhk.SignatureFile,
  609 + remark = hyhk.Remark,
608 610 overtimeCoefficient = hyhk.OvertimeCoefficient,
609 611 originalSgfy = hyhk.OriginalSgfy,
610 612 overtimeSgfy = hyhk.OvertimeSgfy,
... ... @@ -769,6 +771,7 @@ namespace NCC.Extend.LqXhHyhk
769 771 memberPhone = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
770 772 isEffective = hyhk.IsEffective,
771 773 signatureFile = hyhk.SignatureFile,
  774 + remark = hyhk.Remark,
772 775 overtimeCoefficient = hyhk.OvertimeCoefficient,
773 776 originalSgfy = hyhk.OriginalSgfy,
774 777 overtimeSgfy = hyhk.OvertimeSgfy,
... ... @@ -1155,6 +1158,7 @@ namespace NCC.Extend.LqXhHyhk
1155 1158 sfykjb = it.Sfykjb,
1156 1159 hksj = it.Hksj,
1157 1160 czry = it.Czry,
  1161 + remark = it.Remark,
1158 1162 })
1159 1163 .MergeTable()
1160 1164 .OrderBy(sidx + " " + input.sort)
... ... @@ -1495,6 +1499,64 @@ namespace NCC.Extend.LqXhHyhk
1495 1499 }
1496 1500 #endregion
1497 1501  
  1502 + #region 更新耗卡备注
  1503 + /// <summary>
  1504 + /// 更新耗卡备注
  1505 + /// </summary>
  1506 + /// <remarks>
  1507 + /// 专门用于更新耗卡记录的备注信息
  1508 + ///
  1509 + /// 示例请求:
  1510 + /// PUT /api/Extend/LqXhHyhk/{id}/UpdateRemark
  1511 + ///
  1512 + /// 请求体:
  1513 + /// ```json
  1514 + /// {
  1515 + /// "remark": "备注信息(最大2000字符)"
  1516 + /// }
  1517 + /// ```
  1518 + /// </remarks>
  1519 + /// <param name="id">耗卡记录ID</param>
  1520 + /// <param name="input">备注信息</param>
  1521 + /// <returns>无返回值</returns>
  1522 + /// <response code="200">更新成功</response>
  1523 + /// <response code="400">参数错误</response>
  1524 + /// <response code="404">耗卡记录不存在</response>
  1525 + /// <response code="500">服务器错误</response>
  1526 + [HttpPut("{id}/UpdateRemark")]
  1527 + public async Task UpdateRemark(string id, [FromBody] LqXhHyhkUpdateRemarkInput input)
  1528 + {
  1529 + try
  1530 + {
  1531 + // 查询记录
  1532 + var entity = await _db.Queryable<LqXhHyhkEntity>()
  1533 + .Where(p => p.Id == id && p.IsEffective == StatusEnum.有效.GetHashCode())
  1534 + .FirstAsync();
  1535 +
  1536 + if (entity == null)
  1537 + {
  1538 + throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在或已作废");
  1539 + }
  1540 +
  1541 + // 更新备注
  1542 + entity.Remark = input.remark;
  1543 + entity.UpdateTime = DateTime.Now;
  1544 +
  1545 + // 保存更新
  1546 + await _db.Updateable(entity)
  1547 + .UpdateColumns(it => new { it.Remark, it.UpdateTime })
  1548 + .ExecuteCommandAsync();
  1549 +
  1550 + _logger.LogInformation($"更新耗卡备注成功,ID:{id}");
  1551 + }
  1552 + catch (Exception ex)
  1553 + {
  1554 + _logger.LogError(ex, "更新耗卡备注失败,ID:{Id}", id);
  1555 + throw;
  1556 + }
  1557 + }
  1558 + #endregion
  1559 +
1498 1560 #region 删除会员耗卡
1499 1561 /// <summary>
1500 1562 /// 删除会员耗卡
... ...
sql/添加lq_xh_hyhk表备注字段.sql 0 → 100644
  1 +-- 在 lq_xh_hyhk 表中添加备注字段
  2 +-- 字段名: F_Remark
  3 +-- 数据类型: VARCHAR(2000)
  4 +-- 位置: 在 F_CancelRemark 字段之后
  5 +
  6 +ALTER TABLE lq_xh_hyhk
  7 +ADD COLUMN F_Remark VARCHAR(2000) NULL COMMENT '备注' AFTER F_CancelRemark;
... ...