# Authorization Service 开发指导

## 项目概述

Authorization Service 是 RWA 榴莲皇后平台的授权管理微服务，负责社区授权、省/市公司授权、阶梯式考核管理、月度考核评估与排名、权益激活/失效管理等功能。

### 核心职责 ✅
- 社区/省公司/市公司授权管理
- 阶梯式考核规则执行
- 月度考核评估与排名
- 权益激活/失效管理
- 单月免考核授权（豁免功能）
- 自有团队占比考核
- 授权限制验证（团队内唯一性）
- 火柴人排名显示数据支持
- 认种限制功能（账户限时限量、总量限制）

### 不负责 ❌
- 团队统计数据计算（Referral Context）
- 奖励金额计算（Reward Context）
- 用户基本信息管理（Identity Context）
- 实际认种操作（Planting Context）

---

## 核心业务需求

### 1. 授权省公司功能
```
功能说明：
- 可以给账户授权成为《授权省公司》
- 授权后前端头像显示金色字体，如"授权湖南省"
- 取消授权后头像标识立即消失
- 可以给多个账号同时授权成为同一个省的《授权省公司》

团队权益：
- 省公司团队权益：20 USDT/棵

初始考核：
- 伞下团队认种总量达到3000棵后权益自动生效
- 考核的3000棵权益归上级省公司所有（无上级则归总部社区账户）
```

### 2. 授权市公司功能
```
功能说明：
- 可以给账户授权成为《授权市公司》
- 授权后前端头像显示金色字体，如"授权长沙市"
- 取消授权后头像标识立即消失
- 可以给多个账号同时授权成为同一个市的《授权市公司》

团队权益：
- 市公司团队权益：40 USDT/棵

初始考核：
- 伞下团队认种总量达到500棵后权益自动生效
- 考核的500棵权益归上级市公司所有（无上级则归总部社区账户）
```

### 3. 正式省公司授权
```
功能说明：
- 可以给账户授权成为《省公司》（正式）
- 授权后前端头像显示"湖南省"（无"授权"二字）
- 取消授权后头像标识立即消失

区域权益：
- 省区域权益：15 USDT/棵 + 1%算力
- 授权后该省新认种产生的区域权益直接进入省公司账户
- 取消后进入系统省公司账户
```

### 4. 正式市公司授权
```
功能说明：
- 可以给账户授权成为《市公司》（正式）
- 授权后前端头像显示"长沙市"（无"授权"二字）
- 取消授权后头像标识立即消失

区域权益：
- 市区域权益：35 USDT/棵 + 2%算力
- 授权后该市新认种产生的区域权益直接进入市公司账户
- 取消后进入系统市公司账户
```

### 5. 社区授权功能
```
功能说明：
- 可以给账户授权成为《量子社区》等社区

社区权益：
- 社区权益：80 USDT/棵

初始考核：
- 伞下团队认种总量达到100棵后权益自动生效
- 考核的100棵权益归上级社区所有（无上级则归总部社区账户）

月度考核：
- 每月需新增10棵
- 上月未达成10棵则权益失效
- 失效后重新从0开始考核10棵
```

### 6. 阶梯式考核规则

#### 省代阶梯考核目标
| 考核月 | 当月目标 | 累计目标 |
|--------|----------|----------|
| 第1个月 | 150 | 150 |
| 第2个月 | 300 | 450 |
| 第3个月 | 600 | 1,050 |
| 第4个月 | 1,200 | 2,250 |
| 第5个月 | 2,400 | 4,650 |
| 第6个月 | 4,800 | 9,450 |
| 第7个月 | 9,600 | 19,050 |
| 第8个月 | 19,200 | 38,250 |
| 第9个月 | 11,750 | 50,000 |

#### 市代阶梯考核目标
| 考核月 | 当月目标 | 累计目标 |
|--------|----------|----------|
| 第1个月 | 30 | 30 |
| 第2个月 | 60 | 90 |
| 第3个月 | 120 | 210 |
| 第4个月 | 240 | 450 |
| 第5个月 | 480 | 930 |
| 第6个月 | 960 | 1,890 |
| 第7个月 | 1,920 | 3,810 |
| 第8个月 | 3,840 | 7,650 |
| 第9个月 | 2,350 | 10,000 |

### 7. 授权限制规则
```
限制规则：
① 一个账号只能申请一个省代或一个市代
② 团队内唯一性：某账号申请某省/市授权后，其上级团队无法再申请同一省/市授权
   - 举例：D申请《授权长沙市》后，C、B、A都无法再授权《授权长沙市》
   - 申请时提示"本团队已有人申请"
③ 自有团队占比考核：本地用户（本省/市）占比默认5%才能参与评选第一名
④ 可单独豁免：给单个账号授权《不考核自有团队本省/市公司占比》
```

### 8. 火柴人排名显示
```
显示规则：
- 每个获得《授权省/市公司》的用户头像下方显示火柴人排名
- 火柴人动态奔跑方式展示
- 每个火柴人头上显示团队完成数量
- 脚下显示昵称
- 进度条目标：省50,000棵，市10,000棵
- 目标处图标为红旗
- 显示本月累计可发放收益（USDT与RWAD）
- 火柴人根据完成数量处于进度条对应位置
- 即使只有一个账户申请也显示完整排名信息

排名规则：
- 两用户同时达成累计目标时，取超越累计目标占比最多者为第一名
- 超越累计目标占比 = 实际完成目标 ÷ 累计目标
- 占比一致时，取先完成的为第一名
```

### 9. 单月豁免授权
```
单月社区权益授权：
① 只能对已授权的社区账户使用
② 授权后该社区账号本月不需要完成考核目标直接享有社区权益

单月省代权益授权：
① 只能对已授权的《授权省公司》账户使用
② 授权后该省代账号本月不需要完成累计考核目标直接享有团队权益
③ 下月继续考核上月累计目标（举例：第3个月获得豁免，第4个月继续考核第3个月的累计目标）

单月市代权益授权：
① 只能对已授权的《授权市公司》账户使用
② 授权后该市代账号本月不需要完成累计考核目标直接享有团队权益
③ 下月继续考核上月累计目标
```

### 10. 认种限制功能
```
账户限时限认种量：
- 举例：限制5天之内每个账户只能认种1棵
- 开关及参数可设置

总量限认种功能：
- 举例：设置为限制2天之内，总限种量为100棵
- 认种达到100棵后，所有账户将无法认种
- 直至超出设置时间或关闭此功能才能继续认种
```

### 11. 后台管理分层级
```
管理规则：
- 涉及后台数据修改或授权需要三个后台账号授权通过才能修改
- 后台记录各个账号授权的事件
```

---

## 技术架构

### 架构模式
- **DDD（领域驱动设计）**
- **六边形架构（Hexagonal Architecture）**
  - Presentation Layer (API Controllers)
  - Application Layer (Use Cases)
  - Domain Layer (Entities, Aggregates, Services)
  - Infrastructure Layer (Repos, External Services)

### 技术栈
- **框架**: NestJS + TypeScript
- **数据库**: PostgreSQL + Prisma ORM
- **缓存**: Redis
- **消息队列**: Kafka
- **定时任务**: @nestjs/schedule

---

## 数据模型设计

### Prisma Schema

