# 3171 预种计划（拼种/团购计划）— 实现方案

## Context

RWADurian 平台需要新增"3171 预种计划"功能。这本质是一个**拼种团购计划**：用户以 3171 USDT/份（1 棵树的 1/5 价格）参与认种，每购买一份立即享有分配、推荐奖励和算力收益；累计 5 份自动合成 1 棵树，触发合同签署和挖矿开启，同时解除交易和提现限制。此功能需无缝融入现有 1.0 认种分配和 2.0 算力挖矿体系，不破坏任何已有功能。

---

## 架构决策

### 核心原则：纯新增，零侵入

**现有代码文件一行不改。** 所有预种逻辑通过以下方式实现：
- 新增独立文件（新 controller、新 service、新 handler、新 Guard）
- 新增数据库表（不修改现有表结构）
- 新增 Kafka Topic（不修改现有 Topic 的生产/消费逻辑）
- 唯一的"接触"是在各服务的 `app.module.ts` 中 import 新模块（纯注册，不改现有逻辑）

### 决策 1：在 planting-service 中新增独立模块（非新服务）

预种作为 planting-service 内的一个**完全独立的 NestJS Module**：
- 有自己的 Controller、Service、Repository、Events
- 有自己的 Prisma 表（不触碰现有 planting_orders 等表）
- 通过自己的 Kafka Topic 通信（不复用 `planting.planting.created` 等现有 Topic）
- 与现有 PlantingOrder 聚合根**零耦合**

**不创建新微服务的原因**：共享同一 PostgreSQL (rwa_planting) + 同一 Redis + 同一 Kafka，避免新增基础设施和运维成本。

### 决策 2：预种有自己完整的独立事件流

预种使用全新的 Kafka Topic（`pre-planting.*`），现有消费者不会收到这些事件，所以：
- referral-service 的现有 `planting-created.handler.ts` **完全不受影响**
- reward-service 的现有 `reward-calculation.service.ts` **完全不受影响**
- contract-signing 的现有流程 **完全不受影响**

### 决策 3：合并产生的"树"不进入现有 PlantingOrder 表

5 份合并后，**不创建 PlantingOrder**（避免触发现有分配流程）。而是：
- 在预种模块自己的 `pre_planting_merges` 表中记录合并
- 预种模块自己处理合同签署（复用签署逻辑但在新文件中）
- 预种模块通过自己的事件通知 contribution-service（2.0）
- 预种模块直接调用 wallet-service 内部 API 设置 `hasPlanted=true`

**好处**：现有 PlantingOrder 的整个生命周期管道（事件 → referral → contract → reward → contribution）完全不被触碰。

### 决策 4：从第 1 份起即产生分配与算力，附带限制条件

**从第 1 份预种开始，以下功能立即生效：**
- 10 类权益分配（1/5 比例）立即触发并入账
- 推荐人立即获得 720 推荐奖励
- 算力（贡献值）立即产生并参与 2.0 挖矿分配
- 团队统计立即更新（预种份额计入团队业绩）

**附带的限制条件（未累积满 5 份前）：**
- 不可卖出 eUSDT（只能看，不能卖）
- 不可申请/授权社区、市公司、省公司
- 不可使用对公账户提现
- 若 1 年内未满 5 份 → 算力暂停分配（满 5 份后恢复，恢复起算 2 年后失效）

**合并后解锁全部限制 + 触发合同签署和挖矿开启。**

### 决策 5：推荐奖励取决于"被推荐人买了什么"，与推荐人自身无关

推荐奖励规则（与现有逻辑完全一致，只是新增预种产品类型）：

| 场景 | 推荐人 A | 被推荐人 B | A 获得奖励 |
|------|----------|------------|------------|
| B 认种 1 棵树 | 买了 1 份预种 | 认种 1 棵树 | **3600** 绿积分 |
| B 买 1 份预种 | 买了 1 份预种 | 买 1 份预种 | **720** 绿积分 |
| B 买 1 份预种 | 认种了 1 棵树 | 买 1 份预种 | **720** 绿积分 |
| B 认种 1 棵树 | 认种了 1 棵树 | 认种 1 棵树 | **3600** 绿积分（原有逻辑不变） |

规则本质：奖励金额 = 被推荐人购买产品对应的 SHARE_RIGHT（树=3600，预种份=720）

---

## 涉及服务清单（纯新增方式）

