# Web端商户收入汇总统计接口文档

## 模块概述

本接口提供收入数据的汇总统计功能，一次性返回可提现金额、今日收益、已入账总数、未入账总数四项关键数据，方便前端首页展示。

---

## 接口信息

### 收入汇总统计

#### 接口详情

- **接口名称**: 收入汇总统计
- **接口路径**: `GET /incomeManage/getIncomeSummary`
- **请求方式**: GET
- **接口描述**: 一次性查询可提现金额、今日收益、已入账金额/数量、未入账金额/数量
- **登录验证**: ✅ 需要（通过 JWT token 自动获取店铺ID）
- **数据来源**: 复用三个现有接口的业务逻辑

---

## 业务逻辑说明

### 数据来源接口

本接口整合了以下三个app端接口的数据：

| 数据项 | app端接口 | 业务逻辑 |
|--------|----------|---------|
| 可提现金额 | `/alienStore/storeIncomeDetailsRecord/accountBalance` | 4-27天内未绑定提现的收入 - 待审核/已通过的提现金额 |
| 今日收益 | `/alienStore/storeIncomeDetailsRecord/todayIncome` | 当天00:00:00 ~ 23:59:59的收入总和 |
| 未入账 | `/alienStore/storeIncomeDetailsRecord/noYetPayment?paymentType=0` | 近3天内未绑定提现的收入 |
| 已入账 | `/alienStore/storeIncomeDetailsRecord/noYetPayment?paymentType=1` | 4-27天内的收入 |

---

### 数据计算规则

#### **1. 可提现金额**

```java
// 查询4-27天内未绑定提现记录的收入
cashOutMoney = SUM(money WHERE created_time BETWEEN (NOW() - 27天) AND (NOW() - 4天) 
                              AND cash_out_id IS NULL)

// 减去待审核和已通过的提现金额
totalCashOutAmount = SUM(money WHERE payment_status IN (1, 3) AND delete_flag = 0)

// 可提现金额
cashOutMoney = cashOutMoney - totalCashOutAmount
```

**说明**:
- ✅ 只统计4-27天前的收入（已到提现账期）
- ✅ 减去已申请但未到账的提现金额
- ✅ payment_status: 1-待审核, 3-已通过

---

#### **2. 今日收益**

```java
// 查询今天的收入总和
todayIncome = SUM(money WHERE created_time BETWEEN '今日 00:00:00' AND '今日 23:59:59')
```

**说明**:
- ✅ 统计当天所有收入
- ✅ 包含所有收入类型（优惠券、代金券、团购券等）

---

#### **3. 未入账（近3天）**

```java
// 查询近3天未绑定提现的收入
notPaidMoney = SUM(money WHERE created_time > (NOW() - 3天) 
                              AND cash_out_id IS NULL)
notPaidCount = COUNT(*)
```

**说明**:
- ✅ 统计近3天的收入
- ✅ 未绑定提现记录
- ✅ 还未到提现账期

---

#### **4. 已入账（4-27天）**

```java
// 查询4-27天前的收入
paidMoney = SUM(money WHERE created_time BETWEEN (NOW() - 27天) AND (NOW() - 4天))
paidCount = COUNT(*)
```

**说明**:
- ✅ 统计4-27天前的收入
- ✅ 已到提现账期
- ✅ 可以申请提现

---

## 请求参数

**无参数**

从 JWT token 自动获取店铺ID（`storeId`）

---

## 请求示例

```http
GET /incomeManage/getIncomeSummary
Headers:
  Authorization: Bearer YOUR_JWT_TOKEN
```

```bash
curl -X GET "http://localhost:8080/incomeManage/getIncomeSummary" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
```

---

## 响应参数

### 成功响应

```json
{
    "code": 200,
    "success": true,
    "data": {
        "balance": "3580.50",
        "cashOutMoney": "1256.80",
        "todayIncome": "128.30",
        "notPaidMoney": "385.60",
        "notPaidCount": 12,
        "notPaidDateRange": "2025-11-18 ~ 2025-11-21",
        "paidMoney": "1580.20",
        "paidCount": 45,
        "paidDateRange": "2025-10-25 ~ 2025-11-17"
    },
    "msg": "操作成功"
}
```

### 响应字段说明

| 字段名 | 类型 | 说明 |
|--------|------|------|
| code | Integer | 状态码（200-成功） |
| success | Boolean | 是否成功 |
| data | Object | 汇总数据 |
| msg | String | 提示信息 |