```prisma
// schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// ============ 授权角色表 ============
model AuthorizationRole {
  id                        String   @id @default(uuid())
  userId                    String   @map("user_id")
  roleType                  RoleType @map("role_type")
  regionCode                String   @map("region_code")
  regionName                String   @map("region_name")
  status                    AuthorizationStatus @default(PENDING)
  displayTitle              String   @map("display_title")

  // 授权信息
  authorizedAt              DateTime? @map("authorized_at")
  authorizedBy              String?   @map("authorized_by")
  revokedAt                 DateTime? @map("revoked_at")
  revokedBy                 String?   @map("revoked_by")
  revokeReason              String?   @map("revoke_reason")

  // 考核配置
  initialTargetTreeCount    Int      @map("initial_target_tree_count")
  monthlyTargetType         MonthlyTargetType @map("monthly_target_type")

  // 自有团队占比
  requireLocalPercentage    Decimal  @default(5.0) @map("require_local_percentage") @db.Decimal(5, 2)
  exemptFromPercentageCheck Boolean  @default(false) @map("exempt_from_percentage_check")

  // 权益状态
  benefitActive             Boolean  @default(false) @map("benefit_active")
  benefitActivatedAt        DateTime? @map("benefit_activated_at")
  benefitDeactivatedAt      DateTime? @map("benefit_deactivated_at")

  // 当前考核月份索引
  currentMonthIndex         Int      @default(0) @map("current_month_index")

  // 时间戳
  createdAt                 DateTime @default(now()) @map("created_at")
  updatedAt                 DateTime @updatedAt @map("updated_at")

  // 关联
  assessments               MonthlyAssessment[]
  bypassRecords             MonthlyBypass[]

  @@unique([userId, roleType, regionCode])
  @@index([userId])
  @@index([roleType, regionCode])
  @@index([status])
  @@index([roleType, status])
  @@map("authorization_roles")
}

// ============ 月度考核表 ============
model MonthlyAssessment {
  id                    String   @id @default(uuid())
  authorizationId       String   @map("authorization_id")
  userId                String   @map("user_id")
  roleType              RoleType @map("role_type")
  regionCode            String   @map("region_code")

  // 考核月份
  assessmentMonth       String   @map("assessment_month") // YYYY-MM
  monthIndex            Int      @map("month_index") // 第几个月考核

  // 考核目标
  monthlyTarget         Int      @map("monthly_target")
  cumulativeTarget      Int      @map("cumulative_target")

  // 完成情况
  monthlyCompleted      Int      @default(0) @map("monthly_completed")
  cumulativeCompleted   Int      @default(0) @map("cumulative_completed")
  completedAt           DateTime? @map("completed_at") // 达标时间（用于排名）

  // 自有团队占比
  localTeamCount        Int      @default(0) @map("local_team_count")
  totalTeamCount        Int      @default(0) @map("total_team_count")
  localPercentage       Decimal  @default(0) @map("local_percentage") @db.Decimal(5, 2)
  localPercentagePass   Boolean  @default(false) @map("local_percentage_pass")

  // 超越目标占比
  exceedRatio           Decimal  @default(0) @map("exceed_ratio") @db.Decimal(10, 4)

  // 考核结果
  result                AssessmentResult @default(NOT_ASSESSED)

  // 排名
  rankingInRegion       Int?     @map("ranking_in_region")
  isFirstPlace          Boolean  @default(false) @map("is_first_place")

  // 豁免
  isBypassed            Boolean  @default(false) @map("is_bypassed")
  bypassedBy            String?  @map("bypassed_by")
  bypassedAt            DateTime? @map("bypassed_at")

  // 时间戳
  assessedAt            DateTime? @map("assessed_at")
  createdAt             DateTime @default(now()) @map("created_at")
  updatedAt             DateTime @updatedAt @map("updated_at")

  // 关联
  authorization         AuthorizationRole @relation(fields: [authorizationId], references: [id])

  @@unique([authorizationId, assessmentMonth])
  @@index([userId, assessmentMonth])
  @@index([roleType, regionCode, assessmentMonth])
  @@index([assessmentMonth, result])
  @@index([assessmentMonth, roleType, exceedRatio(sort: Desc)])
  @@map("monthly_assessments")
}

// ============ 单月豁免记录表 ============
model MonthlyBypass {
  id                String   @id @default(uuid())
  authorizationId   String   @map("authorization_id")
  userId            String   @map("user_id")
  roleType          RoleType @map("role_type")
  bypassMonth       String   @map("bypass_month") // YYYY-MM

  // 授权信息
  grantedBy         String   @map("granted_by")
  grantedAt         DateTime @map("granted_at")
  reason            String?

  // 审批信息（三人授权）
  approver1Id       String   @map("approver1_id")
  approver1At       DateTime @map("approver1_at")
  approver2Id       String?  @map("approver2_id")
  approver2At       DateTime? @map("approver2_at")
  approver3Id       String?  @map("approver3_id")
  approver3At       DateTime? @map("approver3_at")
  approvalStatus    ApprovalStatus @default(PENDING) @map("approval_status")

  createdAt         DateTime @default(now()) @map("created_at")

  authorization     AuthorizationRole @relation(fields: [authorizationId], references: [id])

  @@unique([authorizationId, bypassMonth])
  @@index([userId, bypassMonth])
  @@map("monthly_bypasses")
}

// ============ 阶梯考核目标配置表 ============
model LadderTargetConfig {
  id               String   @id @default(uuid())
  roleType         RoleType @map("role_type")
  monthIndex       Int      @map("month_index")
  monthlyTarget    Int      @map("monthly_target")
  cumulativeTarget Int      @map("cumulative_target")
  isActive         Boolean  @default(true) @map("is_active")

  createdAt        DateTime @default(now()) @map("created_at")
  updatedAt        DateTime @updatedAt @map("updated_at")

  @@unique([roleType, monthIndex])
  @@map("ladder_target_configs")
}

// ============ 认种限制配置表 ============
model PlantingRestriction {
  id                  String   @id @default(uuid())
  restrictionType     RestrictionType @map("restriction_type")

  // 账户限制配置
  accountLimitDays    Int?     @map("account_limit_days")
  accountLimitCount   Int?     @map("account_limit_count")

  // 总量限制配置
  totalLimitDays      Int?     @map("total_limit_days")
  totalLimitCount     Int?     @map("total_limit_count")
  currentTotalCount   Int      @default(0) @map("current_total_count")

  // 生效时间
  startAt             DateTime @map("start_at")
  endAt               DateTime @map("end_at")

  isActive            Boolean  @default(true) @map("is_active")
  createdBy           String   @map("created_by")
  createdAt           DateTime @default(now()) @map("created_at")
  updatedAt           DateTime @updatedAt @map("updated_at")

  @@map("planting_restrictions")
}

// ============ 管理员授权审批表 ============
model AdminApproval {
  id              String   @id @default(uuid())
  operationType   OperationType @map("operation_type")
  targetId        String   @map("target_id")
  targetType      String   @map("target_type")
  requestData     Json     @map("request_data")

  // 审批状态
  status          ApprovalStatus @default(PENDING)

  // 审批人
  requesterId     String   @map("requester_id")
  approver1Id     String?  @map("approver1_id")
  approver1At     DateTime? @map("approver1_at")
  approver2Id     String?  @map("approver2_id")
  approver2At     DateTime? @map("approver2_at")
  approver3Id     String?  @map("approver3_id")
  approver3At     DateTime? @map("approver3_at")

  // 完成信息
  completedAt     DateTime? @map("completed_at")
  rejectedBy      String?   @map("rejected_by")
  rejectedAt      DateTime? @map("rejected_at")
  rejectReason    String?   @map("reject_reason")

  createdAt       DateTime @default(now()) @map("created_at")
  updatedAt       DateTime @updatedAt @map("updated_at")

  @@index([status])
  @@index([targetId, targetType])
  @@map("admin_approvals")
}

// ============ 授权操作日志表 ============
model AuthorizationAuditLog {
  id              String   @id @default(uuid())
  operationType   String   @map("operation_type")
  targetUserId    String   @map("target_user_id")
  targetRoleType  RoleType? @map("target_role_type")
  targetRegionCode String? @map("target_region_code")

  operatorId      String   @map("operator_id")
  operatorRole    String   @map("operator_role")

  beforeState     Json?    @map("before_state")
  afterState      Json?    @map("after_state")

  ipAddress       String?  @map("ip_address")
  userAgent       String?  @map("user_agent")

  createdAt       DateTime @default(now()) @map("created_at")

  @@index([targetUserId])
  @@index([operatorId])
  @@index([createdAt])
  @@map("authorization_audit_logs")
}

// ============ 省市热度统计表 ============
model RegionHeatMap {
  id              String   @id @default(uuid())
  regionCode      String   @map("region_code")
  regionName      String   @map("region_name")
  regionType      RegionType @map("region_type")

  totalPlantings  Int      @default(0) @map("total_plantings")
  monthlyPlantings Int     @default(0) @map("monthly_plantings")
  weeklyPlantings Int      @default(0) @map("weekly_plantings")
  dailyPlantings  Int      @default(0) @map("daily_plantings")

  activeUsers     Int      @default(0) @map("active_users")
  authCompanyCount Int     @default(0) @map("auth_company_count")

  heatScore       Decimal  @default(0) @map("heat_score") @db.Decimal(10, 2)

  updatedAt       DateTime @updatedAt @map("updated_at")

  @@unique([regionCode, regionType])
  @@map("region_heat_maps")
}

// ============ 火柴人排名视图数据表 ============
model StickmanRanking {
  id                  String   @id @default(uuid())
  userId              String   @map("user_id")
  authorizationId     String   @map("authorization_id")
  roleType            RoleType @map("role_type")
  regionCode          String   @map("region_code")
  regionName          String   @map("region_name")

  // 用户信息
  nickname            String
  avatarUrl           String?  @map("avatar_url")

  // 进度信息
  currentMonth        String   @map("current_month")
  cumulativeCompleted Int      @map("cumulative_completed")
  cumulativeTarget    Int      @map("cumulative_target")
  progressPercentage  Decimal  @map("progress_percentage") @db.Decimal(5, 2)
  exceedRatio         Decimal  @map("exceed_ratio") @db.Decimal(10, 4)

  // 排名
  ranking             Int
  isFirstPlace        Boolean  @map("is_first_place")

  // 本月收益
  monthlyRewardUsdt   Decimal  @map("monthly_reward_usdt") @db.Decimal(18, 2)
  monthlyRewardRwad   Decimal  @map("monthly_reward_rwad") @db.Decimal(18, 8)

  updatedAt           DateTime @updatedAt @map("updated_at")

  @@unique([authorizationId, currentMonth])
  @@index([roleType, regionCode, currentMonth])
  @@map("stickman_rankings")
}

// ============ 系统配置表 ============
model AuthorizationConfig {
  id          String   @id @default(uuid())
  configKey   String   @unique @map("config_key")
  configValue String   @map("config_value")
  description String?
  isActive    Boolean  @default(true) @map("is_active")

  createdAt   DateTime @default(now()) @map("created_at")
  updatedAt   DateTime @updatedAt @map("updated_at")

  @@map("authorization_configs")
}

// ============ 枚举定义 ============
enum RoleType {
  COMMUNITY              // 社区
  AUTH_PROVINCE_COMPANY  // 授权省公司（团队权益20U）
  PROVINCE_COMPANY       // 正式省公司（区域权益15U+1%算力）
  AUTH_CITY_COMPANY      // 授权市公司（团队权益40U）
  CITY_COMPANY           // 正式市公司（区域权益35U+2%算力）
}

enum AuthorizationStatus {
  PENDING     // 待授权/待考核
  AUTHORIZED  // 已授权
  REVOKED     // 已撤销
}

enum AssessmentResult {
  NOT_ASSESSED  // 未考核
  PASS          // 达标
  FAIL          // 未达标
  BYPASSED      // 豁免
}

enum MonthlyTargetType {
  NONE    // 无月度考核（正式省市公司）
  FIXED   // 固定目标（社区：10棵/月）
  LADDER  // 阶梯目标（授权省市公司）
}

enum RestrictionType {
  ACCOUNT_LIMIT  // 账户限时限量
  TOTAL_LIMIT    // 总量限制
}

enum ApprovalStatus {
  PENDING   // 待审批
  APPROVED  // 已通过
  REJECTED  // 已拒绝
}

enum OperationType {
  GRANT_AUTHORIZATION    // 授予授权
  REVOKE_AUTHORIZATION   // 撤销授权
  GRANT_BYPASS           // 授予豁免
  EXEMPT_PERCENTAGE      // 豁免占比考核
  MODIFY_CONFIG          // 修改配置
}

enum RegionType {
  PROVINCE  // 省
  CITY      // 市
}
```

---

## 领域层设计

### 值对象（Value Objects）

```typescript
// domain/value-objects/authorization-id.vo.ts
export class AuthorizationId {
  constructor(public readonly value: string) {
    if (!value) {
      throw new DomainError('授权ID不能为空')
    }
  }

  static generate(): AuthorizationId {
    return new AuthorizationId(uuidv4())
  }

  static create(value: string): AuthorizationId {
    return new AuthorizationId(value)
  }

  equals(other: AuthorizationId): boolean {
    return this.value === other.value
  }
}

// domain/value-objects/region-code.vo.ts
export class RegionCode {
  constructor(public readonly value: string) {
    if (!value) {
      throw new DomainError('区域代码不能为空')
    }
  }

  static create(value: string): RegionCode {
    return new RegionCode(value)
  }

  equals(other: RegionCode): boolean {
    return this.value === other.value
  }
}

// domain/value-objects/month.vo.ts
export class Month {
  constructor(public readonly value: string) {
    if (!/^\d{4}-\d{2}$/.test(value)) {
      throw new DomainError('月份格式错误，应为YYYY-MM')
    }
  }

  static current(): Month {
    const now = new Date()
    const year = now.getFullYear()
    const month = String(now.getMonth() + 1).padStart(2, '0')
    return new Month(`${year}-${month}`)
  }

  static create(value: string): Month {
    return new Month(value)
  }

  next(): Month {
    const [year, month] = this.value.split('-').map(Number)
    const nextMonth = month === 12 ? 1 : month + 1
    const nextYear = month === 12 ? year + 1 : year
    return new Month(`${nextYear}-${String(nextMonth).padStart(2, '0')}`)
  }

  previous(): Month {
    const [year, month] = this.value.split('-').map(Number)
    const prevMonth = month === 1 ? 12 : month - 1
    const prevYear = month === 1 ? year - 1 : year
    return new Month(`${prevYear}-${String(prevMonth).padStart(2, '0')}`)
  }

  equals(other: Month): boolean {
    return this.value === other.value
  }
}

// domain/value-objects/assessment-config.vo.ts
export class AssessmentConfig {
  constructor(
    public readonly initialTargetTreeCount: number,
    public readonly monthlyTargetType: MonthlyTargetType
  ) {}

  static forCommunity(): AssessmentConfig {
    return new AssessmentConfig(10, MonthlyTargetType.FIXED)
  }

  static forAuthProvince(): AssessmentConfig {
    return new AssessmentConfig(500, MonthlyTargetType.LADDER)
  }

  static forProvince(): AssessmentConfig {
    return new AssessmentConfig(0, MonthlyTargetType.NONE)
  }

  static forAuthCity(): AssessmentConfig {
    return new AssessmentConfig(100, MonthlyTargetType.LADDER)
  }

  static forCity(): AssessmentConfig {
    return new AssessmentConfig(0, MonthlyTargetType.NONE)
  }
}

// domain/value-objects/benefit-amount.vo.ts
export class BenefitAmount {
  constructor(
    public readonly usdtPerTree: number,
    public readonly computingPowerPercentage: number
  ) {}

  static forCommunity(): BenefitAmount {
    return new BenefitAmount(80, 0)
  }

  static forAuthProvince(): BenefitAmount {
    return new BenefitAmount(20, 0)
  }

  static forProvince(): BenefitAmount {
    return new BenefitAmount(15, 1)
  }

  static forAuthCity(): BenefitAmount {
    return new BenefitAmount(40, 0)
  }

  static forCity(): BenefitAmount {
    return new BenefitAmount(35, 2)
  }

  static forRoleType(roleType: RoleType): BenefitAmount {
    switch (roleType) {
      case RoleType.COMMUNITY:
        return this.forCommunity()
      case RoleType.AUTH_PROVINCE_COMPANY:
        return this.forAuthProvince()
      case RoleType.PROVINCE_COMPANY:
        return this.forProvince()
      case RoleType.AUTH_CITY_COMPANY:
        return this.forAuthCity()
      case RoleType.CITY_COMPANY:
        return this.forCity()
      default:
        throw new DomainError(`未知角色类型: ${roleType}`)
    }
  }
}
```