| 服务 | 现有代码改动 | 新增内容 |
|------|-------------|----------|
| **planting-service** | `app.module.ts` 加 1 行 import | **新增 PrePlantingModule**（独立聚合根、Controller、Service、Repository、Events、合并逻辑、开关） |
| **referral-service** | `app.module.ts` 加 1 行 import | **新增 PrePlantingStatsModule**（新 handler 消费预种事件更新团队统计） |
| **reward-service** | 无 | **不涉及**。预种的 1/5 分配由 planting-service 的预种模块直接调用 wallet-service 完成 |
| **authorization-service** | `app.module.ts` 加 1 行 import | **新增 PrePlantingGuardModule**（路由级 Middleware，仅对授权申请路由生效） |
| **trading-service** | `app.module.ts` 加 1 行 import | **新增 PrePlantingGuardModule**（路由级 Middleware，仅对卖单路由生效） |
| **wallet-service** | `app.module.ts` 加 1 行 import | **新增 PrePlantingGuardModule**（路由级 Middleware，仅对提现路由生效） |
| **admin-service** | `app.module.ts` 加 1 行 import | **新增 PrePlantingAdminModule**（开关管理 + 预种订单查询视图） |
| **contribution-service (2.0)** | `app.module.ts` 加 1 行 import | **新增 PrePlantingCdcModule**（CDC 消费预种表、1/5 算力、冻结逻辑） |
| **mobile-app (Flutter)** | 无 | **新增预种功能模块**（购买页、持仓页、合并流程、签约弹窗） |

**关键**：除 `app.module.ts` 的 import 注册外，**每个服务的现有 .ts 文件零修改**。

---

## 数据模型变更（全部是新增表，不修改任何现有表）

### planting-service（新增 4 张表）

```prisma
// ===== 以下全部是新增表，写在 schema.prisma 末尾 =====

// 预种订单
model PrePlantingOrder {
  id              BigInt   @id @default(autoincrement())
  orderNo         String   @unique       // PPL{timestamp}{random}
  userId          BigInt
  accountSequence String
  portionCount    Int      @default(1)
  pricePerPortion Decimal  @default(3171)
  totalAmount     Decimal                // portionCount × 3171
  provinceCode    String                 // 省代码（购买时选择）
  cityCode        String                 // 市代码（购买时选择）
  status          String   @default("CREATED") // CREATED → PAID → MERGED
  mergedToMergeId BigInt?                // 合并后指向 PrePlantingMerge.id
  mergedAt        DateTime?
  createdAt       DateTime @default(now())
  paidAt          DateTime?
  updatedAt       DateTime @updatedAt
  @@map("pre_planting_orders")
}

// 预种持仓（每用户一条）
model PrePlantingPosition {
  id                BigInt   @id @default(autoincrement())
  userId            BigInt   @unique
  accountSequence   String   @unique
  totalPortions     Int      @default(0)  // 累计购买份数（含已合并）
  availablePortions Int      @default(0)  // 待合并份数（0-4）
  mergedPortions    Int      @default(0)  // 已合并份数
  totalTreesMerged  Int      @default(0)  // 已合成的树数
  provinceCode      String?               // 首次购买时选择的省代码（后续复用）
  cityCode          String?               // 首次购买时选择的市代码（后续复用）
  firstPurchaseAt   DateTime?             // 首次购买时间（1年冻结起点）
  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt
  @@map("pre_planting_positions")
}

// 合并记录（预种的"树"，不进入现有 planting_orders 表）
model PrePlantingMerge {
  id              BigInt   @id @default(autoincrement())
  mergeNo         String   @unique       // PMG{timestamp}{random}
  userId          BigInt
  accountSequence String
  sourceOrderNos  Json                   // 5 笔预种订单号数组
  treeCount       Int      @default(1)   // 合并产生的树数（固定 1）

  // 省市选择（合并后由用户选择）
  selectedProvince String?
  selectedCity     String?
  provinceCityConfirmedAt DateTime?

  // 合同签署
  contractStatus  String   @default("PENDING") // PENDING → SIGNED → EXPIRED
  contractSignedAt DateTime?

  // 挖矿
  miningEnabledAt DateTime?

  mergedAt        DateTime @default(now())
  @@map("pre_planting_merges")
}

// 预种分配记录（预种模块自己管理分配，不经过 reward-service）
model PrePlantingRewardEntry {
  id                     BigInt   @id @default(autoincrement())
  sourceOrderNo          String          // 来源预种订单号
  sourceAccountSequence  String          // 购买者 accountSequence
  recipientAccountSequence String        // 接收者 accountSequence（推荐人/社区/省市公司/系统账户）
  rightType              String          // 权益类型（同 RightType 枚举）
  usdtAmount             Decimal
  rewardStatus           String   @default("SETTLED") // SETTLED / PENDING / EXPIRED
  memo                   String?         // 分配原因说明
  createdAt              DateTime @default(now())
  @@map("pre_planting_reward_entries")
}
```