#### data 字段详细说明

| 字段名 | 类型 | 单位 | 说明 |
|--------|------|------|------|
| **balance** | String | 元 | 账户总余额 |
| **cashOutMoney** | String | 元 | 可提现金额（4-27天内可提现的收入） |
| **todayIncome** | String | 元 | 今日收益（当天所有收入） |
| **notPaidMoney** | String | 元 | 未入账金额（近3天的收入） |
| **notPaidCount** | Integer | 条 | 未入账记录数 |
| **notPaidDateRange** | String | - | 未入账时间范围 |
| **paidMoney** | String | 元 | 已入账金额（4-27天的收入） |
| **paidCount** | Integer | 条 | 已入账记录数 |
| **paidDateRange** | String | - | 已入账时间范围 |

---

### 失败响应

#### 1. 未登录或 token 无效

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "请先登录"
}
```

#### 2. 查询异常

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "查询失败：{异常信息}"
}
```

---

## 业务场景

### 场景 1: 首页数据展示

**业务需求**: 商户登录后，首页需要展示收入概况的关键数据

**请求**:
```http
GET /incomeManage/getIncomeSummary
Headers:
  Authorization: Bearer VALID_TOKEN
```

**响应**:
```json
{
    "code": 200,
    "success": true,
    "data": {
        "balance": "3580.50",
        "cashOutMoney": "1256.80",
        "todayIncome": "128.30",
        "notPaidMoney": "385.60",
        "notPaidCount": 12,
        "paidMoney": "1580.20",
        "paidCount": 45
    }
}
```

**前端展示**:
```
┌────────────────────────────────────────┐
│          收入概况                       │
├────────────────────────────────────────┤
│  账户余额: ¥3,580.50                   │
│  可提现金额: ¥1,256.80                 │
│  今日收益: ¥128.30                     │
│                                        │
│  未入账: ¥385.60 (12笔)                │
│  已入账: ¥1,580.20 (45笔)              │
└────────────────────────────────────────┘
```

---

### 场景 2: 数据刷新

**业务需求**: 用户下拉刷新或手动刷新收入数据

**请求**:
```http
GET /incomeManage/getIncomeSummary
Headers:
  Authorization: Bearer VALID_TOKEN
```

**业务逻辑**:
- ✅ 重新计算所有统计数据
- ✅ 返回最新的收入概况
- ✅ 前端更新展示

---

### 场景 3: 定时刷新

**业务需求**: 前端每隔一定时间自动刷新数据

**实现方式**:
```javascript
setInterval(() => {
    fetchIncomeSummary();
}, 60000);  // 每分钟刷新一次
```

---

## 数据关系说明

### 金额之间的关系

```
账户余额 (balance)
    = 历史所有收入 - 已提现金额

可提现金额 (cashOutMoney)
    = 4-27天内未绑定提现的收入 
    - 待审核的提现金额 
    - 已通过的提现金额

今日收益 (todayIncome)
    = 今天所有收入

未入账 (notPaidMoney)
    = 近3天未绑定提现的收入
    （还未到提现账期）

已入账 (paidMoney)
    = 4-27天的收入
    （已到提现账期，可以提现）
```

---

### 时间范围关系

```
时间轴: ←────────────────────────────────→
       -27天  -4天  -3天  今天

已入账:  ├─────┤
         4-27天

未入账:          ├──┤
                3天内

今日收益:             │
                    今天
```

---

## 前端集成示例

### Vue.js 示例

```javascript
export default {
    data() {
        return {
            summary: {
                balance: '0.00',
                cashOutMoney: '0.00',
                todayIncome: '0.00',
                notPaidMoney: '0.00',
                notPaidCount: 0,
                paidMoney: '0.00',
                paidCount: 0,
                notPaidDateRange: '',
                paidDateRange: ''
            },
            loading: false
        };
    },
    methods: {
        async fetchIncomeSummary() {
            this.loading = true;
            try {
                const response = await this.$axios.get('/incomeManage/getIncomeSummary');
                
                if (response.data.success) {
                    this.summary = response.data.data;
                    this.$message.success('数据加载成功');
                } else {
                    this.$message.error(response.data.msg);
                }
            } catch (error) {
                console.error('查询失败:', error);
                this.$message.error('数据加载失败，请稍后重试');
            } finally {
                this.loading = false;
            }
        },
        
        // 格式化金额显示
        formatMoney(amount) {
            return '¥' + parseFloat(amount).toLocaleString('zh-CN', {
                minimumFractionDigits: 2,
                maximumFractionDigits: 2
            });
        },
        
        // 下拉刷新
        onRefresh() {
            this.fetchIncomeSummary();
        }
    },
    mounted() {
        this.fetchIncomeSummary();
        
        // 每分钟自动刷新
        this.timer = setInterval(() => {
            this.fetchIncomeSummary();
        }, 60000);
    },
    beforeDestroy() {
        if (this.timer) {
            clearInterval(this.timer);
        }
    }
}
```

