# Web端商户账期查询接口文档

## 模块概述

本模块提供收入账期查询功能,支持按时间范围、收入类型、账期类型查询店铺的收入明细数据。

---

## 接口信息

### 账期查询

#### 接口详情

- **接口名称**: 账期查询
- **接口路径**: `GET /incomeManage/getPaymentCycle`
- **请求方式**: GET
- **接口描述**: 查询门店的收入账期数据,支持未到账期和已到账期的查询,可按收入类型筛选
- **登录验证**: ✅ 需要（使用 `@LoginRequired` 注解）

---

## 请求参数

| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| storeId | Integer | 是 | 门店ID | 103 |
| incomeType | Integer | 否 | 收入类型<br/>0:主页（优惠券+团购券）<br/>1:优惠券<br/>2:代金券<br/>3:套餐<br/>4:联名卡 | 0 |
| paymentType | Integer | 否 | 账期类型<br/>0:未到账期<br/>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<StoreIncomeDetailsRecordVo>       │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  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<StoreIncomeDetailsRecordVo> 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