**注意**：现有 `planting_orders`、`planting_positions` 等表**零修改**。
**注意**：现有 `PlantingOrder` 聚合根代码**零修改**。

### admin-service（新增配置表）

```prisma
// 追加到 schema.prisma 末尾
model PrePlantingConfig {
  id          String   @id @default(uuid())
  isActive    Boolean  @default(false)
  activatedAt DateTime?
  updatedAt   DateTime @updatedAt
  updatedBy   String?
  @@map("pre_planting_configs")
}
```

### contribution-service 2.0（新增同步表）

```prisma
// 追加到 schema.prisma 末尾
model SyncedPrePlantingAdoption {
  id                     BigInt   @id @default(autoincrement())
  originalOrderId        BigInt   @unique
  accountSequence        String
  portionCount           Int
  purchaseDate           DateTime @db.Date
  contributionPerPortion Decimal
  contributionFrozen     Boolean  @default(false)
  freezeExpiresAt        DateTime?
  contributionDistributed Boolean @default(false)
  syncedAt               DateTime @default(now())
  @@map("synced_pre_planting_adoptions")
}
```

### 预种分配金额配置（在预种模块新文件中定义）

```typescript
// planting-service/src/pre-planting/domain/value-objects/pre-planting-right-amounts.ts（新文件）
// 基准：reward-service 的 RIGHT_AMOUNTS（实际生效的分配金额）
// 预种每份 = 整棵树金额 / 5，差额 4.8 归入总部社区
export const PRE_PLANTING_RIGHT_AMOUNTS = {
  COST_FEE:               576,      // 2880/5
  OPERATION_FEE:          420,      // 2100/5
  HEADQUARTERS_BASE_FEE:  29.4,     // 123/5 + 4.8 差额（3171 - 3166.2 = 4.8）
  RWAD_POOL_INJECTION:    1152,     // 5760/5
  SHARE_RIGHT:            720,      // 3600/5 = 720（推荐奖励金额）
  PROVINCE_AREA_RIGHT:    21.6,     // 108/5
  PROVINCE_TEAM_RIGHT:    28.8,     // 144/5
  CITY_AREA_RIGHT:        50.4,     // 252/5
  CITY_TEAM_RIGHT:        57.6,     // 288/5
  COMMUNITY_RIGHT:        115.2,    // 576/5
} as const;
// 合计 = 576 + 420 + 29.4 + 1152 + 720 + 21.6 + 28.8 + 50.4 + 57.6 + 115.2 = 3171.0 ✓
```

---

## 核心事件流

### 流程 A：预种购买（每份） — 全部在预种模块内完成

**重要**：购买时即选择省市（与现有认种流程一致），后续购买复用同一省市。
这样 10 类权益全部可在购买时立即分配。