---

### React 示例

```javascript
import { useState, useEffect } from 'react';
import axios from 'axios';
import { Card, Statistic, Row, Col, Spin } from 'antd';
import { WalletOutlined, DollarOutlined, RiseOutlined } from '@ant-design/icons';

function IncomeSummary() {
    const [summary, setSummary] = useState({
        balance: '0.00',
        cashOutMoney: '0.00',
        todayIncome: '0.00',
        notPaidMoney: '0.00',
        notPaidCount: 0,
        paidMoney: '0.00',
        paidCount: 0
    });
    const [loading, setLoading] = useState(false);
    
    const fetchData = async () => {
        setLoading(true);
        try {
            const response = await axios.get('/incomeManage/getIncomeSummary');
            
            if (response.data.success) {
                setSummary(response.data.data);
            }
        } catch (error) {
            console.error('查询失败:', error);
        } finally {
            setLoading(false);
        }
    };
    
    useEffect(() => {
        fetchData();
        
        // 每分钟自动刷新
        const timer = setInterval(fetchData, 60000);
        
        return () => clearInterval(timer);
    }, []);
    
    return (
        <Spin spinning={loading}>
            <Row gutter={16}>
                <Col span={6}>
                    <Card>
                        <Statistic
                            title="账户余额"
                            value={summary.balance}
                            prefix="¥"
                            precision={2}
                            valueStyle={{ color: '#3f8600' }}
                            suffix={<WalletOutlined />}
                        />
                    </Card>
                </Col>
                
                <Col span={6}>
                    <Card>
                        <Statistic
                            title="可提现金额"
                            value={summary.cashOutMoney}
                            prefix="¥"
                            precision={2}
                            valueStyle={{ color: '#cf1322' }}
                            suffix={<DollarOutlined />}
                        />
                    </Card>
                </Col>
                
                <Col span={6}>
                    <Card>
                        <Statistic
                            title="今日收益"
                            value={summary.todayIncome}
                            prefix="¥"
                            precision={2}
                            valueStyle={{ color: '#1890ff' }}
                            suffix={<RiseOutlined />}
                        />
                    </Card>
                </Col>
                
                <Col span={6}>
                    <Card>
                        <Statistic
                            title="未入账"
                            value={summary.notPaidMoney}
                            prefix="¥"
                            precision={2}
                            suffix={`(${summary.notPaidCount}笔)`}
                        />
                    </Card>
                </Col>
            </Row>
            
            <Row gutter={16} style={{ marginTop: 16 }}>
                <Col span={12}>
                    <Card>
                        <Statistic
                            title="已入账"
                            value={summary.paidMoney}
                            prefix="¥"
                            precision={2}
                            suffix={`(${summary.paidCount}笔)`}
                        />
                        <p style={{ marginTop: 8, color: '#999' }}>
                            {summary.paidDateRange}
                        </p>
                    </Card>
                </Col>
            </Row>
        </Spin>
    );
}
```

---

## 测试用例

### 测试场景1: 正常查询

**前置条件**:
- 用户已登录
- 店铺ID=103
- 有收入记录

**执行步骤**:
1. 发送请求: `GET /incomeManage/getIncomeSummary`

**预期结果**:
- 返回成功
- `balance` > 0
- `cashOutMoney` >= 0
- `todayIncome` >= 0
- `notPaidMoney` >= 0, `notPaidCount` >= 0
- `paidMoney` >= 0, `paidCount` >= 0
- 包含时间范围字段

---

### 测试场景2: 无收入记录

**前置条件**:
- 用户已登录
- 新店铺，无任何收入

**执行步骤**:
1. 发送请求: `GET /incomeManage/getIncomeSummary`

**预期结果**:
- 返回成功
- 所有金额字段 = "0.00"
- 所有数量字段 = 0

---

### 测试场景3: 只有今日收入

**前置条件**:
- 用户已登录
- 只有今天有收入记录

**执行步骤**:
1. 发送请求: `GET /incomeManage/getIncomeSummary`