### 聚合根（Aggregates）

```typescript
// domain/aggregates/authorization-role.aggregate.ts
export class AuthorizationRole extends AggregateRoot {
  private readonly _authorizationId: AuthorizationId
  private readonly _userId: UserId
  private readonly _roleType: RoleType
  private readonly _regionCode: RegionCode
  private readonly _regionName: string
  private _status: AuthorizationStatus
  private _displayTitle: string

  // 授权信息
  private _authorizedAt: Date | null
  private _authorizedBy: AdminUserId | null
  private _revokedAt: Date | null
  private _revokedBy: AdminUserId | null
  private _revokeReason: string | null

  // 考核配置
  private readonly _assessmentConfig: AssessmentConfig

  // 自有团队占比
  private _requireLocalPercentage: number
  private _exemptFromPercentageCheck: boolean

  // 权益状态
  private _benefitActive: boolean
  private _benefitActivatedAt: Date | null
  private _benefitDeactivatedAt: Date | null

  // 当前考核月份索引
  private _currentMonthIndex: number

  private readonly _createdAt: Date
  private _updatedAt: Date

  // Getters
  get authorizationId(): AuthorizationId { return this._authorizationId }
  get userId(): UserId { return this._userId }
  get roleType(): RoleType { return this._roleType }
  get regionCode(): RegionCode { return this._regionCode }
  get regionName(): string { return this._regionName }
  get status(): AuthorizationStatus { return this._status }
  get displayTitle(): string { return this._displayTitle }
  get assessmentConfig(): AssessmentConfig { return this._assessmentConfig }
  get requireLocalPercentage(): number { return this._requireLocalPercentage }
  get exemptFromPercentageCheck(): boolean { return this._exemptFromPercentageCheck }
  get benefitActive(): boolean { return this._benefitActive }
  get currentMonthIndex(): number { return this._currentMonthIndex }
  get isActive(): boolean { return this._status === AuthorizationStatus.AUTHORIZED }

  // 工厂方法
  static createCommunityAuth(params: {
    userId: UserId
    communityName: string
  }): AuthorizationRole {
    const auth = new AuthorizationRole(
      AuthorizationId.generate(),
      params.userId,
      RoleType.COMMUNITY,
      RegionCode.create(params.communityName),
      params.communityName,
      AuthorizationStatus.PENDING,
      params.communityName,
      null, null, null, null, null,
      AssessmentConfig.forCommunity(),
      0, true,
      false, null, null,
      0,
      new Date(), new Date()
    )

    auth.addDomainEvent(new CommunityAuthRequestedEvent({
      authorizationId: auth.authorizationId.value,
      userId: params.userId.value,
      communityName: params.communityName
    }))

    return auth
  }

  static createAuthProvinceCompany(params: {
    userId: UserId
    provinceCode: string
    provinceName: string
  }): AuthorizationRole {
    const auth = new AuthorizationRole(
      AuthorizationId.generate(),
      params.userId,
      RoleType.AUTH_PROVINCE_COMPANY,
      RegionCode.create(params.provinceCode),
      params.provinceName,
      AuthorizationStatus.PENDING,
      `授权${params.provinceName}`,
      null, null, null, null, null,
      AssessmentConfig.forAuthProvince(),
      5.0, false,
      false, null, null,
      0,
      new Date(), new Date()
    )

    auth.addDomainEvent(new AuthProvinceCompanyRequestedEvent({
      authorizationId: auth.authorizationId.value,
      userId: params.userId.value,
      provinceCode: params.provinceCode,
      provinceName: params.provinceName
    }))

    return auth
  }

  static createProvinceCompany(params: {
    userId: UserId
    provinceCode: string
    provinceName: string
    adminId: AdminUserId
  }): AuthorizationRole {
    const auth = new AuthorizationRole(
      AuthorizationId.generate(),
      params.userId,
      RoleType.PROVINCE_COMPANY,
      RegionCode.create(params.provinceCode),
      params.provinceName,
      AuthorizationStatus.AUTHORIZED,
      params.provinceName,
      new Date(), params.adminId, null, null, null,
      AssessmentConfig.forProvince(),
      0, true,
      true, new Date(), null,
      0,
      new Date(), new Date()
    )

    auth.addDomainEvent(new ProvinceCompanyAuthorizedEvent({
      authorizationId: auth.authorizationId.value,
      userId: params.userId.value,
      provinceCode: params.provinceCode,
      provinceName: params.provinceName,
      authorizedBy: params.adminId.value
    }))

    return auth
  }

  static createAuthCityCompany(params: {
    userId: UserId
    cityCode: string
    cityName: string
  }): AuthorizationRole {
    const auth = new AuthorizationRole(
      AuthorizationId.generate(),
      params.userId,
      RoleType.AUTH_CITY_COMPANY,
      RegionCode.create(params.cityCode),
      params.cityName,
      AuthorizationStatus.PENDING,
      `授权${params.cityName}`,
      null, null, null, null, null,
      AssessmentConfig.forAuthCity(),
      5.0, false,
      false, null, null,
      0,
      new Date(), new Date()
    )

    auth.addDomainEvent(new AuthCityCompanyRequestedEvent({
      authorizationId: auth.authorizationId.value,
      userId: params.userId.value,
      cityCode: params.cityCode,
      cityName: params.cityName
    }))

    return auth
  }

  static createCityCompany(params: {
    userId: UserId
    cityCode: string
    cityName: string
    adminId: AdminUserId
  }): AuthorizationRole {
    const auth = new AuthorizationRole(
      AuthorizationId.generate(),
      params.userId,
      RoleType.CITY_COMPANY,
      RegionCode.create(params.cityCode),
      params.cityName,
      AuthorizationStatus.AUTHORIZED,
      params.cityName,
      new Date(), params.adminId, null, null, null,
      AssessmentConfig.forCity(),
      0, true,
      true, new Date(), null,
      0,
      new Date(), new Date()
    )

    auth.addDomainEvent(new CityCompanyAuthorizedEvent({
      authorizationId: auth.authorizationId.value,
      userId: params.userId.value,
      cityCode: params.cityCode,
      cityName: params.cityName,
      authorizedBy: params.adminId.value
    }))

    return auth
  }

  // 核心领域行为

  /**
   * 激活权益（初始考核达标后）
   */
  activateBenefit(): void {
    if (this._benefitActive) {
      throw new DomainError('权益已激活')
    }

    this._status = AuthorizationStatus.AUTHORIZED
    this._benefitActive = true
    this._benefitActivatedAt = new Date()
    this._currentMonthIndex = 1
    this._updatedAt = new Date()

    this.addDomainEvent(new BenefitActivatedEvent({
      authorizationId: this._authorizationId.value,
      userId: this._userId.value,
      roleType: this._roleType,
      regionCode: this._regionCode.value
    }))
  }

  /**
   * 失效权益（月度考核不达标）
   */
  deactivateBenefit(reason: string): void {
    if (!this._benefitActive) {
      return
    }

    this._benefitActive = false
    this._benefitDeactivatedAt = new Date()
    this._currentMonthIndex = 0 // 重置月份索引
    this._updatedAt = new Date()

    this.addDomainEvent(new BenefitDeactivatedEvent({
      authorizationId: this._authorizationId.value,
      userId: this._userId.value,
      roleType: this._roleType,
      reason
    }))
  }

  /**
   * 管理员授权
   */
  authorize(adminId: AdminUserId): void {
    if (this._status === AuthorizationStatus.AUTHORIZED) {
      throw new DomainError('已授权，无需重复授权')
    }

    this._status = AuthorizationStatus.AUTHORIZED
    this._authorizedAt = new Date()
    this._authorizedBy = adminId
    this._updatedAt = new Date()

    this.addDomainEvent(new RoleAuthorizedEvent({
      authorizationId: this._authorizationId.value,
      userId: this._userId.value,
      roleType: this._roleType,
      regionCode: this._regionCode.value,
      authorizedBy: adminId.value
    }))
  }

  /**
   * 撤销授权
   */
  revoke(adminId: AdminUserId, reason: string): void {
    if (this._status === AuthorizationStatus.REVOKED) {
      throw new DomainError('已撤销')
    }

    this._status = AuthorizationStatus.REVOKED
    this._revokedAt = new Date()
    this._revokedBy = adminId
    this._revokeReason = reason
    this._benefitActive = false
    this._benefitDeactivatedAt = new Date()
    this._updatedAt = new Date()

    this.addDomainEvent(new RoleRevokedEvent({
      authorizationId: this._authorizationId.value,
      userId: this._userId.value,
      roleType: this._roleType,
      regionCode: this._regionCode.value,
      reason,
      revokedBy: adminId.value
    }))
  }

  /**
   * 豁免占比考核
   */
  exemptLocalPercentageCheck(adminId: AdminUserId): void {
    this._exemptFromPercentageCheck = true
    this._updatedAt = new Date()

    this.addDomainEvent(new PercentageCheckExemptedEvent({
      authorizationId: this._authorizationId.value,
      userId: this._userId.value,
      exemptedBy: adminId.value
    }))
  }

  /**
   * 取消豁免占比考核
   */
  cancelExemptLocalPercentageCheck(): void {
    this._exemptFromPercentageCheck = false
    this._updatedAt = new Date()
  }

  /**
   * 递增考核月份
   */
  incrementMonthIndex(): void {
    this._currentMonthIndex += 1
    this._updatedAt = new Date()
  }

  /**
   * 获取初始考核目标
   */
  getInitialTarget(): number {
    return this._assessmentConfig.initialTargetTreeCount
  }

  /**
   * 检查是否需要占比考核
   */
  needsLocalPercentageCheck(): boolean {
    return !this._exemptFromPercentageCheck &&
           this._requireLocalPercentage > 0 &&
           (this._roleType === RoleType.AUTH_PROVINCE_COMPANY ||
            this._roleType === RoleType.AUTH_CITY_COMPANY)
  }

  /**
   * 检查是否需要阶梯考核
   */
  needsLadderAssessment(): boolean {
    return this._assessmentConfig.monthlyTargetType === MonthlyTargetType.LADDER
  }

  /**
   * 检查是否需要固定月度考核
   */
  needsFixedAssessment(): boolean {
    return this._assessmentConfig.monthlyTargetType === MonthlyTargetType.FIXED
  }

  // 私有构造函数
  private constructor(
    authorizationId: AuthorizationId,
    userId: UserId,
    roleType: RoleType,
    regionCode: RegionCode,
    regionName: string,
    status: AuthorizationStatus,
    displayTitle: string,
    authorizedAt: Date | null,
    authorizedBy: AdminUserId | null,
    revokedAt: Date | null,
    revokedBy: AdminUserId | null,
    revokeReason: string | null,
    assessmentConfig: AssessmentConfig,
    requireLocalPercentage: number,
    exemptFromPercentageCheck: boolean,
    benefitActive: boolean,
    benefitActivatedAt: Date | null,
    benefitDeactivatedAt: Date | null,
    currentMonthIndex: number,
    createdAt: Date,
    updatedAt: Date
  ) {
    super()
    this._authorizationId = authorizationId
    this._userId = userId
    this._roleType = roleType
    this._regionCode = regionCode
    this._regionName = regionName
    this._status = status
    this._displayTitle = displayTitle
    this._authorizedAt = authorizedAt
    this._authorizedBy = authorizedBy
    this._revokedAt = revokedAt
    this._revokedBy = revokedBy
    this._revokeReason = revokeReason
    this._assessmentConfig = assessmentConfig
    this._requireLocalPercentage = requireLocalPercentage
    this._exemptFromPercentageCheck = exemptFromPercentageCheck
    this._benefitActive = benefitActive
    this._benefitActivatedAt = benefitActivatedAt
    this._benefitDeactivatedAt = benefitDeactivatedAt
    this._currentMonthIndex = currentMonthIndex
    this._createdAt = createdAt
    this._updatedAt = updatedAt
  }
}
```

