# 团购收益查询接口

## 接口信息

- **接口名称**: 团购收益查询
- **接口路径**: `/incomeManage/getGroupIncome`
- **请求方式**: `GET`
- **接口描述**: 查询指定日期内的团购收益详情，包括收益金额、手续费、券数量、退款金额等统计信息，以及分页的收入明细记录列表

---

## 请求参数

### Query参数

| 参数名 | 类型 | 必填 | 描述 | 示例 |
|--------|------|------|------|------|
| date | String | 是 | 查询日期（格式：yyyy-MM-dd） | 2025-11-11 |
| incomeType | Integer | 否 | 收入类型<br/>0: 主页（优惠券+团购券）<br/>1: 优惠券<br/>2: 代金券<br/>3: 套餐<br/>4: 联名卡<br/>不传则查询所有类型 | 0 |
| page | Integer | 否 | 页码，默认1 | 1 |
| size | Integer | 否 | 每页条数，默认10 | 10 |

### Headers

| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| token | String | 是 | 用户登录凭证（JWT Token） |

### 说明

- **门店ID**：通过`LoginUserUtil`从当前登录用户token中自动获取，无需前端传递
- **收入类型特殊处理**：前端展示逻辑中，当`incomeType=0`时，会同时查询优惠券（code=1）和团购券（code=2）
- **账期过滤**：仅查询未绑定提现记录的收入（`cashOutId`为空）

---

## 响应数据

### 响应格式

```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "storeId": 103,
    "storeName": "测试门店",
    "date": "2025-11-11",
    "commissionRate": 5,
    "incomeMoney": "100.00",
    "commissionStr": "5.00",
    "noYetPaymentMoney": "95.00",
    "couponCount": 10,
    "refundMoney": 2.50,
    "incomeDetailsRecordVoList": [
      {
        "id": 1001,
        "storeId": 103,
        "orderId": "ORDER123456",
        "money": 1000,
        "moneyStr": "10.00",
        "commission": 50,
        "incomeType": 1,
        "refundType": "false",
        "checkTime": "2025-11-11 10:30:00",
        "createdTime": "2025-11-11 10:30:00"
      },
      {
        "id": 1002,
        "storeId": 103,
        "orderId": "ORDER123457",
        "money": 2000,
        "moneyStr": "20.00",
        "commission": 100,
        "incomeType": 2,
        "refundType": "false",
        "checkTime": "2025-11-11 14:20:00",
        "createdTime": "2025-11-11 14:20:00"
      }
    ]
  }
}
```

### 响应字段说明

#### 主要字段

| 字段名 | 类型 | 描述 |
|--------|------|------|
| storeId | Integer | 门店ID |
| storeName | String | 门店名称 |
| date | String | 查询日期 |
| commissionRate | Integer | 佣金率（百分比） |
| incomeMoney | String | 售价总额（收入+手续费，单位：元） |
| commissionStr | String | 手续费总额（单位：元） |
| noYetPaymentMoney | String | 实际收益金额（售价-手续费，单位：元） |
| couponCount | Integer | 券数量（正常订单数 - 退款订单数） |
| refundMoney | BigDecimal | 退款金额（单位：元） |
| incomeDetailsRecordVoList | Array | 收入明细记录列表（分页后的数据） |

#### incomeDetailsRecordVoList数组元素字段

| 字段名 | 类型 | 描述 |
|--------|------|------|
| id | Long | 收入明细记录ID |
| storeId | Integer | 门店ID |
| orderId | String | 订单ID |
| money | Integer | 收入金额（单位：分） |
| moneyStr | String | 收入金额（单位：元，格式化后） |
| commission | Integer | 手续费（单位：分） |
| incomeType | Integer | 收入类型（1:优惠券, 2:代金券, 3:套餐, 4:联名卡） |
| refundType | String | 是否退款（"true":已退款, "false":未退款） |
| checkTime | String | 核销时间 |
| createdTime | String | 创建时间 |