**预期结果**:
- `todayIncome` > 0
- `notPaidMoney` > 0（今天的收入也在3天内）
- `paidMoney` = 0（没有4-27天前的收入）

---

## 常见问题

### Q1: 为什么可提现金额比已入账金额少？

**答案**: 
- 可提现金额 = 已入账金额 - 待审核的提现 - 已通过的提现
- 如果有提现申请在审核中或已通过，会从可提现金额中扣除

---

### Q2: 今日收益会计入未入账吗？

**答案**: 会的。
- 今日收益统计当天所有收入
- 未入账统计近3天的收入（包括今天）
- 所以今日收益会包含在未入账中

---

### Q3: 账户余额和可提现金额有什么区别？

**答案**:
- **账户余额**: 所有历史收入 - 已提现金额（总余额）
- **可提现金额**: 4-27天内可以立即提现的金额（扣除审核中的提现）

---

### Q4: 已入账的收入一定可以提现吗？

**答案**: 不一定。
- 已入账 = 4-27天前的收入（已到账期）
- 可提现金额 = 已入账 - 审核中的提现 - 已通过的提现
- 如果已有提现申请，可提现金额会相应减少

---

### Q5: 时间范围是如何计算的？

**答案**:
- **未入账**: 当前时间 - 3天 ~ 当前时间
- **已入账**: 当前时间 - 27天 ~ 当前时间 - 4天
- 时间范围会随着查询时间动态变化

---

## 注意事项

### 1. 数据实时性

- ⚠️ 数据基于查询时刻实时计算
- ⚠️ 建议前端设置定时刷新（如每分钟）
- ⚠️ 或提供手动刷新按钮

### 2. 金额单位

- ⚠️ 数据库存储单位：**分**
- ⚠️ 接口返回单位：**元**（保留2位小数）
- ⚠️ 转换公式：元 = 分 / 100

### 3. 时间范围

- ⚠️ 时间范围是动态的，基于当前时间计算
- ⚠️ 未入账：3天内
- ⚠️ 已入账：4-27天
- ⚠️ 可提现：4-27天内未绑定提现的

### 4. 性能考虑

- ⚠️ 涉及多次数据库查询
- ⚠️ 建议适当缓存（如Redis，5分钟过期）
- ⚠️ 避免频繁调用

---

## 性能优化建议

### 1. 缓存方案

```java
@Cacheable(value = "incomeSummary", key = "#storeId", unless = "#result == null")
public Map<String, Object> getIncomeSummary(Integer storeId) {
    // ... 查询逻辑
}
```

**配置**:
```yaml
spring:
  cache:
    type: redis
    redis:
      time-to-live: 300000  # 5分钟过期
```

---

### 2. 异步查询

```java
// 使用CompletableFuture并行查询
CompletableFuture<String> balanceFuture = CompletableFuture.supplyAsync(() -> getBalance(storeId));
CompletableFuture<String> todayIncomeFuture = CompletableFuture.supplyAsync(() -> getTodayIncome(storeId));
CompletableFuture<Map> notPaidFuture = CompletableFuture.supplyAsync(() -> getNotPaidData(storeId));
CompletableFuture<Map> paidFuture = CompletableFuture.supplyAsync(() -> getPaidData(storeId));

// 等待所有查询完成
CompletableFuture.allOf(balanceFuture, todayIncomeFuture, notPaidFuture, paidFuture).join();
```

---

## 更新日志

### 2025-11-21 (v1.0)

**新增接口**:
- ✅ `GET /incomeManage/getIncomeSummary` - 收入汇总统计

**核心功能**:
- ✅ 一次性返回4项关键数据
- ✅ 账户余额
- ✅ 可提现金额
- ✅ 今日收益
- ✅ 已入账/未入账金额和数量

**数据来源**:
- 复用三个现有接口的业务逻辑
- `/accountBalance` - 可提现金额
- `/todayIncome` - 今日收益
- `/noYetPayment` - 已入账/未入账

**涉及文件**:
- `IncomeManageController.java` - 新增 `getIncomeSummary` 接口
- `IncomeManageService.java` - 新增方法定义
- `IncomeManageServiceImpl.java` - 新增方法实现

**代码质量**:
- ✅ Linter检查：无错误
- ✅ 日志记录：详细
- ✅ 异常处理：完善
- ✅ 代码注释：完整

**开发人员**: ssk

---

**文档版本**: v1.0  
**最后更新**: 2025-11-21  
**维护人员**: ssk


