Модуль «Партнёры» — внутренний учёт в 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) - Кто завёл, когда

Привязка клиента к партнёру + конкретные условия именно по этому клиенту.

Поля: - 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. Новые таблицы

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. Изменения существующих таблиц

# 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. Технические

  1. Когда срабатывает accrual — на done или сразу при создании сделки? Мой выбор: на done (как сейчас работают операции). Если сделка в new или in_work — комиссии ещё нет.

  2. Что если менеджер забыл указать партнёра, а сделка прошла? Возможность: ретро-привязка. Менеджер открывает клиента → ставит партнёра → отдельная кнопка «Начислить за прошлые сделки» (с ограничением по дате). Не в 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