# Web端商户账期查询接口文档 ## 模块概述 本模块提供收入账期查询功能,支持按时间范围、收入类型、账期类型查询店铺的收入明细数据。 --- ## 接口信息 ### 账期查询 #### 接口详情 - **接口名称**: 账期查询 - **接口路径**: `GET /incomeManage/getPaymentCycle` - **请求方式**: GET - **接口描述**: 查询门店的收入账期数据,支持未到账期和已到账期的查询,可按收入类型筛选 - **登录验证**: ✅ 需要(使用 `@LoginRequired` 注解) --- ## 请求参数 | 参数名 | 类型 | 必填 | 说明 | 示例值 | |--------|------|------|------|--------| | storeId | Integer | 是 | 门店ID | 103 | | incomeType | Integer | 否 | 收入类型
0:主页(优惠券+团购券)
1:优惠券
2:代金券
3:套餐
4:联名卡 | 0 | | paymentType | Integer | 否 | 账期类型
0:未到账期
1:已到账期 | 0 | | startTime | String | 是 | 开始时间(格式:yyyy-MM-dd) | 2025-11-09 | | endTime | String | 是 | 结束时间(格式:yyyy-MM-dd) | 2025-11-11 | | page | Integer | 否 | 页码(默认1) | 1 | | size | Integer | 否 | 每页条数(默认10) | 10 | --- ## 请求示例 ```http GET /incomeManage/getPaymentCycle?storeId=103&incomeType=0&paymentType=0&startTime=2025-11-09&endTime=2025-11-11&page=1&size=10 ``` ```bash curl "http://localhost:8080/incomeManage/getPaymentCycle?storeId=103&incomeType=0&paymentType=0&startTime=2025-11-09&endTime=2025-11-11&page=1&size=10" \ -H "Authorization: Bearer YOUR_TOKEN" ``` --- ## 响应参数 ### 成功响应 ```json { "code": 200, "success": true, "data": { "date": "2025-11-11 ~ 2025-11-14", "data": { "records": [ { "id": 12345, "storeId": 103, "userOrderId": 67890, "incomeType": 1, "businessId": 111, "money": 9600, "moneyStr": "96.00", "commission": 400, "cashOutId": null, "createdTime": "2025-11-11T10:30:00", "date": "2025-11-11", "orderTime": "2025-11-11T10:00:00", "checkTime": "2025-11-11T10:30:00", "incomeTime": "2025-11-15T10:30:00", "couponName": "100元代金券" } ], "total": 50, "size": 10, "current": 1, "pages": 5 }, "money": "4800.00" }, "msg": "操作成功" } ``` ### 响应字段说明 | 字段名 | 类型 | 说明 | |--------|------|------| | data.date | String | 账期时间范围 | | data.data | Object | 分页数据对象 | | data.data.records | Array | 收入明细记录列表 | | data.data.records[].id | Integer | 收入记录ID | | data.data.records[].storeId | Integer | 门店ID | | data.data.records[].userOrderId | Integer | 用户订单ID | | data.data.records[].incomeType | Integer | 收入类型 1-优惠券 2-团购券 | | data.data.records[].businessId | Integer | 业务ID(券ID) | | data.data.records[].money | Integer | 收入金额(分) | | data.data.records[].moneyStr | String | 收入金额(元,格式化后) | | data.data.records[].commission | Integer | 抽成金额(分) | | data.data.records[].cashOutId | Integer | 提现记录ID(null表示未提现) | | data.data.records[].createdTime | String | 创建时间 | | data.data.records[].date | String | 日期(格式化) | | data.data.records[].orderTime | String | 订单时间 | | data.data.records[].checkTime | String | 核销时间 | | data.data.records[].incomeTime | String | 到账时间(创建时间+4天) | | data.data.records[].couponName | String | 券名称 | | data.data.total | Integer | 总记录数 | | data.data.size | Integer | 每页条数 | | data.data.current | Integer | 当前页码 | | data.data.pages | Integer | 总页数 | | data.money | String | 汇总金额(元) | ### 失败响应 #### 1. 门店不存在 ```json { "code": 500, "success": false, "data": null, "msg": "查询失败:门店不存在" } ``` #### 2. 未登录或登录过期 ```json { "code": 500, "success": false, "data": null, "msg": "请先登录" } ``` #### 3. 参数错误 ```json { "code": 500, "success": false, "data": null, "msg": "查询失败:日期格式错误" } ``` --- ## 业务逻辑说明 ### 账期类型说明 #### 未到账期(paymentType=0) - **定义**: 距离核销时间不足3天,资金尚未到达可提现账期 - **查询条件**: - `cash_out_id` 为空(未绑定提现记录) - 创建时间 > 当前时间 - 3天 - **账期时间范围**: 当前时间-3天 ~ 当前时间 #### 已到账期(paymentType=1) - **定义**: 距离核销时间超过4天但不超过27天,资金已到达可提现账期 - **查询条件**: - `cash_out_id` 不为空(已绑定提现记录) - 创建时间在 [当前时间-27天, 当前时间-4天] 范围内 - **账期时间范围**: 当前时间-27天 ~ 当前时间-4天 ### 收入类型说明 | incomeType | 名称 | 说明 | |------------|------|------| | 0 | 主页 | 包含优惠券(1)和团购券(2) | | 1 | 优惠券 | 仅优惠券收入 | | 2 | 代金券 | 仅代金券收入 | | 3 | 套餐 | 仅套餐收入 | | 4 | 联名卡 | 仅联名卡收入 | ### 查询流程 ``` ┌─────────────────────────────────────────────┐ │ 1. 接收查询参数 │ │ storeId, incomeType, paymentType, │ │ startTime, endTime, page, size │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 2. 构建查询条件(QueryWrapper) │ │ - 根据 paymentType 设置账期条件 │ │ - 根据 storeId 筛选门店 │ │ - 根据 startTime/endTime 设置时间范围 │ │ - 根据 incomeType 设置收入类型 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 3. 查询收入明细记录(Mapper) │ │ 调用 selectRecordList(wrapper) │ │ 返回 List │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 4. 查询门店信息 │ │ 获取抽成比例 commissionRate │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 5. 处理列表数据 │ │ - 金额格式化(分→元,保留2位小数) │ │ - 日期格式化(yyyy-MM-dd) │ │ - 设置抽成比例 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 6. 手动分页 │ │ 使用 ListToPage.setPage(list, page, size) │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 7. 计算汇总金额 │ │ stream().mapToInt(money).sum() │ │ 转换为元并格式化 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 8. 返回结果 │ │ JSONObject包含: date, data, money │ └─────────────────────────────────────────────┘ ``` --- ## 数据库查询 ### 涉及的表 #### 1. store_income_details_record(收入明细表) **查询SQL**(简化版): ```sql SELECT sidr.id, sidr.money, sidr.commission, sidr.created_time checkTime, luo.buy_time orderTime, ADDDATE(sidr.created_time, 4) incomeTime, sidr.income_type, tc.name couponName FROM store_income_details_record sidr LEFT JOIN order_coupon_middle ocm ON ocm.id = sidr.user_order_id LEFT JOIN life_user_order luo ON luo.id = ocm.order_id LEFT JOIN totalCoupon tc ON tc.id = sidr.business_id WHERE sidr.store_id = ? AND sidr.created_time BETWEEN ? AND ? -- 根据 paymentType 添加条件 AND (sidr.cash_out_id IS NULL OR sidr.cash_out_id IS NOT NULL) -- 根据 incomeType 添加条件 AND sidr.income_type IN (1, 2) OR sidr.income_type = ? ORDER BY sidr.created_time DESC ``` #### 2. store_info(门店信息表) ```sql SELECT * FROM store_info WHERE id = ? ``` --- ## 金额计算规则 ### 金额单位转换 | 数据库存储(分) | 显示(元) | 转换公式 | |------------------|-----------|----------| | 9600 | 96.00 | money ÷ 100 | | 400 | 4.00 | commission ÷ 100 | ### 汇总金额计算 ```java // 1. 累加所有记录的金额(分) int totalMoney = list.stream() .mapToInt(StoreIncomeDetailsRecord::getMoney) .sum(); // 2. 转换为元(保留2位小数) String moneyStr = new BigDecimal(totalMoney) .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP) .toString(); ``` **示例**: ``` 记录1: 9600分 记录2: 8800分 记录3: 10200分 汇总: (9600 + 8800 + 10200) ÷ 100 = 286.00元 ``` --- ## 分页说明 ### 手动分页实现 使用 `ListToPage.setPage()` 工具类进行内存分页: ```java IPage pageResult = ListToPage.setPage(list, page, size); ``` ### 分页计算逻辑 ``` 总记录数: 50 每页条数: 10 总页数: Math.ceil(50 ÷ 10) = 5 第1页: records[0-9] 第2页: records[10-19] 第3页: records[20-29] ... ``` ### 分页响应结构 ```json { "records": [...], // 当前页数据 "total": 50, // 总记录数 "size": 10, // 每页条数 "current": 1, // 当前页码 "pages": 5 // 总页数 } ``` --- ## 登录验证 ### 验证机制 接口使用 `@LoginRequired` 注解进行登录验证,通过AOP切面实现: 1. **Token验证**: 从请求中获取JWT Token并解析用户信息 2. **用户类型验证**: 确认用户类型为 `storePlatform` 3. **Redis Token验证**: 检查Token是否在Redis中存在(未过期/未注销) ### 验证失败场景 | 场景 | 返回消息 | |------|----------| | Token无效或不存在 | "请先登录" | | 用户类型不正确 | "请先登录" | | Token已过期或已注销 | "请先登录" | --- ## 异常处理 ### 异常场景 | 异常情况 | HTTP状态码 | 返回code | 返回msg | |----------|-----------|----------|---------| | 门店不存在 | 200 | 500 | "查询失败:门店不存在" | | 日期格式错误 | 200 | 500 | "查询失败:日期格式错误" | | 未登录 | 200 | 500 | "请先登录" | | 系统异常 | 200 | 500 | "查询失败:{异常信息}" | ### 日志记录 所有请求和异常都会记录详细日志: ```java // 请求日志 log.info("IncomeManageController.getPaymentCycle?storeId={}, incomeType={}, ...", ...); // 查询日志 log.info("IncomeManageServiceImpl.getPaymentCycle - 查询到收入记录数: {}", list.size()); // 异常日志 log.error("IncomeManageController.getPaymentCycle ERROR: {}", e.getMessage(), e); ``` --- ## 业务规则 ### 账期时间计算规则 #### 1. 未到账期时间范围 ``` 当前时间: 2025-11-14 未到账期范围: 2025-11-11 ~ 2025-11-14 计算公式: [当前时间-3天, 当前时间] ``` #### 2. 已到账期时间范围 ``` 当前时间: 2025-11-14 已到账期范围: 2025-10-18 ~ 2025-11-10 计算公式: [当前时间-27天, 当前时间-4天] ``` #### 3. 到账时间计算 ``` 核销时间: 2025-11-11 10:30:00 到账时间: 2025-11-15 10:30:00 计算公式: 核销时间 + 4天 ``` ### 收入类型筛选规则 ```java if (incomeType == 0) { // 主页: 查询优惠券(1) + 团购券(2) wrapper.in("income_type", 1, 2); } else if (incomeType != null) { // 指定类型: 精确匹配 wrapper.eq("income_type", incomeType); } // incomeType为null: 不添加类型条件,查询所有 ``` --- ## 测试用例 ### 测试场景1: 查询未到账期 **请求**: ```http GET /incomeManage/getPaymentCycle?storeId=103&paymentType=0&startTime=2025-11-09&endTime=2025-11-14&page=1&size=10 ``` **预期结果**: - 返回未绑定提现记录的收入明细 - date字段显示未到账期时间范围 - 所有记录的cashOutId为null ### 测试场景2: 查询已到账期 **请求**: ```http GET /incomeManage/getPaymentCycle?storeId=103&paymentType=1&startTime=2025-10-18&endTime=2025-11-10&page=1&size=10 ``` **预期结果**: - 返回已绑定提现记录的收入明细 - date字段显示已到账期时间范围 - 所有记录的cashOutId不为null ### 测试场景3: 查询主页收入 **请求**: ```http GET /incomeManage/getPaymentCycle?storeId=103&incomeType=0&startTime=2025-11-09&endTime=2025-11-14&page=1&size=10 ``` **预期结果**: - 返回优惠券(1)和团购券(2)的收入记录 - 不包含其他类型的收入 ### 测试场景4: 分页测试 **请求**: ```http GET /incomeManage/getPaymentCycle?storeId=103&startTime=2025-11-01&endTime=2025-11-14&page=2&size=5 ``` **预期结果**: - 返回第2页数据(records[5-9]) - total字段为总记录数 - pages字段为总页数 ### 测试场景5: 门店不存在 **请求**: ```http GET /incomeManage/getPaymentCycle?storeId=999999&startTime=2025-11-09&endTime=2025-11-14&page=1&size=10 ``` **预期结果**: - 返回失败响应 - msg为"查询失败:门店不存在" --- ## 迁移说明 ### 原接口(app端) - **服务**: `alien-store` - **路径**: `/alienStore/storeIncomeDetailsRecord/noYetPayment` - **Controller**: `StoreIncomeDetailsRecordController` - **Service**: `StoreIncomeDetailsRecordService.noYetPayment()` ### 新接口(web端) - **服务**: `alien-store-platform` - **路径**: `/incomeManage/getPaymentCycle` - **Controller**: `IncomeManageController` - **Service**: `IncomeManageService.getPaymentCycle()` ### 差异说明 | 项目 | app端 | web端 | 说明 | |------|-------|-------|------| | 接口路径 | `/noYetPayment` | `/getPaymentCycle` | 更符合web端命名规范 | | 登录验证 | 无 | ✅ 有(@LoginRequired) | 增加登录验证 | | 返回类型 | JSONObject | JSONObject | 保持一致 | | 业务逻辑 | ✅ | ✅ | 完全复用 | | 日志记录 | 基础 | 详细 | 增强日志记录 | ### 复用的核心组件 1. **Mapper**: `StoreIncomeDetailsRecordMapper.selectRecordList()` 2. **Entity**: `StoreIncomeDetailsRecord`, `StoreIncomeDetailsRecordVo` 3. **Enum**: `CouponTypeEnum` 4. **Util**: `ListToPage`, `DateUtils` --- ## 性能优化建议 ### 1. 数据库索引 确保以下字段有索引: ```sql -- store_income_details_record表 ALTER TABLE store_income_details_record ADD INDEX idx_store_created (store_id, created_time); ALTER TABLE store_income_details_record ADD INDEX idx_cashout_created (cash_out_id, created_time); ALTER TABLE store_income_details_record ADD INDEX idx_income_type (income_type); ``` ### 2. 查询优化 - 使用时间范围索引加速查询 - 限制查询时间跨度(建议≤3个月) - 对大数据量结果进行分页处理 ### 3. 缓存策略 对于频繁查询的数据,可以考虑: - Redis缓存热点店铺的账期数据 - 设置合理的过期时间(如5分钟) - 在数据变更时清除缓存 --- ## 注意事项 ### 1. 日期格式 - ✅ 使用 `yyyy-MM-dd` 格式(如:2025-11-09) - ❌ 不支持其他格式(如:2025/11/09, 20251109) ### 2. 时间范围 - 建议查询时间跨度不超过3个月 - startTime 必须 ≤ endTime - 超大时间范围可能导致查询超时 ### 3. 金额精度 - 数据库存储单位:分(整数) - 前端显示单位:元(保留2位小数) - 使用 BigDecimal 进行金额计算 ### 4. 分页限制 - 单页最大条数建议不超过100 - 使用内存分页,大数据量时注意性能 ### 5. 登录验证 - 所有请求必须携带有效Token - Token过期需要重新登录 - 用户类型必须为 `storePlatform` --- ## 更新日志 ### 2025-11-14 **新增接口**: - ✅ `GET /incomeManage/getPaymentCycle` - 账期查询 **核心功能**: - ✅ 支持未到账期/已到账期查询 - ✅ 支持按收入类型筛选(主页、优惠券、代金券等) - ✅ 支持自定义时间范围查询 - ✅ 金额自动格式化(分→元) - ✅ 手动分页支持 - ✅ 汇总金额计算 - ✅ 登录验证(@LoginRequired) - ✅ 详细日志记录 - ✅ 完善异常处理 **涉及文件**: - `IncomeManageController.java` - 新建 - `IncomeManageService.java` - 新建 - `IncomeManageServiceImpl.java` - 新建 **代码质量**: - ✅ Linter检查:无错误 - ✅ 空指针检查:已添加 - ✅ 日志记录:详细 - ✅ 异常处理:完善 **开发人员**: ssk --- **文档版本**: v1.0 **最后更新**: 2025-11-14 **维护人员**: ssk