# Модуль «Партнёры» — внутренний учёт в CRM Obmenka **Версия:** 1.0 (после уточнения цели) **Дата:** 2026-05-16 **Заменяет:** `affiliate-prd-v2.md` в части реализации (исследования остаются как backup-материал) --- ## 1. Цель модуля Внутренний CRM-модуль для учёта того, **кто привёл каждого клиента** и **сколько мы должны этому человеку** по каждой сделке. Это **НЕ публичная affiliate-программа** с кабинетом, ref-ссылками и маркетингом. Это **учётная книга партнёров** в нашей CRM. Все данные ведут менеджеры вручную, партнёры в систему не заходят. --- ## 2. Бизнес-задача ### 2.1. Что болит сейчас У руководителя (и менеджеров) **нет единого места**, где зафиксировано: - Кто привёл каждого клиента - Контакты этого «привёдшего» (телефон, TG) - На каких условиях мы работаем с этим клиентом (например: «10% от маржи отдаём Ахмету» или «$5 с каждой сделки») - Сколько всего начислено партнёру по этому клиенту - Сколько уже выплачено, сколько остаётся должны - Статистика по партнёру в целом: сколько клиентов привёл, какой оборот, общая комиссия Сейчас это всё в голове, в WhatsApp-переписках, в Excel-табличке менеджера. При смене менеджера информация теряется. При споре с партнёром («ты мне должен $400, нет, $250») — невозможно проверить. ### 2.2. Какая задача решается После внедрения модуля: 1. У каждого клиента в карточке видно «привёл партнёр Ахмет, условия 10% от маржи» 2. Каждая сделка клиента → автоматически генерится начисление партнёру 3. В карточке партнёра видно: «должны Ахмету 425 USDT за май, выплачено 200, баланс 225» 4. При выплате партнёру (наличными в офисе / USDT-переводом) — фиксируется в системе 5. Аудит-история: кто и когда менял условия, кто начислил, кто выплатил ### 2.3. Почему важно сейчас - Объём операций растёт (149 сделок за 15 дней в seed, реально ~300/мес сейчас) — учёт «в голове» не масштабируется - Партнёры спрашивают «сколько я заработал?» — без модуля ответ занимает часы Excel-работы - При смене кассира/менеджера привязки и условия теряются - Это базис для будущего: когда созреем до настоящей программы с кабинетом — данные уже есть --- ## 3. Сценарии использования (user stories) ### 3.1. Менеджер заводит нового партнёра «Мне Ахмет, владелец туристического агентства в Лимане, говорит — буду присылать тебе своих клиентов, дай мне 1% с их оборотов». → Менеджер в админке: «Партнёры» → «+ Новый партнёр» → Заполняет: имя «Ахмет Йылмаз», TG `@ahmet_lara`, телефон `+90 555 ...`, **дефолтные условия**: 1% от volume USDT → Сохраняет → партнёр в системе ### 3.2. Менеджер привязывает нового клиента к партнёру Клиент пришёл, кассир оформляет сделку. → В форме новой сделки (или карточке клиента) — поле «Партнёр» с autocomplete → Кассир выбирает «Ахмет Йылмаз» → Условия по клиенту = дефолт партнёра (1% volume) — можно override на этом клиенте → Сохраняет ### 3.3. Менеджер ставит особые условия на конкретного клиента «Ахмет привёл крупного клиента — для него договорились 0.5% volume, не 1%». → В карточке клиента → блок «Партнёр» → кнопка «Изменить условия для этого клиента» → Заполняет: 0.5% от volume USDT → С момента изменения новые сделки этого клиента считаются по 0.5% → Старые начисления не пересчитываются (snapshot) ### 3.4. Сделка автоматически генерит начисление партнёру Сделка клиента переходит в `done` → система: 1. Видит у клиента поле `partner_id` → есть привязка 2. Берёт условия по этому клиенту (или дефолт партнёра если override нет) 3. Считает комиссию = `0.5% × volume_usdt` 4. Создаёт запись `partner_commission` в статусе `pending` 5. Через hold-период (по умолчанию 0 — пользователь решит) → `accrued` (должны выплатить) ### 3.5. Руководитель смотрит сколько должен партнёру → Админка → «Партнёры» → карточка «Ахмет Йылмаз» → Видит: «4 клиента, 23 сделки, оборот $84 000, накопленная комиссия $420, выплачено $200, **должны $220**» → Список всех клиентов Ахмета с агрегатами → Журнал начислений по каждой сделке → Журнал выплат ### 3.6. Менеджер фиксирует выплату партнёру Руководитель отдал Ахмету $200 наличными в офисе. → Карточка партнёра → «Зафиксировать выплату» → 200 USDT, метод «cash», комментарий «Отдали в Лиман-офисе, расписка #134» → В системе: 200 USDT помечается как `paid` (списывается с накопленной комиссии) → Опционально: связать с кассовой `cash_movement` (расход с кассы) --- ## 4. Сущности модуля ### 4.1. Партнёр (`Partner`) Кто-то, кто приводит нам клиентов. Может быть физлицо, может быть юрлицо, может быть «организация» (типа турбюро). **В систему НЕ заходит** — только данные о нём. Поля: - ID, external_ref (например `P-0001`) - Display name («Ахмет Йылмаз», «Travel-агентство Mavi») - Контакты: TG-username, телефон, email - Заметка (свободный текст: «Знакомый Дмитрия, лояльный, платит ему наличкой раз в месяц») - **Default условия** комиссии (применяются к новым клиентам без override): - Модель: `pct_of_volume` / `pct_of_profit` / `fixed_per_deal` - Значение: 0.01 = 1% или 5 USDT и т.д. - Статус: `active` / `paused` (не начисляем) / `archived` - Optional: parent_partner_id (для multi-level, фаза 2) - Кто завёл, когда ### 4.2. Условия по клиенту (`ClientPartnerLink`) Привязка клиента к партнёру + конкретные условия именно по этому клиенту. Поля: - ID - Client (FK) - Partner (FK) - attached_at, detached_at (история привязок) - **Условия** (могут отличаться от дефолтных партнёра): - Модель: pct_of_volume / pct_of_profit / fixed_per_deal - Значение - Кто привязал (user), причина **Логика:** у клиента в каждый момент времени **одна активная привязка** (partial UNIQUE). Если клиента переключили на другого партнёра — старая запись `detached_at` заполняется, создаётся новая. ### 4.3. Начисление комиссии (`PartnerCommission`) Одна запись на каждую сделку клиента, привязанного к партнёру. Поля: - ID - Deal (FK) - Partner (FK, snapshot) - ClientPartnerLink (FK, snapshot условий) - Расчётные данные: - base_value_usdt (volume или profit на момент расчёта) - rate (% или фикс) - amount_usdt (итоговая сумма) - Статус: `pending` (только что начислено) / `accrued` (готово к выплате) / `paid` / `voided` / `reversed` - payout_id (FK на Выплату, если оплачено) - Создано когда, кем (system / manual recalc) **Идемпотентность:** UNIQUE по (deal_id, partner_id) — на одну сделку максимум одна запись на партнёра. ### 4.4. Выплата (`PartnerPayout`) Факт того, что мы отдали партнёру деньги. Поля: - ID - Partner (FK) - Amount USDT - Method: `cash` / `usdt-trc20` / `bank` / `other` (свободный) - Дата выплаты - Кто провёл (user) - Комментарий («Расписка #134», «TX hash 0xabc...») - Optional: cash_movement_id (FK) — если связано с расходом из кассы - Optional: operation_id (FK) — если выплата прошла как crypto_out через нашу Operation Когда фиксируется выплата на сумму X: - Покрываем `accrued` начисления партнёра в порядке FIFO - Помечаем их `paid`, заполняем `payout_id` - Если X больше чем accrued — записываем избыток как «predoplata» (отрицательный баланс) --- ## 5. Расчёт комиссии ### 5.1. Trigger Момент `Deal.status → done`. Post-commit hook (после успешного коммита сделки): 1. Если у `deal.client.id` есть активный `ClientPartnerLink` → берём условия (override клиента ИЛИ дефолт партнёра) 2. Если партнёр `paused`/`archived` → не начисляем 3. Считаем сумму: - `pct_of_volume`: `volume_usdt × rate` - `pct_of_profit`: `profit_usdt × rate` - `fixed_per_deal`: rate (фиксированная сумма) 4. Создаём `PartnerCommission(status='accrued')` (без hold на MVP — будем использовать сразу) 5. UNIQUE(deal_id, partner_id) защищает от дублирования ### 5.2. Reversal (отмена сделки) Если сделка перешла в `cancelled` уже после начисления: - Запись помечается `voided` (если ещё `accrued`) - Если уже `paid` — создаётся `reverse_entry` с отрицательной суммой (баланс партнёра уйдёт в минус) - Минус-баланс **разрешён**: при следующих начислениях покрывается, либо при выплате учитывается ### 5.3. Пересчёт при изменении условий Если менеджер изменил условия (override) или дефолт партнёра — **старые записи не пересчитываются**. Snapshot условий хранится в `PartnerCommission`. Только будущие сделки идут по новым условиям. Опционально: admin может вручную дать команду «пересчитать комиссии по сделке X» — voidает существующие записи и создаёт новые. ### 5.4. Edge-cases | Случай | Поведение | |---|---| | У клиента нет привязки к партнёру | Не начисляем (нормальная ситуация) | | Партнёр `paused` | Не начисляем, но привязка клиента остаётся | | Партнёр `archived` | Не начисляем, скрыт из autocomplete | | Override условий = 0% | Начисляем 0 (запись для аудита, видно что «условия 0»), статус `voided: zero_rate` | | Сделка `cancelled` после начисления | `voided` если ещё `accrued`, `reverse` если `paid` | | Сделка в `excluded_asset` партнёра (если задано) | 0 | | Деление partner в системе (archived вместо удаления) | Старые записи остаются, нельзя удалить партнёра с историей | --- ## 6. Выплаты и баланс ### 6.1. Балансы Вычисляются на лету: ``` accrued_total(partner) = SUM(amount) WHERE status='accrued' paid_total(partner) = SUM(amount) WHERE status='paid' balance_owed(partner) = accrued_total - 0 (то что должны) ``` Если был reversal на paid: ``` reversed_total(partner) = SUM(|amount|) WHERE status='reversed' balance_owed(partner) = accrued_total - reversed_total ``` ### 6.2. Фиксация выплаты Кнопка «Зафиксировать выплату» в карточке партнёра: Поля формы: - Сумма USDT - Метод (cash / usdt-trc20 / bank / other) - Дата (по умолчанию сегодня) - Комментарий (свободный) - Optional: «Списать с кассы X» — создаёт связанный `CashMovement(kind='collection_out', reason='Выплата партнёру Ахмет')` - Optional: «Прикрепить tx hash» — если выплата USDT-TRC20, ввести hash для аудита После сохранения: 1. Создаётся `PartnerPayout(status='completed')` 2. Покрываем `accrued` записи по FIFO до суммы выплаты 3. Каждая накрытая → `status='paid'`, `payout_id` заполнен 4. Если сумма больше чем accrued — избыток хранится как `prepayment` (можно показать как «авансом выплачено $30», уменьшит следующие начисления) ### 6.3. Никакого hold-периода на MVP В моём предыдущем PRD был hold 7 дней. Тут он не нужен: - Это внутренний учёт, не публичная программа - Партнёр всё равно деньги получит когда менеджер их физически передаст - Если сделка отменилась после выплаты — отдельный процесс (reverse + договариваться с партнёром) Опция «отложенное начисление» (delay) может быть добавлена позже как настройка партнёра. --- ## 7. Multi-level (опционально, фаза 2) Партнёр A привёл партнёра B. Когда клиенты B делают сделки → B получает свою комиссию, а A получает % от комиссии B. **Реализация (фаза 2):** - Поле `Partner.parent_partner_id` (self-FK) - При начислении B → дополнительно создаём `PartnerCommission` на A с указанием `tier=2` - Cтавка tier 2 — отдельное поле на программе/партнёре (например, 10% от того что получил B, либо 0.05% от volume — два варианта расчёта) - НЕ делаем 3+ уровней (риск пирамиды, нет реальной бизнес-выгоды) **На MVP не делаем.** Сначала запускаем single-level, смотрим на реальное использование, потом решаем нужен ли каскад. --- ## 8. Модель данных (SQLAlchemy) ### 8.1. Новые таблицы ```python class Partner(Base): __tablename__ = "partners" id: Mapped[int] = mapped_column(primary_key=True) external_ref: Mapped[str] = mapped_column(String(32), unique=True, index=True) # "P-0001" name: Mapped[str] = mapped_column(String(255)) tg_username: Mapped[str | None] phone: Mapped[str | None] email: Mapped[str | None] note: Mapped[str | None] = mapped_column(Text) # Дефолтные условия — применяются к новым клиентам без override default_commission_model: Mapped[str] = mapped_column(String(32), default="pct_of_volume") # 'pct_of_volume' / 'pct_of_profit' / 'fixed_per_deal' default_commission_rate: Mapped[Decimal] = mapped_column(Numeric(10, 6), default=0) # для pct — десятичная дробь (0.01 = 1%); для fixed — сумма в USDT status: Mapped[str] = mapped_column(String(16), default="active") # active/paused/archived parent_partner_id: Mapped[int | None] = mapped_column(ForeignKey("partners.id")) # фаза 2 created_at, created_by_id, ... class ClientPartnerLink(Base): __tablename__ = "client_partner_links" id: Mapped[int] = mapped_column(primary_key=True) client_id: Mapped[int] = mapped_column(ForeignKey("clients.id"), index=True) partner_id: Mapped[int] = mapped_column(ForeignKey("partners.id"), index=True) # Условия именно по этому клиенту (если NULL — используем default партнёра) commission_model: Mapped[str | None] commission_rate: Mapped[Decimal | None] attached_at: Mapped[datetime] = mapped_column(default=now_utc) detached_at: Mapped[datetime | None] attached_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id")) reason: Mapped[str | None] # partial UNIQUE: на клиента максимум одна активная привязка # CREATE UNIQUE INDEX uq_active_link ON client_partner_links(client_id) WHERE detached_at IS NULL class PartnerCommission(Base): __tablename__ = "partner_commissions" id: Mapped[int] = mapped_column(primary_key=True) deal_id: Mapped[int] = mapped_column(ForeignKey("deals.id"), index=True) partner_id: Mapped[int] = mapped_column(ForeignKey("partners.id"), index=True) link_id: Mapped[int | None] = mapped_column(ForeignKey("client_partner_links.id")) # snapshot какая привязка tier: Mapped[int] = mapped_column(Integer, default=1) # 1=прямой, 2=upline # Snapshot расчёта commission_model: Mapped[str] # как считали commission_rate: Mapped[Decimal] # ставка применённая base_value_usdt: Mapped[Decimal] # volume или profit на момент amount_usdt: Mapped[Decimal] # итог status: Mapped[str] = mapped_column(String(16), default="accrued") # accrued / paid / voided / reversed payout_id: Mapped[int | None] = mapped_column(ForeignKey("partner_payouts.id")) void_reason: Mapped[str | None] reverses_id: Mapped[int | None] = mapped_column(ForeignKey("partner_commissions.id")) created_at: Mapped[datetime] = mapped_column(default=now_utc) __table_args__ = (UniqueConstraint("deal_id", "partner_id", "tier", name="uq_commission"),) class PartnerPayout(Base): __tablename__ = "partner_payouts" id: Mapped[int] = mapped_column(primary_key=True) partner_id: Mapped[int] = mapped_column(ForeignKey("partners.id"), index=True) amount_usdt: Mapped[Decimal] method: Mapped[str] # cash / usdt-trc20 / bank / other paid_at: Mapped[datetime] = mapped_column(default=now_utc) paid_by_user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) note: Mapped[str | None] # Связи с уже существующими сущностями системы cash_movement_id: Mapped[int | None] = mapped_column(ForeignKey("cash_movements.id")) operation_id: Mapped[int | None] = mapped_column(ForeignKey("operations.id")) tx_hash: Mapped[str | None] # для криптовыплат ``` ### 8.2. Изменения существующих таблиц ```python # Client — добавляем denormalized указатель на текущего партнёра (для скорости) clients.referrer_partner_id: int | None # FK на partners clients.referrer_set_at: datetime | None # Deal — добавляем snapshot для аудита (вдруг привязку поменяют после) deals.commission_calculated_at: datetime | None ``` `referrer_partner_id` — кеш. Истина — в `client_partner_links` (последняя без `detached_at`). Обновляется триггером или в коде при создании/изменении link. --- ## 9. UI экраны (только админка, без партнёрского кабинета) ### 9.1. Новые экраны **`/partners`** — Список партнёров | Колонки | | |---|---| | ID, имя, контакты | TG/телефон/email | | Статус | active/paused/archived (бейдж) | | Клиентов | сколько привёл | | Сделок | сколько прошло по его клиентам | | Должны | balance_owed в USDT (красный если > 100) | | Действия | edit, archive | Фильтры: статус, поиск по имени/TG. Кнопка «+ Новый партнёр». **`/partners/new`** — Форма создания Поля: имя, TG, телефон, email, заметка, дефолтная модель комиссии + ставка, статус. **`/partners/{id}`** — Карточка партнёра Header: имя, контакты, статус. Tabs: - **Обзор**: 4 KPI (клиентов / сделок / должны USDT / выплачено всего), последние 10 начислений - **Клиенты**: таблица с псевдонимом, датой привязки, условиями, кол-вом сделок, оборотом, начислено-выплачено - **Начисления**: журнал PartnerCommission с фильтрами (статус, период) - **Выплаты**: журнал PartnerPayout с TX/cash_movement ссылками - **Настройки**: редактирование дефолтных условий, статуса, заметки Кнопка «Зафиксировать выплату» (modal с формой). ### 9.2. Изменения существующих экранов **Карточка клиента `/clients/{id}`** — добавляется блок «Партнёр»: - Если привязан — показ имени, условий, истории, кнопка «Изменить условия» и «Отвязать» - Если нет — кнопка «Привязать к партнёру» (autocomplete) - История привязок (если перепривязывался) **Список клиентов `/clients`** — колонка «Партнёр» + фильтр. **Карточка сделки `/deals/{id}`** — блок «Партнёрская комиссия»: - Read-only: «По этой сделке начислено партнёру Ахмет: 4.20 USDT (0.5% от volume, статус: accrued)» - Если в reverse — показано **Форма новой сделки `/deals/new`** — если у клиента есть привязка, в подсказке: «Будет начислено партнёру Ахмет (1%)». **Дашборд (для admin/manager)** — виджет: - Активных партнёров: N - Должны партнёрам всего: X USDT - Начислено за период: Y USDT ### 9.3. Удалено из старого PRD - ❌ Партнёрский кабинет (`/aff/*`) - ❌ Партнёрский логин и роль `affiliate` - ❌ 2FA для партнёра - ❌ Реферальные ссылки `/r/{code}` - ❌ Cookie tracking - ❌ Self-signup - ❌ Маркетинговые материалы - ❌ Tax-report (это уровень всей системы, не модуля) - ❌ Public landing pages --- ## 10. RBAC | Роль | Что может | |---|---| | `admin` | CRUD партнёров, изменение условий, фиксация выплат, recalc комиссий | | `manager` | CRUD партнёров, привязка клиентов, фиксация выплат, изменение условий клиента | | `accountant` | Read-only по всему, экспорт CSV (начисления/выплаты по периоду) | | `cashier` | В форме сделки и карточке клиента — видит и может привязать к партнёру (autocomplete) | | `viewer` | Без модуля партнёров (как и сейчас) | Никакой новой роли. Никакого внешнего доступа. --- ## 11. Audit log Новые действия: - `partner_created`, `partner_updated`, `partner_archived` - `partner_link_attached`, `partner_link_detached`, `partner_link_terms_changed` - `commission_accrued` (системно), `commission_voided`, `commission_recalc` (по сделке) - `payout_recorded` Всё пишется через существующий `record_audit()`. --- ## 12. Связь с существующей системой | Точка | Что меняется | Сложность | |---|---|---| | `models.py` | +4 модели (Partner, ClientPartnerLink, PartnerCommission, PartnerPayout) + ALTER на `clients`, `deals` | средняя | | Alembic | Одна миграция со всеми изменениями | низкая | | `routes/deals.py` | Post-commit hook вызывает `commission_engine.accrue_for_deal(deal)` | низкая | | `routes/clients.py` | Блок «Партнёр» в карточке + поле в форме создания | средняя | | `routes/partners.py` | НОВЫЙ — список, карточка, CRUD, выплаты | высокая (основная работа) | | `templates/partners_*.html` | НОВЫЕ шаблоны (list, card, new, payout-modal) | средняя | | `templates/base.html` | Пункт «Партнёры» в сайдбаре | низкая | | `templates/client_card.html` | Блок партнёра | низкая | | `templates/deal_card.html` | Блок «комиссия по сделке» | низкая | | Дашборд | Виджет «партнёры» для admin/manager | низкая | Никаких новых cron-job, никаких новых cookie, никаких новых auth-flows. Минимально-инвазивно. --- ## 13. Фазы реализации ### Фаза 1 — MVP (1.5–2 недели) В скоупе: - Все 4 модели + миграция - CRUD партнёров (`/partners`, `/partners/new`, `/partners/{id}`) - Карточка партнёра с tabs (обзор / клиенты / начисления / выплаты / настройки) - Блок «Партнёр» в карточке клиента и форме сделки - Авторасчёт комиссии при `Deal.status → done` (post-commit hook) - Фиксация выплаты партнёру (modal) - Аудит всех действий - Виджет на дашборде **Без:** - Multi-level - Связь выплаты с cash_movement (можно вручную потом) - Massive recalc tools для admin - CSV экспорт (накопится в фазе 2) ### Фаза 2 — Расширение (1 неделя) - Multi-level (parent_partner_id, tier 2 начисления) - Связь PartnerPayout с CashMovement / Operation (автосписание с кассы) - CSV экспорт начислений/выплат по периоду - Massive recalc по сделке/клиенту (admin tool) - Hold-период как настройка партнёра (если нужен будет) - Excluded assets per partner --- ## 14. Открытые вопросы (требуют ответа) ### 14.1. Бизнес 1. **Multi-level нужен?** Если да — это фаза 2. Если нет вообще — снимаем поле `parent_partner_id`. По описанию из первого сообщения «аффилиатов может быть несколько уровней» — нужен. Подтверди что в **MVP single-level**, фаза 2 — multi-level. 2. **Override условий клиента — перекрывает или плюсуется к дефолту партнёра?** Мой выбор: **перекрывает целиком** (если override стоит — используем его, дефолт партнёра не смотрим). Альтернатива «плюсуется» сложна и редко нужна. 3. **Hold-период на MVP — 0 (всё сразу accrued)?** Мой выбор: **0**. Это внутренний учёт, не клиентские деньги. Если потом понадобится — добавим как настройку партнёра. 4. **Выплата партнёру связана с кассой автоматически или ручной cash_movement?** В MVP — ручной (просто запись PartnerPayout без связи). В фазе 2 — опция «списать с кассы X» автоматом создаёт cash_movement. 5. **Условия по умолчанию для новых партнёров?** Мой выбор: при создании партнёра ставка `0` и модель `pct_of_volume`. Менеджер обязан явно задать ставку до того как привязать клиента. Альтернатива — какой-то общий дефолт (например 0.5% volume), но это слишком предположительно. 6. **Может ли один клиент быть привязан к нескольким партнёрам?** Мой выбор: **НЕТ** (partial UNIQUE гарантирует одну активную привязку). Если есть совместная история — это переключение во времени. 7. **Архивный партнёр — что с его историей?** Мой выбор: историю не трогаем (записи остаются), просто не показываем в autocomplete и не позволяем привязывать новых клиентов. ### 14.2. Технические 8. **Когда срабатывает accrual — на `done` или сразу при создании сделки?** Мой выбор: **на done** (как сейчас работают операции). Если сделка в `new` или `in_work` — комиссии ещё нет. 9. **Что если менеджер забыл указать партнёра, а сделка прошла?** Возможность: ретро-привязка. Менеджер открывает клиента → ставит партнёра → отдельная кнопка «Начислить за прошлые сделки» (с ограничением по дате). Не в MVP — добавим если понадобится. --- ## 15. Метрики успеха модуля Через 1 месяц после внедрения: - 100% активных клиентов с партнёром имеют корректную привязку в системе (а не в Excel) - 0 случаев «не знаем сколько должны партнёру X» - Время на ответ на вопрос партнёра «сколько я заработал?» — < 1 минуты - Журнал выплат — все за месяц зафиксированы в системе Через 3 месяца: - Минимум 80% выплат партнёрам проходят через модуль (а не «отдал и не записал») - 0 спорных случаев по балансу с партнёрами (всё подтверждаемо аудит-логом) --- ## 16. Что НЕ делаем явно | Что | Почему | |---|---| | Партнёрский кабинет | Не нужно по новой цели — это внутренний учёт | | Ref-коды и публичные ссылки | Нет публичной программы | | Self-signup партнёров | Менеджер их заводит | | Маркетинговые материалы | Не в скоупе модуля | | KYC партнёров в системе | Можно держать в свободной заметке, формальный KYC не нужен | | Withholding tax расчёт | Это бухгалтерия в Excel/1С, не CRM | | CMB compliance | Не публичная программа — не релевантно | | 2FA для партнёров | Партнёров в системе нет | | Cookie tracking | Нет публичного приёма клиентов | | Hold-периоды на MVP | Внутренний учёт, не клиентские деньги | | Multi-currency выплаты | Партнёры получают USDT (или наличку зафиксированную как USDT-эквивалент) | | Smart-contracts | Overkill | --- ## Приложение: соотношение со старым PRD **Старый PRD v2** (`affiliate-prd-v2.md`) — это PRD **внешней публичной партнёрской программы** с кабинетом, маркетингом, CMB-compliance. **Этот документ** — это PRD **внутреннего учётного модуля** для CRM. Что взято из старого PRD: - Базовая модель данных (упрощена, без программ-сущности, без ref-кодов) - Принцип snapshot при начислении (UNIQUE deal_id+partner_id) - Идея override условий per-клиент Что выкинуто: - Партнёрский кабинет и роль `affiliate` - Все compliance/KYC/tax расчёты на уровне модуля (это уровень бизнеса в целом) - Tracking, attribution, cookie - Программы с разными ставками (упрощено: ставка на партнёре + override на клиенте) - Tier-бонусы Bronze/Silver/Gold - Hold-периоды, auto-payouts - 3 модели вознаграждения остались (pct_of_volume / pct_of_profit / fixed_per_deal), но default = `pct_of_volume`