```typescript
// domain/aggregates/monthly-assessment.aggregate.ts
export class MonthlyAssessment extends AggregateRoot {
  private readonly _assessmentId: AssessmentId
  private readonly _authorizationId: AuthorizationId
  private readonly _userId: UserId
  private readonly _roleType: RoleType
  private readonly _regionCode: RegionCode

  // 考核月份
  private readonly _assessmentMonth: Month
  private readonly _monthIndex: number

  // 考核目标
  private readonly _monthlyTarget: number
  private readonly _cumulativeTarget: number

  // 完成情况
  private _monthlyCompleted: number
  private _cumulativeCompleted: number
  private _completedAt: Date | null

  // 自有团队占比
  private _localTeamCount: number
  private _totalTeamCount: number
  private _localPercentage: number
  private _localPercentagePass: boolean

  // 超越目标占比
  private _exceedRatio: number

  // 考核结果
  private _result: AssessmentResult

  // 排名
  private _rankingInRegion: number | null
  private _isFirstPlace: boolean

  // 豁免
  private _isBypassed: boolean
  private _bypassedBy: AdminUserId | null
  private _bypassedAt: Date | null

  private _assessedAt: Date | null
  private readonly _createdAt: Date
  private _updatedAt: Date

  // Getters
  get assessmentId(): AssessmentId { return this._assessmentId }
  get authorizationId(): AuthorizationId { return this._authorizationId }
  get userId(): UserId { return this._userId }
  get roleType(): RoleType { return this._roleType }
  get assessmentMonth(): Month { return this._assessmentMonth }
  get monthIndex(): number { return this._monthIndex }
  get monthlyTarget(): number { return this._monthlyTarget }
  get cumulativeTarget(): number { return this._cumulativeTarget }
  get cumulativeCompleted(): number { return this._cumulativeCompleted }
  get result(): AssessmentResult { return this._result }
  get exceedRatio(): number { return this._exceedRatio }
  get rankingInRegion(): number | null { return this._rankingInRegion }
  get isFirstPlace(): boolean { return this._isFirstPlace }
  get completedAt(): Date | null { return this._completedAt }

  // 工厂方法
  static create(params: {
    authorizationId: AuthorizationId
    userId: UserId
    roleType: RoleType
    regionCode: RegionCode
    assessmentMonth: Month
    monthIndex: number
    monthlyTarget: number
    cumulativeTarget: number
  }): MonthlyAssessment {
    return new MonthlyAssessment(
      AssessmentId.generate(),
      params.authorizationId,
      params.userId,
      params.roleType,
      params.regionCode,
      params.assessmentMonth,
      params.monthIndex,
      params.monthlyTarget,
      params.cumulativeTarget,
      0, 0, null,
      0, 0, 0, false,
      0,
      AssessmentResult.NOT_ASSESSED,
      null, false,
      false, null, null,
      null,
      new Date(), new Date()
    )
  }

  /**
   * 执行考核评估
   */
  assess(params: {
    cumulativeCompleted: number
    localTeamCount: number
    totalTeamCount: number
    requireLocalPercentage: number
    exemptFromPercentageCheck: boolean
  }): void {
    this._cumulativeCompleted = params.cumulativeCompleted
    this._localTeamCount = params.localTeamCount
    this._totalTeamCount = params.totalTeamCount

    // 计算本地占比
    if (params.totalTeamCount > 0) {
      this._localPercentage = (params.localTeamCount / params.totalTeamCount) * 100
    } else {
      this._localPercentage = 0
    }

    // 判断占比是否达标
    this._localPercentagePass = params.exemptFromPercentageCheck ||
                                this._localPercentage >= params.requireLocalPercentage

    // 计算超越比例
    if (this._cumulativeTarget > 0) {
      this._exceedRatio = params.cumulativeCompleted / this._cumulativeTarget
    } else {
      this._exceedRatio = 0
    }

    // 记录达标时间（用于同比例时的排名）
    const cumulativePass = params.cumulativeCompleted >= this._cumulativeTarget
    if (cumulativePass && !this._completedAt) {
      this._completedAt = new Date()
    }

    // 判断考核结果
    if (this._isBypassed) {
      this._result = AssessmentResult.BYPASSED
    } else if (cumulativePass && this._localPercentagePass) {
      this._result = AssessmentResult.PASS
      this.addDomainEvent(new MonthlyAssessmentPassedEvent({
        assessmentId: this._assessmentId.value,
        userId: this._userId.value,
        roleType: this._roleType,
        month: this._assessmentMonth.value,
        cumulativeCompleted: params.cumulativeCompleted,
        cumulativeTarget: this._cumulativeTarget
      }))
    } else {
      this._result = AssessmentResult.FAIL
      this.addDomainEvent(new MonthlyAssessmentFailedEvent({
        assessmentId: this._assessmentId.value,
        userId: this._userId.value,
        roleType: this._roleType,
        month: this._assessmentMonth.value,
        cumulativeCompleted: params.cumulativeCompleted,
        cumulativeTarget: this._cumulativeTarget,
        reason: !cumulativePass ? '累计目标未达成' : '本地占比不足'
      }))
    }

    this._assessedAt = new Date()
    this._updatedAt = new Date()
  }

  /**
   * 授予单月豁免
   */
  grantBypass(adminId: AdminUserId): void {
    if (this._isBypassed) {
      throw new DomainError('已授予豁免')
    }

    this._isBypassed = true
    this._bypassedBy = adminId
    this._bypassedAt = new Date()
    this._result = AssessmentResult.BYPASSED
    this._updatedAt = new Date()

    this.addDomainEvent(new MonthlyBypassGrantedEvent({
      assessmentId: this._assessmentId.value,
      userId: this._userId.value,
      roleType: this._roleType,
      month: this._assessmentMonth.value,
      grantedBy: adminId.value
    }))
  }

  /**
   * 设置排名
   */
  setRanking(rank: number, isFirst: boolean): void {
    this._rankingInRegion = rank
    this._isFirstPlace = isFirst
    this._updatedAt = new Date()

    if (isFirst) {
      this.addDomainEvent(new FirstPlaceAchievedEvent({
        assessmentId: this._assessmentId.value,
        userId: this._userId.value,
        roleType: this._roleType,
        regionCode: this._regionCode.value,
        month: this._assessmentMonth.value
      }))
    }
  }

  /**
   * 是否达标
   */
  isPassed(): boolean {
    return this._result === AssessmentResult.PASS ||
           this._result === AssessmentResult.BYPASSED
  }

  // 私有构造函数
  private constructor(
    assessmentId: AssessmentId,
    authorizationId: AuthorizationId,
    userId: UserId,
    roleType: RoleType,
    regionCode: RegionCode,
    assessmentMonth: Month,
    monthIndex: number,
    monthlyTarget: number,
    cumulativeTarget: number,
    monthlyCompleted: number,
    cumulativeCompleted: number,
    completedAt: Date | null,
    localTeamCount: number,
    totalTeamCount: number,
    localPercentage: number,
    localPercentagePass: boolean,
    exceedRatio: number,
    result: AssessmentResult,
    rankingInRegion: number | null,
    isFirstPlace: boolean,
    isBypassed: boolean,
    bypassedBy: AdminUserId | null,
    bypassedAt: Date | null,
    assessedAt: Date | null,
    createdAt: Date,
    updatedAt: Date
  ) {
    super()
    this._assessmentId = assessmentId
    this._authorizationId = authorizationId
    this._userId = userId
    this._roleType = roleType
    this._regionCode = regionCode
    this._assessmentMonth = assessmentMonth
    this._monthIndex = monthIndex
    this._monthlyTarget = monthlyTarget
    this._cumulativeTarget = cumulativeTarget
    this._monthlyCompleted = monthlyCompleted
    this._cumulativeCompleted = cumulativeCompleted
    this._completedAt = completedAt
    this._localTeamCount = localTeamCount
    this._totalTeamCount = totalTeamCount
    this._localPercentage = localPercentage
    this._localPercentagePass = localPercentagePass
    this._exceedRatio = exceedRatio
    this._result = result
    this._rankingInRegion = rankingInRegion
    this._isFirstPlace = isFirstPlace
    this._isBypassed = isBypassed
    this._bypassedBy = bypassedBy
    this._bypassedAt = bypassedAt
    this._assessedAt = assessedAt
    this._createdAt = createdAt
    this._updatedAt = updatedAt
  }
}
```

### 实体（Entities）

```typescript
// domain/entities/ladder-target-rule.entity.ts
export class LadderTargetRule {
  constructor(
    public readonly roleType: RoleType,
    public readonly monthIndex: number,
    public readonly monthlyTarget: number,
    public readonly cumulativeTarget: number
  ) {}

  // 省代阶梯目标表
  static readonly PROVINCE_LADDER: LadderTargetRule[] = [
    new LadderTargetRule(RoleType.AUTH_PROVINCE_COMPANY, 1, 150, 150),
    new LadderTargetRule(RoleType.AUTH_PROVINCE_COMPANY, 2, 300, 450),
    new LadderTargetRule(RoleType.AUTH_PROVINCE_COMPANY, 3, 600, 1050),
    new LadderTargetRule(RoleType.AUTH_PROVINCE_COMPANY, 4, 1200, 2250),
    new LadderTargetRule(RoleType.AUTH_PROVINCE_COMPANY, 5, 2400, 4650),
    new LadderTargetRule(RoleType.AUTH_PROVINCE_COMPANY, 6, 4800, 9450),
    new LadderTargetRule(RoleType.AUTH_PROVINCE_COMPANY, 7, 9600, 19050),
    new LadderTargetRule(RoleType.AUTH_PROVINCE_COMPANY, 8, 19200, 38250),
    new LadderTargetRule(RoleType.AUTH_PROVINCE_COMPANY, 9, 11750, 50000)
  ]

  // 市代阶梯目标表
  static readonly CITY_LADDER: LadderTargetRule[] = [
    new LadderTargetRule(RoleType.AUTH_CITY_COMPANY, 1, 30, 30),
    new LadderTargetRule(RoleType.AUTH_CITY_COMPANY, 2, 60, 90),
    new LadderTargetRule(RoleType.AUTH_CITY_COMPANY, 3, 120, 210),
    new LadderTargetRule(RoleType.AUTH_CITY_COMPANY, 4, 240, 450),
    new LadderTargetRule(RoleType.AUTH_CITY_COMPANY, 5, 480, 930),
    new LadderTargetRule(RoleType.AUTH_CITY_COMPANY, 6, 960, 1890),
    new LadderTargetRule(RoleType.AUTH_CITY_COMPANY, 7, 1920, 3810),
    new LadderTargetRule(RoleType.AUTH_CITY_COMPANY, 8, 3840, 7650),
    new LadderTargetRule(RoleType.AUTH_CITY_COMPANY, 9, 2350, 10000)
  ]

  // 社区固定目标
  static readonly COMMUNITY_FIXED: LadderTargetRule =
    new LadderTargetRule(RoleType.COMMUNITY, 1, 10, 10)

  /**
   * 获取指定角色和月份的目标
   */
  static getTarget(roleType: RoleType, monthIndex: number): LadderTargetRule {
    switch (roleType) {
      case RoleType.AUTH_PROVINCE_COMPANY:
        // 超过9个月后使用第9个月的目标
        const provinceIndex = Math.min(monthIndex, 9) - 1
        return this.PROVINCE_LADDER[provinceIndex]

      case RoleType.AUTH_CITY_COMPANY:
        const cityIndex = Math.min(monthIndex, 9) - 1
        return this.CITY_LADDER[cityIndex]

      case RoleType.COMMUNITY:
        return this.COMMUNITY_FIXED

      default:
        throw new DomainError(`不支持的角色类型: ${roleType}`)
    }
  }

  /**
   * 获取最终累计目标
   */
  static getFinalTarget(roleType: RoleType): number {
    switch (roleType) {
      case RoleType.AUTH_PROVINCE_COMPANY:
        return 50000
      case RoleType.AUTH_CITY_COMPANY:
        return 10000
      case RoleType.COMMUNITY:
        return 10
      default:
        return 0
    }
  }
}
```