```
用户购买 1 份预种（3171 USDT）
  │
  ▼
[planting-service / PrePlantingModule]（全新代码，不触碰现有代码）
  │
  ├─ Step 1：前置校验
  │   ├─ 开关 ON（或续购规则通过）
  │   ├─ 余额 ≥ 3171
  │   └─ 省市信息（首次购买：用户选择；续购：自动复用 PrePlantingPosition 中的省市）
  │
  ├─ Step 2：冻结余额
  │   └─ wallet.freezeForPlanting(3171)（调用已有内部 API）
  │
  ├─ Step 3：确定 10 类权益的分配对象（pre-planting-reward.service.ts）
  │   │
  │   ├─ 4 类系统费用（硬编码，无需查询）：
  │   │   ├─ COST_FEE:      576  → S0000000002（成本账户）
  │   │   ├─ OPERATION_FEE:  420  → S0000000003（运营账户）
  │   │   ├─ HQ_BASE_FEE:   29.4 → S0000000001（总部社区）
  │   │   └─ RWAD_POOL:     1152  → S0000000004（RWAD底池）
  │   │
  │   ├─ 推荐奖励（调用 referral-service 已有 API）：
  │   │   └─ SHARE_RIGHT:   720  → GET /api/v1/referral/chain/{accountSeq}
  │   │       ├─ 有推荐人且已认种 → 推荐人账户（立即到账）
  │   │       ├─ 有推荐人未认种 → 推荐人账户（PENDING，24h 内认种可领取）
  │   │       └─ 无推荐人 → S0000000005（分享权益池）
  │   │
  │   ├─ 社区权益（调用 authorization-service 已有 API）：
  │   │   └─ COMMUNITY:     115.2 → GET /authorization/community-reward-distribution
  │   │       ├─ 有前有效社区 → 社区领导账户
  │   │       └─ 无社区 → S0000000001（总部）
  │   │
  │   └─ 省市 4 类权益（调用 authorization-service 已有 API，需要 provinceCode/cityCode）：
  │       ├─ PROVINCE_AREA: 21.6  → GET /authorization/province-area-reward-distribution?provinceCode=XX
  │       │   └─ 有正式省公司 → 省公司账户 | 无 → 9{provinceCode}（系统省账户）
  │       ├─ PROVINCE_TEAM: 28.8  → GET /authorization/province-team-reward-distribution
  │       │   └─ 有授权省团队 → 团队领导账户 | 无 → S0000000001（总部）
  │       ├─ CITY_AREA:     50.4  → GET /authorization/city-area-reward-distribution?cityCode=XX
  │       │   └─ 有正式市公司 → 市公司账户 | 无 → 8{cityCode}（系统市账户）
  │       └─ CITY_TEAM:     57.6  → GET /authorization/city-team-reward-distribution
  │           └─ 有授权市团队 → 团队领导账户 | 无 → S0000000001（总部）
  │
  ├─ Step 4：事务内持久化
  │   ├─ 创建 PrePlantingOrder(status=PAID, provinceCode, cityCode)
  │   ├─ 更新 PrePlantingPosition(totalPortions++, availablePortions++)
  │   ├─ 设置 firstPurchaseAt + provinceCode/cityCode（仅首次）
  │   ├─ 创建 PrePlantingRewardEntry（10 条记录，含每条的接收人 accountSequence）
  │   └─ Outbox: "pre-planting.portion.purchased"
  │
  ├─ Step 5：执行资金转账
  │   ├─ wallet.confirmDeduction(3171)（调用已有 API）
  │   └─ wallet.allocateFunds(10 条分配)（调用已有 API，与 reward-service 调用格式完全一致）
  │
  └─ Step 6：检查是否触发合并
      └─ availablePortions == 5 ? → 触发合并（流程 B）
  │
  ▼
[referral-service / PrePlantingStatsModule]（新增 handler，不改现有文件）
  ├─ 消费 "pre-planting.portion.purchased"
  └─ 更新 TeamStatistics（团队认种统计，预种份额按 1/5 棵树计入）
```

**与现有系统的隔离**：
- 预种模块**直接调用** referral-service 和 authorization-service 的**已有内部 API** 确定分配对象
- 确定对象后，调用 wallet-service 的 `allocateFunds` 执行转账（与 reward-service 调同一个 API）
- **reward-service 完全不感知预种的存在**——预种模块自己做了 reward-service 做的事情
- 推荐奖励 720 由预种模块计算后直接写入推荐人钱包

### 流程 B：自动合并（5 份 → 1 棵树） — 不创建 PlantingOrder

```
availablePortions == 5
  │
  ▼
[planting-service / PrePlantingModule] 同一事务内：
  ├─ 创建 PrePlantingMerge 记录（mergeNo, 5个sourceOrderNos, contractStatus=PENDING）
  │   └─ 注意：不创建 PlantingOrder！合并的树记录在自己的表中
  ├─ 标记 5 笔 PrePlantingOrder → MERGED
  ├─ 更新 PrePlantingPosition(available-=5, merged+=5, treesMerged++)
  └─ Outbox: "pre-planting.merged"
  │
  ▼
前端收到合并通知 → 弹出合同签署流程（省市已在首次购买时确定，无需再选）
  │
  ▼
[planting-service / PrePlantingModule] 处理合同签署：
  ├─ 省市从 PrePlantingPosition 直接带入 PrePlantingMerge（无需用户重新选择）
  ├─ 生成合同 PDF（复用 MinIO 上传逻辑）
  ├─ 用户签约 → contractStatus = SIGNED
  ├─ wallet.setHasPlanted(accountSequence, true)（调用已有 API）
  ├─ Outbox: "pre-planting.contract.signed"
  └─ 开启挖矿：miningEnabledAt = now()
  │
  ▼
[contribution-service / PrePlantingCdcModule]（新增 handler）
  ├─ 消费 CDC 或 "pre-planting.contract.signed"
  └─ 同步合并记录，标记为 MINING_ENABLED
```

**关键隔离点**：
- 不创建 PlantingOrder → referral-service 的现有 handler 不会被触发
- 不发布 `planting.planting.created` → reward-service 的现有 handler 不会被触发
- 不发布 `planting-events` → contract-signing 的现有 handler 不会被触发
- **所有预种事件走 `pre-planting.*` 独立 Topic**