### 响应说明

1. **金额单位转换**
   - 数据库存储单位：分（Integer）
   - 返回给前端单位：元（String，保留2位小数）

2. **收益计算逻辑**
   - **售价总额（incomeMoney）** = 收入（money） + 手续费（commission）
   - **手续费总额（commissionStr）** = 所有记录的commission之和
   - **实际收益（noYetPaymentMoney）** = 收入（money）之和（存入时已减去手续费）

3. **退款处理**
   - 根据`refundType`字段分组统计
   - `refundType="false"`: 正常订单，计入明细列表
   - `refundType="true"`: 退款订单，只统计退款金额

4. **券数量计算**
   - 券数量 = 正常订单数量 - 退款订单数量

5. **分页说明**
   - 明细列表（`incomeDetailsRecordVoList`）按照分页参数返回
   - 统计数据（总金额、券数量、退款金额）基于所有符合条件的记录计算

---

## 业务逻辑

### 查询流程

```
1. 从token中获取当前登录用户的storeId
   ↓
2. 构建查询条件
   - 时间范围：指定日期的00:00:00 ~ 23:59:59
   - 账期过滤：cashOutId为空（未绑定提现记录）
   - 门店过滤：storeId匹配
   - 收入类型：根据incomeType参数筛选
   ↓
3. 查询收入明细记录（基础统计）
   - 计算总收入（allIncome）
   - 计算总手续费（commission）
   ↓
4. 查询店铺信息
   - 获取店铺名称、佣金率
   ↓
5. 查询明细记录列表（含退款标识）
   - 通过自定义Mapper查询（getIncomeList）
   - 包含退款标识字段
   ↓
6. 按退款类型分组
   - refundType="false": 正常订单
   - refundType="true": 退款订单
   ↓
7. 计算统计信息
   - 售价总额 = 收入 + 手续费
   - 券数量 = 正常订单数 - 退款订单数
   - 退款金额 = 退款订单金额之和
   ↓
8. 手动分页（ListToPage.setPage）
   - 对明细列表进行内存分页
   ↓
9. 返回结果
```

### 收入类型处理

```java
// incomeType参数说明
if (incomeType == null) {
    // 不传：查询所有类型
    // 不添加incomeType筛选条件
} else if (incomeType == 0) {
    // 0: 主页（优惠券+团购券）
    // 查询incomeType IN (1, 2)
    wrapper.in(StoreIncomeDetailsRecord::getIncomeType, 
        CouponTypeEnum.COUPON.getCode(),      // 1
        CouponTypeEnum.GROUP_BUY.getCode());  // 2
} else {
    // 1/2/3/4: 具体类型
    // 查询incomeType = 指定值
    wrapper.eq(StoreIncomeDetailsRecord::getIncomeType, incomeType);
}
```

### 账期过滤规则

- 仅查询未绑定提现记录的收入（`cashOutId IS NULL`）
- 这部分收入代表"可提现"但尚未提现的金额
- 已提现的收入（`cashOutId`不为空）不在此接口查询范围

---

## 错误响应

### 门店不存在

```json
{
  "code": 500,
  "msg": "查询失败：门店不存在",
  "data": null
}
```

### 未登录

```json
{
  "code": 500,
  "msg": "查询失败：用户未登录",
  "data": null
}
```

### 日期格式错误

```json
{
  "code": 500,
  "msg": "查询失败：日期格式错误",
  "data": null
}
```

---

## 调用示例

### 请求示例