### 领域服务（Domain Services）

```typescript
// domain/services/authorization-validator.service.ts
export class AuthorizationValidatorService {
  /**
   * 验证授权申请（团队内唯一性）
   */
  async validateAuthorizationRequest(
    userId: UserId,
    roleType: RoleType,
    regionCode: RegionCode,
    referralRepository: IReferralRepository,
    authorizationRepository: IAuthorizationRoleRepository
  ): Promise<ValidationResult> {
    // 1. 检查用户是否已有同类型授权
    const existingAuth = await authorizationRepository.findByUserIdAndRoleType(
      userId,
      roleType
    )

    if (existingAuth) {
      return ValidationResult.failure('一个账号只能申请一个省或市的授权')
    }

    // 2. 检查团队内唯一性（上下级不能重复）
    const relationship = await referralRepository.findByUserId(userId)
    if (!relationship) {
      return ValidationResult.success()
    }

    // 检查所有上级
    const ancestors = await referralRepository.getAllAncestors(userId)
    for (const ancestorId of ancestors) {
      const ancestorAuth = await authorizationRepository.findByUserIdRoleTypeAndRegion(
        ancestorId,
        roleType,
        regionCode
      )

      if (ancestorAuth && ancestorAuth.status !== AuthorizationStatus.REVOKED) {
        return ValidationResult.failure(
          `本团队已有人申请该${this.getRoleTypeName(roleType)}授权`
        )
      }
    }

    // 检查所有下级
    const descendants = await referralRepository.getAllDescendants(userId)
    for (const descendantId of descendants) {
      const descendantAuth = await authorizationRepository.findByUserIdRoleTypeAndRegion(
        descendantId,
        roleType,
        regionCode
      )

      if (descendantAuth && descendantAuth.status !== AuthorizationStatus.REVOKED) {
        return ValidationResult.failure(
          `本团队已有人申请该${this.getRoleTypeName(roleType)}授权`
        )
      }
    }

    return ValidationResult.success()
  }

  private getRoleTypeName(roleType: RoleType): string {
    switch (roleType) {
      case RoleType.AUTH_PROVINCE_COMPANY:
        return '省'
      case RoleType.AUTH_CITY_COMPANY:
        return '市'
      default:
        return ''
    }
  }
}

// domain/value-objects/validation-result.vo.ts
export class ValidationResult {
  private constructor(
    public readonly isValid: boolean,
    public readonly errorMessage: string | null
  ) {}

  static success(): ValidationResult {
    return new ValidationResult(true, null)
  }

  static failure(message: string): ValidationResult {
    return new ValidationResult(false, message)
  }
}
```

```typescript
// domain/services/assessment-calculator.service.ts
export class AssessmentCalculatorService {
  /**
   * 计算月度考核
   */
  async calculateMonthlyAssessment(
    authorization: AuthorizationRole,
    assessmentMonth: Month,
    teamStats: TeamStatistics,
    repository: IMonthlyAssessmentRepository
  ): Promise<MonthlyAssessment> {
    // 1. 查找或创建本月考核
    let assessment = await repository.findByAuthorizationAndMonth(
      authorization.authorizationId,
      assessmentMonth
    )

    if (!assessment) {
      // 获取目标
      const monthIndex = authorization.currentMonthIndex || 1
      const target = LadderTargetRule.getTarget(authorization.roleType, monthIndex)

      assessment = MonthlyAssessment.create({
        authorizationId: authorization.authorizationId,
        userId: authorization.userId,
        roleType: authorization.roleType,
        regionCode: authorization.regionCode,
        assessmentMonth,
        monthIndex,
        monthlyTarget: target.monthlyTarget,
        cumulativeTarget: target.cumulativeTarget
      })
    }

    // 2. 执行考核
    const localTeamCount = this.getLocalTeamCount(
      teamStats,
      authorization.roleType,
      authorization.regionCode
    )

    assessment.assess({
      cumulativeCompleted: teamStats.totalTeamPlantingCount,
      localTeamCount,
      totalTeamCount: teamStats.totalTeamPlantingCount,
      requireLocalPercentage: authorization.requireLocalPercentage,
      exemptFromPercentageCheck: authorization.exemptFromPercentageCheck
    })

    return assessment
  }

  /**
   * 批量评估并排名
   */
  async assessAndRankRegion(
    roleType: RoleType,
    regionCode: RegionCode,
    assessmentMonth: Month,
    authorizationRepository: IAuthorizationRoleRepository,
    statsRepository: ITeamStatisticsRepository,
    assessmentRepository: IMonthlyAssessmentRepository
  ): Promise<MonthlyAssessment[]> {
    // 1. 查找该区域的所有激活授权
    const authorizations = await authorizationRepository.findActiveByRoleTypeAndRegion(
      roleType,
      regionCode
    )

    // 2. 计算所有考核
    const assessments: MonthlyAssessment[] = []

    for (const auth of authorizations) {
      const teamStats = await statsRepository.findByUserId(auth.userId)
      if (!teamStats) continue

      const assessment = await this.calculateMonthlyAssessment(
        auth,
        assessmentMonth,
        teamStats,
        assessmentRepository
      )

      assessments.push(assessment)
    }

    // 3. 排名规则：
    //    - 按超越比例降序排列
    //    - 比例相同时，按达标时间升序排列（先完成的排前面）
    assessments.sort((a, b) => {
      // 先按超越比例降序
      if (b.exceedRatio !== a.exceedRatio) {
        return b.exceedRatio - a.exceedRatio
      }
      // 比例相同时按达标时间升序
      if (a.completedAt && b.completedAt) {
        return a.completedAt.getTime() - b.completedAt.getTime()
      }
      // 有达标时间的排前面
      if (a.completedAt) return -1
      if (b.completedAt) return 1
      return 0
    })

    // 4. 设置排名
    assessments.forEach((assessment, index) => {
      assessment.setRanking(index + 1, index === 0)
    })

    return assessments
  }

  private getLocalTeamCount(
    teamStats: TeamStatistics,
    roleType: RoleType,
    regionCode: RegionCode
  ): number {
    if (roleType === RoleType.AUTH_PROVINCE_COMPANY ||
        roleType === RoleType.PROVINCE_COMPANY) {
      return teamStats.getProvinceTeamCount(regionCode.value)
    } else if (roleType === RoleType.AUTH_CITY_COMPANY ||
               roleType === RoleType.CITY_COMPANY) {
      return teamStats.getCityTeamCount(regionCode.value)
    }
    return 0
  }
}
```

```typescript
// domain/services/planting-restriction.service.ts
export class PlantingRestrictionService {
  /**
   * 检查用户是否可以认种
   */
  async canUserPlant(
    userId: UserId,
    treeCount: number,
    restrictionRepository: IPlantingRestrictionRepository,
    userPlantingRepository: IUserPlantingRecordRepository
  ): Promise<RestrictionCheckResult> {
    const now = new Date()

    // 1. 检查账户限时限量
    const accountRestriction = await restrictionRepository.findActiveAccountRestriction()
    if (accountRestriction) {
      const userPlantingCount = await userPlantingRepository.countUserPlantingsInPeriod(
        userId,
        accountRestriction.startAt,
        accountRestriction.endAt
      )

      if (userPlantingCount + treeCount > accountRestriction.accountLimitCount!) {
        return RestrictionCheckResult.blocked(
          `限制期内每个账户只能认种${accountRestriction.accountLimitCount}棵，` +
          `您已认种${userPlantingCount}棵`
        )
      }
    }

    // 2. 检查总量限制
    const totalRestriction = await restrictionRepository.findActiveTotalRestriction()
    if (totalRestriction) {
      if (totalRestriction.currentTotalCount + treeCount > totalRestriction.totalLimitCount!) {
        return RestrictionCheckResult.blocked(
          `系统限制期内总认种量为${totalRestriction.totalLimitCount}棵，` +
          `当前已认种${totalRestriction.currentTotalCount}棵`
        )
      }
    }

    return RestrictionCheckResult.allowed()
  }
}

// domain/value-objects/restriction-check-result.vo.ts
export class RestrictionCheckResult {
  private constructor(
    public readonly allowed: boolean,
    public readonly message: string | null
  ) {}

  static allowed(): RestrictionCheckResult {
    return new RestrictionCheckResult(true, null)
  }

  static blocked(message: string): RestrictionCheckResult {
    return new RestrictionCheckResult(false, message)
  }
}
```

### 仓储接口（Repository Interfaces）

```typescript
// domain/repositories/authorization-role.repository.ts
export interface IAuthorizationRoleRepository {
  save(authorization: AuthorizationRole): Promise<void>
  findById(authorizationId: AuthorizationId): Promise<AuthorizationRole | null>
  findByUserIdAndRoleType(userId: UserId, roleType: RoleType): Promise<AuthorizationRole | null>
  findByUserIdRoleTypeAndRegion(userId: UserId, roleType: RoleType, regionCode: RegionCode): Promise<AuthorizationRole | null>
  findByUserId(userId: UserId): Promise<AuthorizationRole[]>
  findActiveByRoleTypeAndRegion(roleType: RoleType, regionCode: RegionCode): Promise<AuthorizationRole[]>
  findAllActive(roleType?: RoleType): Promise<AuthorizationRole[]>
  findPendingByUserId(userId: UserId): Promise<AuthorizationRole[]>
}

// domain/repositories/monthly-assessment.repository.ts
export interface IMonthlyAssessmentRepository {
  save(assessment: MonthlyAssessment): Promise<void>
  saveAll(assessments: MonthlyAssessment[]): Promise<void>
  findById(assessmentId: AssessmentId): Promise<MonthlyAssessment | null>
  findByAuthorizationAndMonth(authorizationId: AuthorizationId, month: Month): Promise<MonthlyAssessment | null>
  findByUserAndMonth(userId: UserId, month: Month): Promise<MonthlyAssessment[]>
  findFirstByAuthorization(authorizationId: AuthorizationId): Promise<MonthlyAssessment | null>
  findByMonthAndRegion(month: Month, roleType: RoleType, regionCode: RegionCode): Promise<MonthlyAssessment[]>
  findRankingsByMonthAndRegion(month: Month, roleType: RoleType, regionCode: RegionCode): Promise<MonthlyAssessment[]>
}

// domain/repositories/planting-restriction.repository.ts
export interface IPlantingRestrictionRepository {
  save(restriction: PlantingRestriction): Promise<void>
  findActiveAccountRestriction(): Promise<PlantingRestriction | null>
  findActiveTotalRestriction(): Promise<PlantingRestriction | null>
  incrementTotalCount(restrictionId: string, count: number): Promise<void>
}
```

---

## 应用层设计

### 应用服务