### 流程 C：CDC 同步到 2.0（算力 — 从第 1 份即生效）

```
每份预种购买支付成功后，Debezium 立即捕获 pre_planting_orders 变更
  │
  ▼
[contribution-service] CDC Consumer
  ├─ 同步到 SyncedPrePlantingAdoption
  ├─ 立即计算算力 = contributionPerTree / 5 × portionCount
  ├─ 算力立即参与挖矿分配（从第 1 份起就有收益）
  ├─ 解锁条件同正常认种（15 级 + 3 阶梯，按预种份额判断）
  │
  ├─ 1 年冻结逻辑（定时任务检查）：
  │   ├─ 正常状态：firstPurchaseAt + 1 年内 → 算力正常分配
  │   ├─ 触发冻结：firstPurchaseAt + 1 年后仍未满 5 份 → 所有算力暂停分配
  │   └─ 解冻恢复：此 ID 后续累计满 5 份 → 算力恢复分配
  │       └─ 恢复后的失效期 = 恢复日起算 + 2 年
  │
  └─ 正常到期：首次产生挖矿收益日 + 2 年后失效（未被冻结过的情况）
```

**关键时间线示例：**
```
Day 0:   买第 1 份 → 算力 = tree算力/5，立即开始挖矿分配
Day 30:  买第 2 份 → 算力 += tree算力/5
Day 200: 买第 3 份 → 算力 += tree算力/5
Day 365: 仍只有 3 份 → 所有 3 份算力冻结（暂停分配）
Day 400: 买第 4 份 → 仍冻结（未满 5 份）
Day 420: 买第 5 份 → 自动合并成 1 棵树
         → 所有 5 份算力解冻，恢复分配
         → 解冻起算 + 2 年后失效
```

---

## 跨服务限制实现（全部通过新增 NestJS Middleware，不改现有代码）

### 实现模式：NestJS Middleware + forRoutes 精确路由匹配 + 内部 API

每个需要限制的服务新增一个 `PrePlantingGuardModule`（新文件），通过 `NestModule.configure()` + `forRoutes()` 注册路由级别 Middleware。
**只有特定路由才会执行检查逻辑**，其他路由完全无感知、零执行开销。Middleware 调用 planting-service 暴露的预种资格 API。

**planting-service 新增内部 API**（在 PrePlantingModule 内）：
```
GET /internal/pre-planting/eligibility/{accountSequence}
返回：{
  hasPrePlanting: boolean,        // 是否有预种记录
  totalPortions: number,          // 累计份数
  totalTreesMerged: number,       // 已合成树数
  canApplyAuthorization: boolean, // 可申请授权
  canTrade: boolean,              // 可卖出 eUSDT
}
```

逻辑：
- `canApplyAuthorization = totalTreesMerged >= 1`（已有认种记录的老用户不受影响，因为 hasPrePlanting=false 时 Guard 直接放行）
- `canTrade = totalTreesMerged >= 1`（同上）

### 授权锁（authorization-service — 新增文件）

```
新增文件：authorization-service/src/pre-planting/pre-planting-guard.module.ts
新增文件：authorization-service/src/pre-planting/pre-planting-authorization.middleware.ts
新增文件：authorization-service/src/pre-planting/pre-planting.client.ts
```

```typescript
// pre-planting-guard.module.ts（新文件，不触碰现有代码）
@Module({ providers: [PrePlantingClient] })
export class PrePlantingGuardModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(PrePlantingAuthorizationMiddleware)
      .forRoutes(
        { path: 'authorization/apply*', method: RequestMethod.POST },
      );
  }
}
```

```typescript
// pre-planting-authorization.middleware.ts（新文件）
@Injectable()
export class PrePlantingAuthorizationMiddleware implements NestMiddleware {
  constructor(private readonly prePlantingClient: PrePlantingClient) {}

  async use(req: Request, res: Response, next: NextFunction) {
    try {
      const accountSequence = req['user']?.accountSequence;
      if (!accountSequence) return next(); // 无用户信息，放行

      const eligibility = await this.prePlantingClient.getEligibility(accountSequence);

      // 没有预种记录（纯认种用户）→ 直接放行，现有逻辑零影响
      if (!eligibility.hasPrePlanting) return next();

      // 有预种记录但未满 5 份合并 → 拦截
      if (!eligibility.canApplyAuthorization) {
        return res.status(403).json({
          message: '须累积购买5份预种计划合并成树后方可申请授权',
        });
      }
      return next();
    } catch (error) {
      // planting-service 不可达时，默认放行（不影响现有功能）
      return next();
    }
  }
}
```

