# Приёмка liderra.ru — ПУНКТ №7: Деньги — списание + тройная запись (копейки сходятся)

> **Для исполнителя:** формат PR1 (одобрен 20.06). Каждый шаг = `🔧 Код-факт (file:line)` · `🎬 Действие` · `📋 ОТЧЁТ (было→ожидали→стало + 📸/📝)` · `❌ Если не так`. Техника «под капотом»; владельцу — человеческие карточки.

**Цель:** показать, что за каждую доставленную заявку портал делает **аккуратную бухгалтерию**: списывает с клиента ровно цену тарифа (до копейки), и на это же списание заводит **три согласованные записи** — «сколько списали с клиента», «движение по его кошельку» и «во сколько эта заявка обошлась нам у поставщика». Все три бьются между собой, копейки не теряются. Это фундамент, на котором стоят и баланс клиента, и наша маржа.

**Что снимаем глазами:** **Биллинг** — списание = цена тарифа, новый остаток правильный. Живые 📸. (Три записи — внутренняя бухгалтерия, 📝.)

**Источник сценария:** свод проверок №7 (M1); R3 «движок».

---

## 🔧 Код-факты (подтверждено чтением 20.06)

Одно списание ([LedgerService::chargeForDelivery :47-128](../../../app/app/Services/Billing/LedgerService.php#L47-L128)) в одной транзакции делает:

1. **Списание с баланса (bcmath, копейки)** — `amount = цена/100`, `новый баланс = баланс − amount` (2 знака, без потери копеек) — [:80-84](../../../app/app/Services/Billing/LedgerService.php#L80-L84).
2. **Запись 1 — `lead_charges`** (что списали с клиента): ступень `tier_no`, цена `price_per_lead_kopecks`, источник `charge_source='rub'`, привязка к сделке — [:89-98](../../../app/app/Services/Billing/LedgerService.php#L89-L98).
3. **Запись 2 — `balance_transactions`** (движение кошелька): тип `lead_charge`, сумма `amount_rub = −цена`, остаток после `balance_rub_after`, привязка к сделке — [:100-110](../../../app/app/Services/Billing/LedgerService.php#L100-L110).
4. **Запись 3 — `supplier_lead_costs`** (себестоимость): `cost_rub` поставщика для этой сделки — основа маржи — [:112-124](../../../app/app/Services/Billing/LedgerService.php#L112-L124).

- Цена берётся по ступени (пункт №8) — [:53-57](../../../app/app/Services/Billing/LedgerService.php#L53-L57).
- Всё **атомарно**: транзакция оборачивает все три INSERT'а — либо всё, либо ничего (контракт — [:43-45](../../../app/app/Services/Billing/LedgerService.php#L43-L45)).
- Себестоимость берётся у поставщика по платформе B1/B2/B3 (или DIRECT) — [resolveSupplierId :138-172](../../../app/app/Services/Billing/LedgerService.php#L138-L172).

---

## Шаг 7-1 — Одна заявка → одно списание, видно в Биллинге

**🔧 Код:** списание — [:80-98](../../../app/app/Services/Billing/LedgerService.php#L80-L98).
**🎬 Действие (лок):** влить одному клиенту 1 заявку; зафиксировать баланс «до».

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Списание | — | строка списания = цена ступени (напр. 500 ₽) | _(слот прогона)_ | 📸 Биллинг — новое списание |
| Новый баланс | X ₽ | X − цена (до копейки) | _(слот)_ | 📸 Биллинг/Дашборд (новый остаток) |

**❌ Если не так:** списание ≠ цена / баланс «уехал» на копейки → денежный дефект (bcmath).

---

## Шаг 7-2 — Три записи согласованы, копейки сходятся (внутренняя сверка)

**🔧 Код:** три INSERT'а — [:89-124](../../../app/app/Services/Billing/LedgerService.php#L89-L124).
**🎬 Действие:** для этой сделки сверить три записи между собой.

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Списание с клиента (`lead_charges`) | — | цена ступени, привязка к сделке | _(слот)_ | 📝 SQL `lead_charges WHERE deal_id=…` |
| Движение кошелька (`balance_transactions`) | — | `amount_rub = −цена`, `balance_rub_after` = новый баланс | _(слот)_ | 📝 SQL `balance_transactions WHERE related_id=…` |
| Себестоимость (`supplier_lead_costs`) | — | `cost_rub` поставщика на эту сделку | _(слот)_ | 📝 SQL `supplier_lead_costs WHERE deal_id=…` |
| Сходимость | — | `lead_charges.price` = `−balance_transactions.amount_rub` = (баланс до − баланс после); `balance_rub_after` совпал с балансом клиента | _(слот)_ | 📝 сверка трёх чисел |

**💡 Что внутри:** на каждое списание портал делает не одну, а три связанные записи. Первая — «с клиента взяли столько-то». Вторая — «в его кошельке стало вот столько». Третья — «нам эта заявка обошлась во столько-то у поставщика». Все три считаются от одной цифры и сходятся между собой до копейки. Это значит: и баланс клиента всегда правильный, и мы в любой момент знаем, сколько заработали на каждой заявке. Деньги не теряются и не появляются из ниоткуда.

**❌ Если не так:** записи расходятся / `balance_rub_after` ≠ реальному балансу / нет одной из трёх → денежный дефект (СТОП).

---

## Шаг 7-3 — Маржа: выручка минус себестоимость (внутренняя)

**🔧 Код:** выручка — `lead_charges`; себестоимость — `supplier_lead_costs` [:117-123](../../../app/app/Services/Billing/LedgerService.php#L117-L123).
**🎬 Действие:** на одной сделке показать разницу «взяли с клиента − заплатили поставщику».

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Маржа на сделке | — | цена клиенту − себестоимость = заработок > 0 | _(слот)_ | 📝 SQL: `lead_charges.price/100 − supplier_lead_costs.cost_rub` |

**💡 Что внутри:** портал заодно фиксирует, во сколько каждая заявка обошлась у поставщика — поэтому всегда видно, сколько на ней заработали (взяли с клиента минус заплатили поставщику). Это основа итогового отчёта о прибыли (пункт F4).

**❌ Если не так:** нет записи себестоимости → маржу не посчитать (дыра в учёте, не блокер денег клиента, но важно для отчёта владельцу).

---

## Verification №7 (выход из пункта)
- [ ] 📸 Биллинг: списание = цена ступени, новый баланс правильный (до копейки).
- [ ] 📝 Три записи на сделку (lead_charges + balance_transactions + supplier_lead_costs) есть и согласованы.
- [ ] 📝 `balance_rub_after` совпадает с реальным балансом клиента; сумма списания = разнице баланса.
- [ ] 📝 Маржа = выручка − себестоимость считается на сделке.

## 📸 vs 📝 в этом пункте
- **📸 Скриншот:** Биллинг — списание и новый остаток.
- **📝 Текст:** три записи бухгалтерии и их сходимость, себестоимость, маржа — глазами не видны (внутренняя бухгалтерия).

## Грабли
- Копейки — через bcmath; ловить «уехавший» остаток сверкой `balance_rub_after` с фактическим балансом.
- Себестоимость пишется только при известном поставщике (B1/B2/B3/DIRECT); для синтетических лидов без поставщика записи себестоимости может не быть — на маржу не влияет критично, но учесть на прогоне.
- Атомарность: если упало списание (нехватка) — НИ одной из трёх записей быть не должно (связка с №9 — полный откат).