```typescript
// application/services/authorization-application.service.ts
@Injectable()
export class AuthorizationApplicationService {
  constructor(
    private readonly authorizationRepository: IAuthorizationRoleRepository,
    private readonly assessmentRepository: IMonthlyAssessmentRepository,
    private readonly referralRepository: IReferralRepository,
    private readonly statsRepository: ITeamStatisticsRepository,
    private readonly validatorService: AuthorizationValidatorService,
    private readonly calculatorService: AssessmentCalculatorService,
    private readonly eventBus: EventBus,
    private readonly unitOfWork: UnitOfWork
  ) {}

  /**
   * 申请社区授权
   */
  async applyCommunityAuth(command: ApplyCommunityAuthCommand): Promise<ApplyCommunityAuthResult> {
    return await this.unitOfWork.execute(async () => {
      const userId = UserId.create(command.userId)

      // 1. 检查是否已有社区授权
      const existing = await this.authorizationRepository.findByUserIdAndRoleType(
        userId,
        RoleType.COMMUNITY
      )

      if (existing) {
        throw new ApplicationError('您已申请过社区授权')
      }

      // 2. 创建社区授权
      const authorization = AuthorizationRole.createCommunityAuth({
        userId,
        communityName: command.communityName
      })

      // 3. 检查初始考核（100棵）
      const teamStats = await this.statsRepository.findByUserId(userId)
      const totalTreeCount = teamStats?.totalTeamPlantingCount || 0

      if (totalTreeCount >= authorization.getInitialTarget()) {
        // 达标，激活权益
        authorization.activateBenefit()
      }

      await this.authorizationRepository.save(authorization)
      await this.eventBus.publishAll(authorization.domainEvents)
      authorization.clearDomainEvents()

      return {
        authorizationId: authorization.authorizationId.value,
        status: authorization.status,
        benefitActive: authorization.benefitActive,
        message: authorization.benefitActive
          ? '社区权益已激活'
          : `需要团队累计认种达到${authorization.getInitialTarget()}棵才能激活`,
        currentTreeCount: totalTreeCount,
        requiredTreeCount: authorization.getInitialTarget()
      }
    })
  }

  /**
   * 申请授权省公司
   */
  async applyAuthProvinceCompany(
    command: ApplyAuthProvinceCompanyCommand
  ): Promise<ApplyAuthProvinceCompanyResult> {
    return await this.unitOfWork.execute(async () => {
      const userId = UserId.create(command.userId)
      const regionCode = RegionCode.create(command.provinceCode)

      // 1. 验证授权申请（团队内唯一性）
      const validation = await this.validatorService.validateAuthorizationRequest(
        userId,
        RoleType.AUTH_PROVINCE_COMPANY,
        regionCode,
        this.referralRepository,
        this.authorizationRepository
      )

      if (!validation.isValid) {
        throw new ApplicationError(validation.errorMessage!)
      }

      // 2. 创建授权
      const authorization = AuthorizationRole.createAuthProvinceCompany({
        userId,
        provinceCode: command.provinceCode,
        provinceName: command.provinceName
      })

      // 3. 检查初始考核（500棵）
      const teamStats = await this.statsRepository.findByUserId(userId)
      const totalTreeCount = teamStats?.totalTeamPlantingCount || 0

      if (totalTreeCount >= authorization.getInitialTarget()) {
        // 达标，激活权益并创建首月考核
        authorization.activateBenefit()
        await this.createInitialAssessment(authorization, teamStats!)
      }

      await this.authorizationRepository.save(authorization)
      await this.eventBus.publishAll(authorization.domainEvents)
      authorization.clearDomainEvents()

      return {
        authorizationId: authorization.authorizationId.value,
        status: authorization.status,
        benefitActive: authorization.benefitActive,
        displayTitle: authorization.displayTitle,
        message: authorization.benefitActive
          ? '授权省公司权益已激活，开始阶梯考核'
          : `需要团队累计认种达到${authorization.getInitialTarget()}棵才能激活`,
        currentTreeCount: totalTreeCount,
        requiredTreeCount: authorization.getInitialTarget()
      }
    })
  }

  /**
   * 申请授权市公司
   */
  async applyAuthCityCompany(
    command: ApplyAuthCityCompanyCommand
  ): Promise<ApplyAuthCityCompanyResult> {
    return await this.unitOfWork.execute(async () => {
      const userId = UserId.create(command.userId)
      const regionCode = RegionCode.create(command.cityCode)

      // 1. 验证
      const validation = await this.validatorService.validateAuthorizationRequest(
        userId,
        RoleType.AUTH_CITY_COMPANY,
        regionCode,
        this.referralRepository,
        this.authorizationRepository
      )

      if (!validation.isValid) {
        throw new ApplicationError(validation.errorMessage!)
      }

      // 2. 创建授权
      const authorization = AuthorizationRole.createAuthCityCompany({
        userId,
        cityCode: command.cityCode,
        cityName: command.cityName
      })

      // 3. 检查初始考核（500棵）
      const teamStats = await this.statsRepository.findByUserId(userId)
      const totalTreeCount = teamStats?.totalTeamPlantingCount || 0

      if (totalTreeCount >= authorization.getInitialTarget()) {
        authorization.activateBenefit()
        await this.createInitialAssessment(authorization, teamStats!)
      }

      await this.authorizationRepository.save(authorization)
      await this.eventBus.publishAll(authorization.domainEvents)
      authorization.clearDomainEvents()

      return {
        authorizationId: authorization.authorizationId.value,
        status: authorization.status,
        benefitActive: authorization.benefitActive,
        displayTitle: authorization.displayTitle,
        message: authorization.benefitActive
          ? '授权市公司权益已激活，开始阶梯考核'
          : `需要团队累计认种达到${authorization.getInitialTarget()}棵才能激活`,
        currentTreeCount: totalTreeCount,
        requiredTreeCount: authorization.getInitialTarget()
      }
    })
  }

  /**
   * 管理员授权正式省公司
   */
  async grantProvinceCompany(
    command: GrantProvinceCompanyCommand
  ): Promise<void> {
    return await this.unitOfWork.execute(async () => {
      const userId = UserId.create(command.userId)
      const adminId = AdminUserId.create(command.adminId)

      const authorization = AuthorizationRole.createProvinceCompany({
        userId,
        provinceCode: command.provinceCode,
        provinceName: command.provinceName,
        adminId
      })

      await this.authorizationRepository.save(authorization)
      await this.eventBus.publishAll(authorization.domainEvents)
      authorization.clearDomainEvents()
    })
  }

  /**
   * 管理员授权正式市公司
   */
  async grantCityCompany(
    command: GrantCityCompanyCommand
  ): Promise<void> {
    return await this.unitOfWork.execute(async () => {
      const userId = UserId.create(command.userId)
      const adminId = AdminUserId.create(command.adminId)

      const authorization = AuthorizationRole.createCityCompany({
        userId,
        cityCode: command.cityCode,
        cityName: command.cityName,
        adminId
      })

      await this.authorizationRepository.save(authorization)
      await this.eventBus.publishAll(authorization.domainEvents)
      authorization.clearDomainEvents()
    })
  }

  /**
   * 撤销授权
   */
  async revokeAuthorization(command: RevokeAuthorizationCommand): Promise<void> {
    return await this.unitOfWork.execute(async () => {
      const authorization = await this.authorizationRepository.findById(
        AuthorizationId.create(command.authorizationId)
      )

      if (!authorization) {
        throw new ApplicationError('授权不存在')
      }

      authorization.revoke(
        AdminUserId.create(command.adminId),
        command.reason
      )

      await this.authorizationRepository.save(authorization)
      await this.eventBus.publishAll(authorization.domainEvents)
      authorization.clearDomainEvents()
    })
  }

  /**
   * 授予单月豁免
   */
  async grantMonthlyBypass(command: GrantMonthlyBypassCommand): Promise<void> {
    return await this.unitOfWork.execute(async () => {
      const assessment = await this.assessmentRepository.findByAuthorizationAndMonth(
        AuthorizationId.create(command.authorizationId),
        Month.create(command.month)
      )

      if (!assessment) {
        throw new ApplicationError('考核记录不存在')
      }

      assessment.grantBypass(AdminUserId.create(command.adminId))

      await this.assessmentRepository.save(assessment)
      await this.eventBus.publishAll(assessment.domainEvents)
      assessment.clearDomainEvents()
    })
  }

  /**
   * 豁免占比考核
   */
  async exemptLocalPercentageCheck(
    command: ExemptLocalPercentageCheckCommand
  ): Promise<void> {
    return await this.unitOfWork.execute(async () => {
      const authorization = await this.authorizationRepository.findById(
        AuthorizationId.create(command.authorizationId)
      )

      if (!authorization) {
        throw new ApplicationError('授权不存在')
      }

      authorization.exemptLocalPercentageCheck(
        AdminUserId.create(command.adminId)
      )

      await this.authorizationRepository.save(authorization)
      await this.eventBus.publishAll(authorization.domainEvents)
      authorization.clearDomainEvents()
    })
  }

  /**
   * 查询用户授权列表
   */
  async getUserAuthorizations(query: GetUserAuthorizationsQuery): Promise<AuthorizationDTO[]> {
    const authorizations = await this.authorizationRepository.findByUserId(
      UserId.create(query.userId)
    )

    return authorizations.map(auth => this.toAuthorizationDTO(auth))
  }

  /**
   * 查询火柴人排名数据
   */
  async getStickmanRanking(query: GetStickmanRankingQuery): Promise<StickmanRankingDTO[]> {
    const assessments = await this.assessmentRepository.findRankingsByMonthAndRegion(
      Month.create(query.month),
      query.roleType,
      RegionCode.create(query.regionCode)
    )

    // 获取用户信息并组装火柴人数据
    const rankings: StickmanRankingDTO[] = []

    for (const assessment of assessments) {
      const userInfo = await this.getUserInfo(assessment.userId)
      const finalTarget = LadderTargetRule.getFinalTarget(assessment.roleType)

      rankings.push({
        userId: assessment.userId.value,
        nickname: userInfo.nickname,
        avatarUrl: userInfo.avatarUrl,
        ranking: assessment.rankingInRegion!,
        isFirstPlace: assessment.isFirstPlace,
        cumulativeCompleted: assessment.cumulativeCompleted,
        cumulativeTarget: assessment.cumulativeTarget,
        finalTarget,
        progressPercentage: (assessment.cumulativeCompleted / finalTarget) * 100,
        exceedRatio: assessment.exceedRatio,
        monthlyRewardUsdt: await this.calculateMonthlyReward(assessment),
        monthlyRewardRwad: 0 // 需要从其他服务获取
      })
    }

    return rankings
  }

  // 辅助方法
  private async createInitialAssessment(
    authorization: AuthorizationRole,
    teamStats: TeamStatistics
  ): Promise<void> {
    const currentMonth = Month.current()
    const target = LadderTargetRule.getTarget(authorization.roleType, 1)

    const assessment = MonthlyAssessment.create({
      authorizationId: authorization.authorizationId,
      userId: authorization.userId,
      roleType: authorization.roleType,
      regionCode: authorization.regionCode,
      assessmentMonth: currentMonth,
      monthIndex: 1,
      monthlyTarget: target.monthlyTarget,
      cumulativeTarget: target.cumulativeTarget
    })

    // 立即评估首月
    const localTeamCount = this.getLocalTeamCount(
      teamStats,
      authorization.roleType,
      authorization.regionCode
    )

    assessment.assess({
      cumulativeCompleted: teamStats.totalTeamPlantingCount,
      localTeamCount,
      totalTeamCount: teamStats.totalTeamPlantingCount,
      requireLocalPercentage: authorization.requireLocalPercentage,
      exemptFromPercentageCheck: authorization.exemptFromPercentageCheck
    })

    await this.assessmentRepository.save(assessment)
    await this.eventBus.publishAll(assessment.domainEvents)
    assessment.clearDomainEvents()
  }

  private getLocalTeamCount(
    teamStats: TeamStatistics,
    roleType: RoleType,
    regionCode: RegionCode
  ): number {
    if (roleType === RoleType.AUTH_PROVINCE_COMPANY) {
      return teamStats.getProvinceTeamCount(regionCode.value)
    } else if (roleType === RoleType.AUTH_CITY_COMPANY) {
      return teamStats.getCityTeamCount(regionCode.value)
    }
    return 0
  }

  private toAuthorizationDTO(auth: AuthorizationRole): AuthorizationDTO {
    return {
      authorizationId: auth.authorizationId.value,
      userId: auth.userId.value,
      roleType: auth.roleType,
      regionCode: auth.regionCode.value,
      regionName: auth.regionName,
      status: auth.status,
      displayTitle: auth.displayTitle,
      benefitActive: auth.benefitActive,
      currentMonthIndex: auth.currentMonthIndex,
      requireLocalPercentage: auth.requireLocalPercentage,
      exemptFromPercentageCheck: auth.exemptFromPercentageCheck
    }
  }
}
```

### 定时任务