### 交易锁（trading-service — 新增文件）

```
新增文件：trading-service/src/pre-planting/pre-planting-guard.module.ts
新增文件：trading-service/src/pre-planting/pre-planting-trading.middleware.ts
新增文件：trading-service/src/pre-planting/pre-planting.client.ts
```

同理：Middleware 通过 `forRoutes({ path: 'trading/orders', method: POST })` 精确匹配卖单路由。其他路由**完全不经过此 Middleware**。对无预种记录的用户直接放行。

### 提现锁（wallet-service — 新增文件）

```
新增文件：wallet-service/src/pre-planting/pre-planting-guard.module.ts
新增文件：wallet-service/src/pre-planting/pre-planting-withdrawal.middleware.ts
新增文件：wallet-service/src/pre-planting/pre-planting.client.ts
```

Middleware 通过 `forRoutes` 精确匹配提现路由，检查 `hasPlanted`（wallet-service 自己的字段，合并签约后由 PrePlantingModule 设置为 true）。

**Middleware 的关键设计**：
- **精确路由匹配**：通过 `forRoutes()` 只对特定路由生效，其他路由零执行开销
- **异常容错**：planting-service 不可达时默认放行（`catch → next()`），不影响现有功能
- `hasPrePlanting=false`（纯认种用户）→ **直接放行**，现有功能零影响
- `hasPrePlanting=true` 且 `canXxx=true` → 放行
- `hasPrePlanting=true` 且 `canXxx=false` → 拦截

### 1 年算力冻结（contribution-service — 新增文件）

```
新增文件：contribution-service/src/pre-planting/pre-planting-cdc.module.ts
新增文件：contribution-service/src/pre-planting/pre-planting-contribution.service.ts
新增文件：contribution-service/src/pre-planting/pre-planting-freeze-scheduler.ts
```

冻结逻辑在全新的 service 文件中实现，不触碰现有 `contribution-calculation.service.ts`：

```typescript
// pre-planting-freeze-scheduler.ts（新文件）
@Cron('0 0 * * *') // 每日检查
async checkContributionFreeze() {
  // 查找所有 firstPurchaseAt + 1年 < NOW() 且 totalPortions < 5 的用户
  // 冻结其预种算力记录
  // 查找所有 totalPortions >= 5 且仍被冻结的用户
  // 解冻，设 expireDate = 解冻日 + 2年
}
```

---

## 后台开关逻辑

修改文件：`admin-service` 新增 `PrePlantingConfigController` + `PrePlantingConfigEntity`

### 核心原则

- **随时可开**：打开后所有用户均可购买预种
- **随时可关**：关闭后限制新购买，但**已成交的一切保持不变**
  - 已支付的预种订单：状态不变、分配不变、算力不变
  - 已获得的推荐奖励：不回收
  - 已产生的算力：继续参与挖矿分配（除非触发 1 年冻结规则）
  - 待合并的份额：允许继续购买至下一个 5 的整数倍（凑满合并）
- **开关仅控制"能否发起新购买"，不影响任何已完成的业务流程**

### 开关规则（planting-service 购买前校验）：

```typescript
async validatePrePlantingPurchase(userId, portionCount) {
  const config = await adminClient.getPrePlantingConfig();
  const position = await prePositionRepo.findByUserId(userId);

  if (config.isActive) {
    // 开关打开：任何人都可以购买
    return;
  }

  // 开关关闭：已成交的保持不变，仅限制新购买
  if (!position || position.totalPortions === 0) {
    // 从未购买过的用户：显示"待开启"
    throw new BusinessError('预种功能待开启');
  }

  // 已有未凑满的份额：允许继续购买至下一个 5 的倍数
  const remainder = position.availablePortions % 5;
  if (remainder === 0) {
    // 已凑满当前轮次（如 5/10/15...），不可开启新一轮
    throw new BusinessError('预种功能已关闭，您当前份额已满，无法继续购买');
  }
  const maxAdditional = 5 - remainder;
  if (portionCount > maxAdditional) {
    throw new BusinessError(`当前只可再购买 ${maxAdditional} 份以凑满5份`);
  }
}
```

### 前端展示逻辑

| 开关状态 | 用户状态 | 前端表现 |
|----------|----------|----------|
| ON | 任何用户 | 正常显示购买入口 |
| OFF | 从未购买 | 显示"待开启"，购买按钮置灰 |
| OFF | 有 1-4 份未合并 | 显示"可继续购买 N 份凑满合并"，购买按钮可用 |
| OFF | 份额恰好为 5 的倍数 | 显示"预种功能已暂停"，购买按钮置灰 |

