# Модульная система лицензирования

## Архитектура модулей

```
Таблица: club_modules
┌──────────┬───────────┬─────────┬──────────────────────────────┬─────────────────────┐
│ club_id  │ module_id │ enabled │ limits_json                  │ usage_json          │
├──────────┼───────────┼─────────┼──────────────────────────────┼─────────────────────┤
│ uuid-a   │ sip       │ true    │ {"max_minutes": 1000}        │ {"used_minutes": 42}│
│ uuid-a   │ webhooks  │ true    │ {"max_events": 10000}        │ {"sent": 3200}      │
│ uuid-a   │ recording │ false   │ null                         │ null                │
└──────────┴───────────┴─────────┴──────────────────────────────┴─────────────────────┘
```

## Backend: NestJS Guard + Decorator

```typescript
@RequireModule('sip')
@Post('/v1/sip/call')
async initiateCall(@Body() dto: InitiateCallDto) { ... }

@Injectable()
export class ModuleGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const requiredModule = this.reflector.get('module', context.getHandler());
    const clubId = context.switchToHttp().getRequest().user.clubId;
    return this.moduleService.isEnabled(clubId, requiredModule);
  }
}
```

## Frontend: хук `useModuleAccess`

```typescript
const { isEnabled, limits } = useModuleAccess('sip');
if (!isEnabled) return null;
```

## Перечень модулей

| ID | Модуль | Стартовая | Стандартная | Премиум | Тарификация |
|----|--------|-----------|------------|---------|-------------|
| core | Ядро CRM | + | + | + | Включено |
| sales | Продажи и реализация | + | + | + | Включено |
| coordinator | Координатор | + | + | + | Включено |
| push | Push-уведомления | + | + | + | Лимит/мес |
| web_reception | Веб: Рецепция | + | + | + | Включено |
| web_reports | Веб: Отчёты | — | + | + | +доплата |
| sip | SIP-телефония | — | + | + | +доплата/мес |
| sip_recording | Запись звонков | — | — | + | +доплата/ГБ |
| webhooks | Webhooks + API | — | + | + | +доплата/мес |
| club_admin | Панель админа клуба | — | + | + | +доплата |
| 1c_sync | Интеграция 1С | — | + | + | +доплата/мес |
| email | Email-рассылки | — | + | + | +доплата/мес |

## Usage-based ресурсы (metering)

| Ресурс | Единица | Лимит (Стандартная) | Сверх лимита |
|--------|---------|-------------------|-------------|
| Активные пользователи | чел/мес | 10 | +500 руб/чел |
| Клиентская база | записей | 1000 | +1 руб/клиент |
| Записи звонков | ГБ/мес | 0 | +50 руб/ГБ |
| Webhook доставка | событий/мес | 10,000 | +0.1 руб/событие |
| API-запросы | запросов/мес | 50,000 | +0.01 руб/запрос |
| Push-уведомления | сообщений/мес | 5,000 | +0.5 руб/сообщение |
| Email-рассылки | писем/мес | 5,000 | +0.3 руб/письмо |

## UsageMeterService

```typescript
@Cron('0 * * * *')
async aggregateUsage() {
  for (const club of await this.clubService.findAllActive()) {
    const usage = {
      active_users: await this.countActiveUsers(club.id),
      clients: await this.countClients(club.id),
      call_storage_gb: await this.sumCallStorage(club.id),
      webhooks_sent: await this.countWebhooksSent(club.id, thisMonth),
      push_sent: await this.countPushSent(club.id, thisMonth),
      emails_sent: await this.countEmailsSent(club.id, thisMonth),
    };
    await this.updateUsage(club.id, usage);
    await this.checkLimitsAndNotify(club.id, usage);
  }
}
```

## Лицензирование: процесс истечения

1. За 30 дней: email + баннер
2. За 7 дней: повторное уведомление
3. День истечения: предупреждение (grace period 3 дня)
4. +3 дня: только чтение
5. +14 дней: полная блокировка