```typescript
// application/schedulers/monthly-assessment.scheduler.ts
@Injectable()
export class MonthlyAssessmentScheduler {
  constructor(
    private readonly authorizationRepository: IAuthorizationRoleRepository,
    private readonly assessmentRepository: IMonthlyAssessmentRepository,
    private readonly statsRepository: ITeamStatisticsRepository,
    private readonly calculatorService: AssessmentCalculatorService,
    private readonly eventBus: EventBus,
    private readonly unitOfWork: UnitOfWork,
    private readonly logger: Logger
  ) {}

  /**
   * 每月1号凌晨2点执行月度考核
   */
  @Cron('0 2 1 * *')
  async executeMonthlyAssessment(): Promise<void> {
    this.logger.log('开始执行月度考核...')

    const previousMonth = Month.current().previous()

    try {
      await this.unitOfWork.execute(async () => {
        // 1. 获取所有激活的授权
        const activeAuths = await this.authorizationRepository.findAllActive()

        // 2. 按角色类型和区域分组处理
        const groupedByRoleAndRegion = this.groupByRoleAndRegion(activeAuths)

        for (const [key, auths] of groupedByRoleAndRegion) {
          const [roleType, regionCode] = key.split('|')

          // 跳过正式省市公司（无月度考核）
          if (roleType === RoleType.PROVINCE_COMPANY ||
              roleType === RoleType.CITY_COMPANY) {
            continue
          }

          // 执行考核并排名
          const assessments = await this.calculatorService.assessAndRankRegion(
            roleType as RoleType,
            RegionCode.create(regionCode),
            previousMonth,
            this.authorizationRepository,
            this.statsRepository,
            this.assessmentRepository
          )

          // 保存考核结果
          await this.assessmentRepository.saveAll(assessments)

          // 处理不达标的授权
          for (const assessment of assessments) {
            if (assessment.result === AssessmentResult.FAIL) {
              const auth = auths.find(a =>
                a.authorizationId.equals(assessment.authorizationId)
              )

              if (auth) {
                // 权益失效
                auth.deactivateBenefit('月度考核不达标')
                await this.authorizationRepository.save(auth)

                await this.eventBus.publishAll(auth.domainEvents)
                auth.clearDomainEvents()
              }
            } else if (assessment.isPassed()) {
              // 达标，递增月份索引
              const auth = auths.find(a =>
                a.authorizationId.equals(assessment.authorizationId)
              )

              if (auth) {
                auth.incrementMonthIndex()
                await this.authorizationRepository.save(auth)
              }
            }

            await this.eventBus.publishAll(assessment.domainEvents)
            assessment.clearDomainEvents()
          }
        }
      })

      this.logger.log('月度考核执行完成')
    } catch (error) {
      this.logger.error('月度考核执行失败', error)
      throw error
    }
  }

  /**
   * 每天凌晨1点更新火柴人排名数据
   */
  @Cron('0 1 * * *')
  async updateStickmanRankings(): Promise<void> {
    this.logger.log('开始更新火柴人排名数据...')

    const currentMonth = Month.current()

    try {
      // 获取所有激活的授权省/市公司
      const activeAuths = await this.authorizationRepository.findAllActive()

      const provinceAuths = activeAuths.filter(
        a => a.roleType === RoleType.AUTH_PROVINCE_COMPANY
      )
      const cityAuths = activeAuths.filter(
        a => a.roleType === RoleType.AUTH_CITY_COMPANY
      )

      // 按区域分组并更新排名
      // ... 实现排名更新逻辑

      this.logger.log('火柴人排名数据更新完成')
    } catch (error) {
      this.logger.error('火柴人排名数据更新失败', error)
    }
  }

  private groupByRoleAndRegion(
    authorizations: AuthorizationRole[]
  ): Map<string, AuthorizationRole[]> {
    const map = new Map<string, AuthorizationRole[]>()

    for (const auth of authorizations) {
      const key = `${auth.roleType}|${auth.regionCode.value}`
      if (!map.has(key)) {
        map.set(key, [])
      }
      map.get(key)!.push(auth)
    }

    return map
  }
}
```

---

## API端点设计

### 用户端 API

```typescript
// presentation/controllers/authorization.controller.ts
@Controller('api/v1/authorizations')
@UseGuards(JwtAuthGuard)
export class AuthorizationController {
  constructor(
    private readonly authorizationService: AuthorizationApplicationService
  ) {}

  /**
   * 申请社区授权
   */
  @Post('community')
  async applyCommunityAuth(
    @CurrentUser() user: UserPayload,
    @Body() dto: ApplyCommunityAuthDTO
  ): Promise<ApiResponse<ApplyCommunityAuthResult>> {
    const result = await this.authorizationService.applyCommunityAuth({
      userId: user.userId,
      communityName: dto.communityName
    })
    return ApiResponse.success(result)
  }

  /**
   * 申请授权省公司
   */
  @Post('auth-province-company')
  async applyAuthProvinceCompany(
    @CurrentUser() user: UserPayload,
    @Body() dto: ApplyAuthProvinceCompanyDTO
  ): Promise<ApiResponse<ApplyAuthProvinceCompanyResult>> {
    const result = await this.authorizationService.applyAuthProvinceCompany({
      userId: user.userId,
      provinceCode: dto.provinceCode,
      provinceName: dto.provinceName
    })
    return ApiResponse.success(result)
  }

  /**
   * 申请授权市公司
   */
  @Post('auth-city-company')
  async applyAuthCityCompany(
    @CurrentUser() user: UserPayload,
    @Body() dto: ApplyAuthCityCompanyDTO
  ): Promise<ApiResponse<ApplyAuthCityCompanyResult>> {
    const result = await this.authorizationService.applyAuthCityCompany({
      userId: user.userId,
      cityCode: dto.cityCode,
      cityName: dto.cityName
    })
    return ApiResponse.success(result)
  }

  /**
   * 获取我的授权列表
   */
  @Get('my')
  async getMyAuthorizations(
    @CurrentUser() user: UserPayload
  ): Promise<ApiResponse<AuthorizationDTO[]>> {
    const result = await this.authorizationService.getUserAuthorizations({
      userId: user.userId
    })
    return ApiResponse.success(result)
  }

  /**
   * 获取我的考核记录
   */
  @Get('my/assessments')
  async getMyAssessments(
    @CurrentUser() user: UserPayload,
    @Query() query: GetAssessmentsQueryDTO
  ): Promise<ApiResponse<AssessmentDTO[]>> {
    const result = await this.authorizationService.getUserAssessments({
      userId: user.userId,
      page: query.page || 1,
      pageSize: query.pageSize || 20
    })
    return ApiResponse.success(result)
  }

  /**
   * 获取火柴人排名（省公司）
   */
  @Get('stickman-ranking/province/:provinceCode')
  async getProvinceStickmanRanking(
    @Param('provinceCode') provinceCode: string,
    @Query('month') month?: string
  ): Promise<ApiResponse<StickmanRankingDTO[]>> {
    const result = await this.authorizationService.getStickmanRanking({
      roleType: RoleType.AUTH_PROVINCE_COMPANY,
      regionCode: provinceCode,
      month: month || Month.current().value
    })
    return ApiResponse.success(result)
  }

  /**
   * 获取火柴人排名（市公司）
   */
  @Get('stickman-ranking/city/:cityCode')
  async getCityStickmanRanking(
    @Param('cityCode') cityCode: string,
    @Query('month') month?: string
  ): Promise<ApiResponse<StickmanRankingDTO[]>> {
    const result = await this.authorizationService.getStickmanRanking({
      roleType: RoleType.AUTH_CITY_COMPANY,
      regionCode: cityCode,
      month: month || Month.current().value
    })
    return ApiResponse.success(result)
  }

  /**
   * 获取省市认种热度（可选是否允许查看）
   */
  @Get('region-heat')
  async getRegionHeat(
    @Query() query: GetRegionHeatQueryDTO
  ): Promise<ApiResponse<RegionHeatDTO[]>> {
    const result = await this.authorizationService.getRegionHeat({
      regionType: query.regionType,
      parentCode: query.parentCode
    })
    return ApiResponse.success(result)
  }
}
```

### 管理端 API

```typescript
// presentation/controllers/admin-authorization.controller.ts
@Controller('api/v1/admin/authorizations')
@UseGuards(JwtAuthGuard, AdminGuard)
export class AdminAuthorizationController {
  constructor(
    private readonly authorizationService: AuthorizationApplicationService,
    private readonly approvalService: AdminApprovalService
  ) {}

  /**
   * 授权正式省公司
   */
  @Post('province-company')
  @RequireApproval(3) // 需要3人审批
  async grantProvinceCompany(
    @CurrentAdmin() admin: AdminPayload,
    @Body() dto: GrantProvinceCompanyDTO
  ): Promise<ApiResponse<void>> {
    await this.authorizationService.grantProvinceCompany({
      userId: dto.userId,
      provinceCode: dto.provinceCode,
      provinceName: dto.provinceName,
      adminId: admin.adminId
    })
    return ApiResponse.success()
  }

  /**
   * 授权正式市公司
   */
  @Post('city-company')
  @RequireApproval(3)
  async grantCityCompany(
    @CurrentAdmin() admin: AdminPayload,
    @Body() dto: GrantCityCompanyDTO
  ): Promise<ApiResponse<void>> {
    await this.authorizationService.grantCityCompany({
      userId: dto.userId,
      cityCode: dto.cityCode,
      cityName: dto.cityName,
      adminId: admin.adminId
    })
    return ApiResponse.success()
  }

  /**
   * 撤销授权
   */
  @Delete(':authorizationId')
  @RequireApproval(3)
  async revokeAuthorization(
    @CurrentAdmin() admin: AdminPayload,
    @Param('authorizationId') authorizationId: string,
    @Body() dto: RevokeAuthorizationDTO
  ): Promise<ApiResponse<void>> {
    await this.authorizationService.revokeAuthorization({
      authorizationId,
      adminId: admin.adminId,
      reason: dto.reason
    })
    return ApiResponse.success()
  }

  /**
   * 授予单月豁免
   */
  @Post(':authorizationId/bypass')
  @RequireApproval(3)
  async grantMonthlyBypass(
    @CurrentAdmin() admin: AdminPayload,
    @Param('authorizationId') authorizationId: string,
    @Body() dto: GrantBypassDTO
  ): Promise<ApiResponse<void>> {
    await this.authorizationService.grantMonthlyBypass({
      authorizationId,
      month: dto.month,
      adminId: admin.adminId,
      reason: dto.reason
    })
    return ApiResponse.success()
  }

  /**
   * 豁免占比考核
   */
  @Post(':authorizationId/exempt-percentage')
  @RequireApproval(3)
  async exemptLocalPercentageCheck(
    @CurrentAdmin() admin: AdminPayload,
    @Param('authorizationId') authorizationId: string
  ): Promise<ApiResponse<void>> {
    await this.authorizationService.exemptLocalPercentageCheck({
      authorizationId,
      adminId: admin.adminId
    })
    return ApiResponse.success()
  }

  /**
   * 获取授权列表
   */
  @Get()
  async getAuthorizations(
    @Query() query: GetAuthorizationsQueryDTO
  ): Promise<ApiResponse<PaginatedResult<AuthorizationDTO>>> {
    const result = await this.authorizationService.getAuthorizationsList(query)
    return ApiResponse.success(result)
  }

  /**
   * 获取考核列表
   */
  @Get('assessments')
  async getAssessments(
    @Query() query: GetAssessmentsQueryDTO
  ): Promise<ApiResponse<PaginatedResult<AssessmentDTO>>> {
    const result = await this.authorizationService.getAssessmentsList(query)
    return ApiResponse.success(result)
  }

  /**
   * 设置认种限制
   */
  @Post('planting-restrictions')
  @RequireApproval(3)
  async setPlantingRestriction(
    @CurrentAdmin() admin: AdminPayload,
    @Body() dto: SetPlantingRestrictionDTO
  ): Promise<ApiResponse<void>> {
    await this.authorizationService.setPlantingRestriction({
      ...dto,
      adminId: admin.adminId
    })
    return ApiResponse.success()
  }

  /**
   * 获取审批列表
   */
  @Get('approvals')
  async getApprovals(
    @Query() query: GetApprovalsQueryDTO
  ): Promise<ApiResponse<PaginatedResult<ApprovalDTO>>> {
    const result = await this.approvalService.getApprovalsList(query)
    return ApiResponse.success(result)
  }

  /**
   * 审批操作
   */
  @Post('approvals/:approvalId/approve')
  async approveOperation(
    @CurrentAdmin() admin: AdminPayload,
    @Param('approvalId') approvalId: string
  ): Promise<ApiResponse<void>> {
    await this.approvalService.approve({
      approvalId,
      adminId: admin.adminId
    })
    return ApiResponse.success()
  }

  /**
   * 拒绝操作
   */
  @Post('approvals/:approvalId/reject')
  async rejectOperation(
    @CurrentAdmin() admin: AdminPayload,
    @Param('approvalId') approvalId: string,
    @Body() dto: RejectApprovalDTO
  ): Promise<ApiResponse<void>> {
    await this.approvalService.reject({
      approvalId,
      adminId: admin.adminId,
      reason: dto.reason
    })
    return ApiResponse.success()
  }
}
```