---

## 新增 Kafka Topics

| Topic | 生产者 | 消费者 | 触发时机 |
|-------|--------|--------|----------|
| `pre-planting.portion.purchased` | planting-service | referral-service | 每份购买支付成功 |
| `pre-planting.order.paid` | referral-service | reward-service | referral 处理完团队统计后 |
| `pre-planting.merged` | planting-service | contribution-service, referral-service | 5 份合并完成 |

**注意：不复用任何现有 Topic**。预种的所有事件（购买、合并、签约）全部走 `pre-planting.*` 独立 Topic，现有消费者完全不受影响。

---

## 新增 Debezium CDC Connector（独立连接器，不修改现有连接器）

**不修改现有 Debezium 连接器**。为预种表创建一个**全新的独立连接器**，确保现有 CDC 流零影响：

```json
{
  "name": "pre-planting-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "postgres-planting",
    "database.port": "5432",
    "database.dbname": "rwa_planting",
    "database.server.name": "cdc.pre-planting",
    "table.include.list": "public.pre_planting_orders,public.pre_planting_positions,public.pre_planting_merges",
    "slot.name": "pre_planting_slot",
    "publication.name": "pre_planting_publication",
    "topic.prefix": "cdc.pre-planting"
  }
}
```

Topic 命名（与现有 CDC Topic 完全隔离）：
- `cdc.pre-planting.public.pre_planting_orders`
- `cdc.pre-planting.public.pre_planting_positions`
- `cdc.pre-planting.public.pre_planting_merges`

contribution-service 新增对应 CDC Consumer（在 PrePlantingCdcModule 内）。

**隔离保证**：
- 独立 replication slot → 不影响现有 slot 的 WAL 消费
- 独立 publication → 不影响现有 publication
- 独立 topic prefix → 现有消费者不会收到预种 CDC 事件

---

## 关键文件索引

### 需要触碰的现有文件（仅 import 注册，不改业务逻辑）

| 文件 | 改动 |
|------|------|
| `planting-service/src/app.module.ts` | `imports: [..., PrePlantingModule]` |
| `planting-service/prisma/schema.prisma` | 文件末尾追加 4 张新表定义 |
| `referral-service/src/app.module.ts` | `imports: [..., PrePlantingStatsModule]` |
| `authorization-service/src/app.module.ts` | `imports: [..., PrePlantingGuardModule]` |
| `trading-service/src/app.module.ts` | `imports: [..., PrePlantingGuardModule]` |
| `wallet-service/src/app.module.ts` | `imports: [..., PrePlantingGuardModule]` |
| `admin-service/src/app.module.ts` | `imports: [..., PrePlantingAdminModule]` |
| `admin-service/prisma/schema.prisma` | 文件末尾追加 1 张新表定义 |
| `contribution-service/src/app.module.ts` | `imports: [..., PrePlantingCdcModule]` |
| `contribution-service/prisma/schema.prisma` | 文件末尾追加 1 张新表定义 |

**以上改动总量**：每个文件仅增加 1 行 import。现有业务逻辑文件**零修改**。

### 全部新增的文件

