# 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