---

## 领域事件

```typescript
// domain/events/authorization.events.ts

// 社区授权申请事件
export class CommunityAuthRequestedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    communityName: string
  }) {
    super()
  }
}

// 授权省公司申请事件
export class AuthProvinceCompanyRequestedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    provinceCode: string
    provinceName: string
  }) {
    super()
  }
}

// 授权市公司申请事件
export class AuthCityCompanyRequestedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    cityCode: string
    cityName: string
  }) {
    super()
  }
}

// 正式省公司授权事件
export class ProvinceCompanyAuthorizedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    provinceCode: string
    provinceName: string
    authorizedBy: string
  }) {
    super()
  }
}

// 正式市公司授权事件
export class CityCompanyAuthorizedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    cityCode: string
    cityName: string
    authorizedBy: string
  }) {
    super()
  }
}

// 角色授权事件
export class RoleAuthorizedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    roleType: RoleType
    regionCode: string
    authorizedBy: string
  }) {
    super()
  }
}

// 角色撤销事件
export class RoleRevokedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    roleType: RoleType
    regionCode: string
    reason: string
    revokedBy: string
  }) {
    super()
  }
}

// 权益激活事件
export class BenefitActivatedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    roleType: RoleType
    regionCode: string
  }) {
    super()
  }
}

// 权益失效事件
export class BenefitDeactivatedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    roleType: RoleType
    reason: string
  }) {
    super()
  }
}

// 月度考核通过事件
export class MonthlyAssessmentPassedEvent extends DomainEvent {
  constructor(public readonly payload: {
    assessmentId: string
    userId: string
    roleType: RoleType
    month: string
    cumulativeCompleted: number
    cumulativeTarget: number
  }) {
    super()
  }
}

// 月度考核失败事件
export class MonthlyAssessmentFailedEvent extends DomainEvent {
  constructor(public readonly payload: {
    assessmentId: string
    userId: string
    roleType: RoleType
    month: string
    cumulativeCompleted: number
    cumulativeTarget: number
    reason: string
  }) {
    super()
  }
}

// 单月豁免授予事件
export class MonthlyBypassGrantedEvent extends DomainEvent {
  constructor(public readonly payload: {
    assessmentId: string
    userId: string
    roleType: RoleType
    month: string
    grantedBy: string
  }) {
    super()
  }
}

// 占比考核豁免事件
export class PercentageCheckExemptedEvent extends DomainEvent {
  constructor(public readonly payload: {
    authorizationId: string
    userId: string
    exemptedBy: string
  }) {
    super()
  }
}

// 第一名达成事件
export class FirstPlaceAchievedEvent extends DomainEvent {
  constructor(public readonly payload: {
    assessmentId: string
    userId: string
    roleType: RoleType
    regionCode: string
    month: string
  }) {
    super()
  }
}
```

---

## Kafka事件主题

```typescript
// 发布的事件主题
export const AUTHORIZATION_TOPICS = {
  // 授权相关
  AUTHORIZATION_REQUESTED: 'authorization.requested',
  AUTHORIZATION_GRANTED: 'authorization.granted',
  AUTHORIZATION_REVOKED: 'authorization.revoked',

  // 权益相关
  BENEFIT_ACTIVATED: 'authorization.benefit.activated',
  BENEFIT_DEACTIVATED: 'authorization.benefit.deactivated',

  // 考核相关
  ASSESSMENT_PASSED: 'authorization.assessment.passed',
  ASSESSMENT_FAILED: 'authorization.assessment.failed',
  ASSESSMENT_BYPASSED: 'authorization.assessment.bypassed',

  // 排名相关
  FIRST_PLACE_ACHIEVED: 'authorization.ranking.first-place'
}

// 订阅的事件主题
export const SUBSCRIBED_TOPICS = {
  // 从Planting Context订阅
  PLANTING_ORDER_PAID: 'planting.order.paid',

  // 从Referral Context订阅
  TEAM_STATS_UPDATED: 'referral.team-stats.updated'
}
```

---

## 跨上下文集成

### 与Referral Context集成
```typescript
// 获取团队统计数据
interface TeamStatistics {
  userId: string
  totalTeamPlantingCount: number
  directReferralCount: number
  teamMemberCount: number
  provinceTeamCounts: Map<string, number>  // 各省团队认种数
  cityTeamCounts: Map<string, number>      // 各市团队认种数

  getProvinceTeamCount(provinceCode: string): number
  getCityTeamCount(cityCode: string): number
}
```

### 与Planting Context集成
```typescript
// 监听认种事件，检查是否激活权益
@EventHandler(PlantingOrderPaidEvent)
async handlePlantingOrderPaid(event: PlantingOrderPaidEvent): Promise<void> {
  // 检查用户是否有待激活的授权
  const pendingAuths = await this.authorizationRepository.findPendingByUserId(
    UserId.create(event.userId)
  )

  for (const auth of pendingAuths) {
    // 检查是否达到初始考核目标
    const teamStats = await this.statsRepository.findByUserId(auth.userId)
    if (teamStats && teamStats.totalTeamPlantingCount >= auth.getInitialTarget()) {
      auth.activateBenefit()
      await this.authorizationRepository.save(auth)
      // 发布权益激活事件
    }
  }
}
```

### 与Reward Context集成
```typescript
// 发布权益激活/失效事件，通知Reward Context调整奖励计算
// BenefitActivatedEvent -> Reward Context开始计算该用户的权益收益
// BenefitDeactivatedEvent -> Reward Context停止计算该用户的权益收益
```

### 与Identity Context集成
```typescript
// 获取用户信息用于显示
interface UserInfo {
  userId: string
  nickname: string
  avatarUrl: string
  provinceCode: string
  cityCode: string
}

// 发布授权事件，更新用户头像显示标识
// RoleAuthorizedEvent -> Identity Context更新用户displayTitle
// RoleRevokedEvent -> Identity Context清除用户displayTitle
```

---

## 目录结构

```
authorization-service/
├── src/
│   ├── domain/
│   │   ├── aggregates/
│   │   │   ├── authorization-role.aggregate.ts
│   │   │   └── monthly-assessment.aggregate.ts
│   │   ├── entities/
│   │   │   ├── ladder-target-rule.entity.ts
│   │   │   ├── planting-restriction.entity.ts
│   │   │   └── admin-approval.entity.ts
│   │   ├── value-objects/
│   │   │   ├── authorization-id.vo.ts
│   │   │   ├── assessment-id.vo.ts
│   │   │   ├── region-code.vo.ts
│   │   │   ├── month.vo.ts
│   │   │   ├── assessment-config.vo.ts
│   │   │   ├── benefit-amount.vo.ts
│   │   │   ├── validation-result.vo.ts
│   │   │   └── restriction-check-result.vo.ts
│   │   ├── services/
│   │   │   ├── authorization-validator.service.ts
│   │   │   ├── assessment-calculator.service.ts
│   │   │   └── planting-restriction.service.ts
│   │   ├── repositories/
│   │   │   ├── authorization-role.repository.ts
│   │   │   ├── monthly-assessment.repository.ts
│   │   │   ├── planting-restriction.repository.ts
│   │   │   └── admin-approval.repository.ts
│   │   ├── events/
│   │   │   └── authorization.events.ts
│   │   └── errors/
│   │       └── domain-errors.ts
│   │
│   ├── application/
│   │   ├── services/
│   │   │   ├── authorization-application.service.ts
│   │   │   └── admin-approval.service.ts
│   │   ├── commands/
│   │   │   ├── apply-community-auth.command.ts
│   │   │   ├── apply-auth-province-company.command.ts
│   │   │   ├── apply-auth-city-company.command.ts
│   │   │   ├── grant-province-company.command.ts
│   │   │   ├── grant-city-company.command.ts
│   │   │   ├── revoke-authorization.command.ts
│   │   │   ├── grant-monthly-bypass.command.ts
│   │   │   └── exempt-percentage-check.command.ts
│   │   ├── queries/
│   │   │   ├── get-user-authorizations.query.ts
│   │   │   ├── get-stickman-ranking.query.ts
│   │   │   └── get-region-heat.query.ts
│   │   ├── dtos/
│   │   │   ├── authorization.dto.ts
│   │   │   ├── assessment.dto.ts
│   │   │   └── stickman-ranking.dto.ts
│   │   ├── schedulers/
│   │   │   └── monthly-assessment.scheduler.ts
│   │   └── event-handlers/
│   │       ├── planting-order-paid.handler.ts
│   │       └── team-stats-updated.handler.ts
│   │
│   ├── infrastructure/
│   │   ├── persistence/
│   │   │   ├── prisma/
│   │   │   │   └── schema.prisma
│   │   │   ├── repositories/
│   │   │   │   ├── prisma-authorization-role.repository.ts
│   │   │   │   ├── prisma-monthly-assessment.repository.ts
│   │   │   │   └── prisma-admin-approval.repository.ts
│   │   │   └── mappers/
│   │   │       ├── authorization-role.mapper.ts
│   │   │       └── monthly-assessment.mapper.ts
│   │   ├── messaging/
│   │   │   ├── kafka/
│   │   │   │   ├── kafka.module.ts
│   │   │   │   ├── kafka-producer.service.ts
│   │   │   │   └── kafka-consumer.service.ts
│   │   │   └── event-bus.ts
│   │   ├── cache/
│   │   │   └── redis-cache.service.ts
│   │   └── external/
│   │       ├── referral-context.client.ts
│   │       └── identity-context.client.ts
│   │
│   ├── presentation/
│   │   ├── controllers/
│   │   │   ├── authorization.controller.ts
│   │   │   └── admin-authorization.controller.ts
│   │   ├── guards/
│   │   │   ├── jwt-auth.guard.ts
│   │   │   └── admin.guard.ts
│   │   ├── decorators/
│   │   │   ├── current-user.decorator.ts
│   │   │   ├── current-admin.decorator.ts
│   │   │   └── require-approval.decorator.ts
│   │   └── interceptors/
│   │       └── approval.interceptor.ts
│   │
│   ├── app.module.ts
│   └── main.ts
│
├── test/
│   ├── unit/
│   │   ├── domain/
│   │   └── application/
│   └── integration/
│
├── prisma/
│   ├── schema.prisma
│   └── migrations/
│
├── Dockerfile
├── package.json
├── tsconfig.json
└── DEVELOPMENT_GUIDE.md
```

---

## 注意事项

1. **阶梯考核逻辑**：豁免当月后，下月继续考核上月的累计目标，月份索引不递增
2. **团队唯一性**：必须检查上下级整条推荐链，确保同一省/市授权在团队内唯一
3. **占比考核**：自有团队占比5%是参与评选第一名的前提条件，可豁免
4. **排名规则**：超越比例相同时，以达标时间先后排序
5. **三人审批**：涉及数据修改或授权的管理操作需要三个管理员审批通过
6. **权益失效**：月度考核不达标后权益失效，需重新完成初始考核才能重新激活
7. **热度数据**：可通过配置控制是否允许未认种用户查看各省市认种热度