**planting-service/src/pre-planting/**（核心模块，约 15-20 个新文件）
| 文件 | 内容 |
|------|------|
| `pre-planting.module.ts` | 模块注册 |
| `domain/aggregates/pre-planting-order.aggregate.ts` | PrePlantingOrder 聚合根 |
| `domain/aggregates/pre-planting-position.aggregate.ts` | PrePlantingPosition 聚合根 |
| `domain/aggregates/pre-planting-merge.aggregate.ts` | PrePlantingMerge 聚合根（合并的树） |
| `domain/events/pre-planting-*.event.ts` | 领域事件（purchased/merged/contract-signed） |
| `domain/value-objects/pre-planting-order-status.enum.ts` | 预种状态枚举 |
| `domain/value-objects/pre-planting-right-amounts.ts` | 1/5 分配金额常量 |
| `application/services/pre-planting-application.service.ts` | 购买 + 合并应用服务 |
| `application/services/pre-planting-reward.service.ts` | 预种分配服务（调用 wallet-service） |
| `application/services/pre-planting-contract.service.ts` | 合并后合同签署服务 |
| `api/controllers/pre-planting.controller.ts` | 用户端 API |
| `api/controllers/internal-pre-planting.controller.ts` | 内部资格查询 API |
| `infrastructure/repositories/pre-planting-order.repository.ts` | 订单仓储 |
| `infrastructure/repositories/pre-planting-position.repository.ts` | 持仓仓储 |
| `infrastructure/repositories/pre-planting-merge.repository.ts` | 合并记录仓储 |
| `infrastructure/kafka/pre-planting-outbox-publisher.service.ts` | Outbox 事件发布 |

**referral-service/src/pre-planting/**（新 handler）
| 文件 | 内容 |
|------|------|
| `pre-planting-stats.module.ts` | 模块注册 |
| `pre-planting-purchased.handler.ts` | 消费预种购买事件，更新团队统计 |

**authorization-service/src/pre-planting/**（路由级 Middleware）
| 文件 | 内容 |
|------|------|
| `pre-planting-guard.module.ts` | Middleware 模块注册（forRoutes 精确匹配） |
| `pre-planting-authorization.middleware.ts` | 授权申请拦截 Middleware |
| `pre-planting.client.ts` | 调用 planting-service 资格 API 的 HTTP Client |

**trading-service/src/pre-planting/**（路由级 Middleware）
| 文件 | 内容 |
|------|------|
| `pre-planting-guard.module.ts` | Middleware 模块注册（forRoutes 精确匹配） |
| `pre-planting-trading.middleware.ts` | 卖单拦截 Middleware |
| `pre-planting.client.ts` | HTTP Client |

**wallet-service/src/pre-planting/**（路由级 Middleware）
| 文件 | 内容 |
|------|------|
| `pre-planting-guard.module.ts` | Middleware 模块注册（forRoutes 精确匹配） |
| `pre-planting-withdrawal.middleware.ts` | 提现拦截 Middleware |
| `pre-planting.client.ts` | HTTP Client |

**admin-service/src/pre-planting/**（开关管理）
| 文件 | 内容 |
|------|------|
| `pre-planting-admin.module.ts` | 模块注册 |
| `pre-planting-config.entity.ts` | 开关实体 |
| `pre-planting-config.controller.ts` | 开关 API |
| `pre-planting-config.repository.ts` | 开关仓储 |

**contribution-service/src/pre-planting/**（CDC + 算力）
| 文件 | 内容 |
|------|------|
| `pre-planting-cdc.module.ts` | 模块注册 |
| `pre-planting-cdc.consumer.ts` | 预种表 CDC 消费 |
| `pre-planting-contribution.service.ts` | 1/5 算力计算 |
| `pre-planting-freeze-scheduler.ts` | 1 年冻结定时任务 |

---

## 实施阶段

### Phase 1：预种购买基础（planting-service + admin-service）
- Schema 迁移（3 新表 + 1 字段）
- PrePlantingOrder 聚合根 + PrePlantingPosition
- 购买 API + 开关配置
- Outbox 事件发布

### Phase 2：分配与推荐（reward-service + referral-service）
- 1/5 分配逻辑 + 720 推荐奖励
- 预种事件消费 + 团队统计
- 合并订单跳过分配逻辑

### Phase 3：自动合并（planting-service 核心）
- 5 份合并 → PrePlantingMerge 创建（不创建 PlantingOrder）
- 合并后省市选择 + 合同签署流程
- 前端合并通知 + 签约弹窗

### Phase 4：跨服务限制（authorization + trading + wallet）
- 授权申请资格校验
- eUSDT 卖出限制
- 提现 hasPlanted 校验
- 内部 API 端点

### Phase 5：2.0 算力集成（contribution-service）
- CDC 新表消费
- 1/5 算力计算
- 1 年冻结 / 解冻逻辑
- Debezium 连接器配置

### Phase 6：前端 + 测试
- Flutter 预种购买/持仓页面
- 合并提示与合同签署
- admin-web 开关管理
- 集成测试（购买 → 分配 → 合并 → 签约 → 挖矿）

---

## 验证方案

1. **单元测试**：PrePlantingOrder 聚合根状态机、PRE_PLANTING_RIGHT_AMOUNTS 总额校验（= 3171）
2. **集成测试**：购买 5 份 → 验证 5 次分配（各 3171）→ 验证自动合并 → 验证 PrePlantingMerge 创建
3. **E2E 测试**：合并后签约 → hasPlanted=true → 可申请授权 → 可卖出 eUSDT → 可提现
4. **CDC 测试**：预种订单 → contribution-service 同步 → 算力 = tree算力/5 → 1 年冻结验证
5. **开关测试**：关闭开关 → 新用户不可购买 → 已有 3 份的用户可继续购买 2 份 → 凑满合并