```bash
# 查询指定日期的主页收益（优惠券+团购券）
GET /incomeManage/getGroupIncome?date=2025-11-11&incomeType=0&page=1&size=10
Headers:
  token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# 查询指定日期的优惠券收益
GET /incomeManage/getGroupIncome?date=2025-11-11&incomeType=1&page=1&size=10
Headers:
  token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# 查询指定日期的所有类型收益
GET /incomeManage/getGroupIncome?date=2025-11-11&page=1&size=20
Headers:
  token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

---

## 代码实现

### Controller

```java
@ApiOperation("团购收益查询")
@GetMapping("/getGroupIncome")
public R<?> getGroupIncome(
        @RequestParam("date") String date,
        @RequestParam(value = "incomeType", required = false) Integer incomeType,
        @RequestParam(value = "page", defaultValue = "1") Integer page,
        @RequestParam(value = "size", defaultValue = "10") Integer size) {
    log.info("IncomeManageController.getGroupIncome?date={}, incomeType={}, page={}, size={}",
            date, incomeType, page, size);
    try {
        Integer storeId = LoginUserUtil.getCurrentStoreId();
        Object result = incomeManageService.getGroupIncome(storeId, date, incomeType, page, size);
        return R.data(result);
    } catch (Exception e) {
        log.error("IncomeManageController.getGroupIncome ERROR: {}", e.getMessage(), e);
        return R.fail("查询失败：" + e.getMessage());
    }
}
```

### Service关键逻辑

```java
@Override
public Object getGroupIncome(Integer storeId, String date, Integer incomeType, Integer page, Integer size) {
    // 1. 构建查询条件
    LambdaQueryWrapper<StoreIncomeDetailsRecord> wrapper = new LambdaQueryWrapper<>();
    wrapper.between(StoreIncomeDetailsRecord::getCreatedTime, date + " 00:00:00", date + " 23:59:59")
            .isNull(StoreIncomeDetailsRecord::getCashOutId)
            .eq(StoreIncomeDetailsRecord::getStoreId, storeId);
    
    // 2. 收入类型筛选
    if (null != incomeType && 0 == incomeType) {
        wrapper.in(StoreIncomeDetailsRecord::getIncomeType,
                CouponTypeEnum.COUPON.getCode(), CouponTypeEnum.GROUP_BUY.getCode());
    } else if (null != incomeType) {
        wrapper.eq(StoreIncomeDetailsRecord::getIncomeType, incomeType);
    }
    
    // 3. 查询并统计
    // ... 省略详细实现
    
    return vo;
}
```

---

## 注意事项

1. **日期格式严格要求**
   - 必须为`yyyy-MM-dd`格式
   - 例如：`2025-11-11`
   - 错误格式会导致SQL查询异常

2. **收入类型说明**
   - 前端展示时，`incomeType=0`表示"主页"，实际查询优惠券+团购券
   - 建议前端根据业务需要选择合适的收入类型参数

3. **分页机制**
   - 使用内存分页（`ListToPage.setPage`）
   - 统计数据（总金额、券数量、退款金额）基于全量数据
   - 明细列表基于分页参数返回

4. **金额精度**
   - 所有金额计算使用`BigDecimal`
   - 四舍五入保留2位小数（`RoundingMode.HALF_UP`）

5. **性能考虑**
   - 单日数据量通常不大，内存分页可接受
   - 如需跨多日查询，建议在数据库层面实现分页

6. **账期规则**
   - 收入创建后3天才能提现
   - 此接口查询的是"可提现"金额
   - 已提现金额不在查询范围

---

## 相关接口

- [账期查询接口](./18-账期查询接口.md) - 查询可提现和不可提现的收入明细
- [账户余额查询接口](./19-查询账户余额接口.md) - 查询账户总余额和可提现金额
- [提现申请接口](./23-提现申请接口.md) - 发起提现申请
- [提现记录查询接口](./24-提现记录查询接口.md) - 查询提现记录

---

## 更新日志

| 版本 | 日期 | 说明 | 作者 |
|------|------|------|------|
| v1.0 | 2025-11-19 | 从app端迁移到web端，使用`LoginUserUtil`获取storeId | ssk |

---

**文档生成时间**: 2025-11-19  
**接口版本**: v1.0  
**维护人**: AI Assistant





