# План: биржа — антидубль, маршрут, конфликты

**Статус:** согласование · **не в коде** (обсуждение 31.08.2026)  
**Связано:** `ADMIN_PLAN.md` (биржа), `plan-seti.html` (этап D — партнёры), `CUSTOMER_PLAN.md` (заявка из ЛК)

---

## 1. Проблема

На открытых досках (ATI и аналоги) типичны ситуации:

1. **Один груз — много объявлений:** заказчик срочно звонит нескольким логистам; каждый выставляет груз на биржу.
2. **Подмена адреса:** логист слегка меняет маршрут в объявлении, обходя «дубликат»; после назначения перевозчика «возвращает» верный адрес.
3. **Два логиста — два перевозчика:** оба вручную завели один физический груз и назначили **разные** машины; логисты не знают, что заказчик работает не только с ними.
4. **Согласие перевозчика не спасает:** перевозчик нажимает «Принять» смену маршрута, не понимая, что это легализация дубля или второй рейс на тот же груз.

**Цель АРМАДА:** биржа **между известными кабинетами**, не клон ATI. Один физический груз — **одна активная заявка** в сети; подмена адреса **не обходит** правила; один перевозчик **не везёт два одинаковых груза** незаметно.

---

## 2. Принципы (обязательные правила продукта)

| # | Правило |
|---|---------|
| П1 | **Один груз — одна активная заявка** в сети (биржа + назначен, не закрыт). |
| П2 | **Маршрут после «Забрать» / назначения партнёра заморожен.** Изменение — только через «Запрос изменения маршрута» и согласие перевозчика, **если** система не нашла конфликт отпечатков. |
| П3 | **Контакты и полный адрес (дом)** перевозчику — не раньше принятия заявки (claim / назначение). На доске биржи — маска адреса. |
| П4 | **Биржа — для партнёров**, не анонимная свалка (см. этап D). |
| П5 | **Согласие перевозчика — не единственная защита.** Конфликт отпечатков блокируется **до** экрана «Принять / Отказаться». |
| П6 | Система **не слышит телефон.** Опора на поля заявки: ИНН, адрес с геокодом, дата, тоннаж, телефон погрузки. |

---

## 3. Отпечаток груза (`cargoFingerprint`)

Считается при: выставлении на биржу, сохранении заявки, назначении перевозчика, запросе смены маршрута.

### 3.1. Поля отпечатка

| Поле | Как нормализовать |
|------|-------------------|
| День подачи | Дата из `vehicleAt` (календарный день) |
| Загрузка | Geohash ~**1 км** (рекомендация; см. решение D) |
| Выгрузка | Geohash ~1 км |
| Тоннаж | `reqPayloadTons` ±10% |
| Якорь заказчика | `customerInn` **или** нормализованное `customer` (если нет ИНН) |
| Якорь отправителя | `shipperInn` / `loadingOwnerInn` |
| Телефон погрузки | `loadingContactPhone` (цифры) |

### 3.2. Степень совпадения

| Уровень | Условие | Действие |
|---------|---------|----------|
| **Точный дубль** | День + geohash загрузки 1 км + тоннаж + (ИНН заказчика **или** ИНН отправителя **или** телефон погрузки) | Жёсткий стоп (см. сценарии) |
| **Похожий** | ≥3 из 5: день, geohash загрузки, тоннаж, якорь заказчика, якорь отправителя | Предупреждение + бейдж «Похожий маршрут»; при назначении — стоп, если у другой фирмы уже назначен перевозчик |
| **Слабый** | Только маршрут без ИНН/телефона | Жёлтый баннер логисту: «Заполните ИНН и телефон погрузки — иначе дубль может не обнаружиться» |

### 3.3. Галочка «Это другой рейс»

Не снимает автоматический стоп при **точном дубле**. Разрешение:

1. Логист жмёт «Запросить разрешение» + комментарий (≥20 символов).
2. **Первый логист** (владелец конфликтующей заявки) **или супер-админ** подтверждает: «Да, другой рейс».
3. Без подтверждения — выставить / назначить нельзя.

---

## 4. Ситуации и решения

### С4.1. Логист выставляет заявку на биржу

**Когда:** «+ Заказ» → «На биржу» или «Вернуть на биржу».

**Обязательные поля** (без них кнопка неактивна):

- дата/время подачи;
- адрес загрузки + геокод;
- адрес выгрузки + геокод;
- тоннаж и габариты;
- цена перевозчику;
- срок оплаты перевозчику (список: по факту / 3 дня / 7 дней).

**Что видит перевозчик на доске (до «Забрать»):**

| Поле | Видно |
|------|--------|
| Заказчик перевозки (логист) | Да — название фирмы |
| Маршрут | Город + улица, **без номера дома** |
| Дата подачи, тоннаж, ставка | Да |
| Телефон / контакт грузоотправителя | **Нет** |
| Полный адрес с домом | **Нет** |

**При точном дубле у другой фирмы (активная заявка):**

- **Действие:** запрет выставления.
- **Текст:** «Такой груз уже в системе: заявка №… у [фирма], статус [на бирже / назначен]. Изменение адреса не создаёт новый груз.»

**При похожем совпадении (70–99%):** предупреждение + бейдж «Похожий маршрут»; выставить можно только с комментарием или OK первого логиста (решение A).

---

### С4.2. Срочный заказ — несколько логистов, первый выставил, второй обходит антидубль сменой адреса

**Ситуация:** заказчик звонит всем; логист 1 выставил верный адрес; логист 2 получает стоп по дублю и **меняет адрес** (соседний дом / опечатка улицы), система пропускает; на заявку 1 уже сел перевозчик П; логист 2 отдаёт заявку и просит **смену маршрута на верный адрес** «с согласия» П.

**Решение (многоуровневое):**

| Уровень | Правило |
|---------|---------|
| **L1** | Отпечаток с geohash **1 км** — сдвиг «хитрого» адреса часто всё равно **точный дубль** → стоп на выставлении (С4.1). |
| **L2** | Перевозчик **П** не может «Забрать» вторую заявку, если у него уже открыт рейс с **похожим** отпечатком (С4.4). |
| **L3** | Запрос смены маршрута: пересчёт отпечатка **нового** адреса. Если совпадает с заявкой №… (любой логист, любой статус «активен») → **автоблок**, экран перевозчику **не показывается** (С4.5). |

**Текст логисту при блоке смены маршрута:** «После смены адреса заявка совпадёт с №… у [фирма]. Это тот же груз. Смена запрещена.»

**Итог сценария:** шаг «легализовать через согласие перевозчика» **не наступает**.

---

### С4.3. Два логиста — один заказчик, заявки по телефону (не из ЛК), два разных перевозчика

**Ситуация:** заказчик не в портале; оба логиста вручную создали заявку; логисты **не знают** друг о друге; каждый назначил **своего** перевозчика.

**Момент 1 — сохранение второй заявки** (ещё без перевозчика):

| Найдено у другой фирмы | Действие |
|------------------------|----------|
| Похожий / точный отпечаток | Экран выбора (решение F): |

**Текст:**

> **Похожий груз уже в системе**  
> Заявка №… · фирма «…» · [на бирже / назначен перевозчик].  
> [ **Это тот же груз — не создавать** ]  
> [ **Другой рейс — всё равно сохранить** ] (комментарий ≥20 символов)

**Момент 2 — назначение / «Забрать» вторым логистом:**

| У другой фирмы | Действие |
|----------------|----------|
| Заявка без перевозчика | Предупреждение; назначить **можно** (риск на логисте) |
| **Уже назначен** перевозчик | **Жёсткий стоп** (решение E): «На этот груз уже назначен перевозчик у [фирма], заявка №…. Назначение заблокировано. Свяжитесь с заказчиком.» |

**Момент 3 — оба успели назначить до срабатывания правил** (разные формулировки адреса, не заполнен ИНН, гонка):

1. Создаётся **«Конфликт груза #…»** (две заявки, два перевозчика).
2. **Оба логиста** — баннер: «Возможен двойной выезд. Заявка №… (ваша) и №… (другая фирма). **Один рейс отмените до выезда.**»
3. **Оба перевозчика / водители:** «На этот груз может быть вторая машина. **Не начинайте погрузку**, пока логист не подтвердит.»
4. **Автоотмены нет** — только фиксация и эскалация.

**Кнопки разрешения конфликта (логист):**

| Кнопка | Эффект |
|--------|--------|
| «Наш рейс отменяем» | Заявка отменена, конфликт снят |
| «Подтверждаем — наш рейс основной» | Ждёт отмены второй стороны или решения супер-админа |
| «Это разные грузы» | Комментарий + снятие конфликта; событие в журнал |

**Эскалация:** оба нажали «наш основной» → супер-админ или таймаут 24 ч → блок новых назначений по этому отпечатку.

**Кто «выигрывает»:** система **не выбирает**. Фиксируются время назначения, «Выехал», комментарии. Метка в журнале партнёра: «Конфликт — двойной выезд».

**Конфиденциальность для логиста:**

| Показываем | Не показываем (по умолчанию) |
|------------|------------------------------|
| «Другая фирма в АРМАДА», № заявки, статус | Карточка чужого заказчика, ставка конкурента |
| Имя фирмы — если партнёры по «Сотрудничать» | Имя и телефон **чужого** перевозчика |

---

### С4.4. Перевозчик забирает заявку с биржи (claim)

**Действия перевозчика:** Биржа → «Забрать» → водитель + ТС → «Подписать заявку и назначить».

**Действия системы:**

1. Проверка **L2:** нет ли у перевозчика открытого рейса с похожим отпечатком → иначе стоп: «У вас уже рейс №… на этот груз.»
2. Атомарно: `onExchange=false`; второй claim → «Заявка уже забрана перевозчиком [фирма].»
3. Снимок **маршрута v1** и **ставки** в `transportApp` (неизменяемый блок).
4. Полные адреса и контакты — перевозчику после подписи.
5. Поля маршрута у логиста **заблокированы** (редактирование только через С4.5).

**Текст на экране claim:**

> Подписывая заявку, вы соглашаетесь везти по указанному маршруту и ставке. Изменение адреса — только с вашего подтверждения, если система не обнаружит конфликт с другой заявкой.

---

### С4.5. Логист меняет адрес после назначения партнёра

**Запрещено:** тихое сохранение карточки с новым `routePoints` при наличии `transportApp.signedAt`.

**Разрешённый путь:**

| Шаг | Кто | Действие |
|-----|-----|----------|
| 1 | Логист | «Запросить изменение маршрута» |
| 2 | Логист | Что меняется, новый адрес, причина (обязательно); при смене ставки — отдельное поле |
| 3 | Система | Пересчёт отпечатка нового маршрута → таблица L3 (С4.2) |
| 4a | Конфликт | Автоблок, логисту текст (без экрана у перевозчика) |
| 4b | Нет конфликта | Статус «Ожидает согласия перевозчика»; водитель **не** видит новый адрес |
| 5 | Перевозчик | Экран: старый / новый маршрут, причина, ставка; предупреждение при similarity 70–99% |
| 6a | «Принять» | Маршрут v2, новый снимок в договоре; повторная проверка L2 |
| 6b | «Отказаться» | Логисту: «Перевозчик не согласен»; назначение **сохраняется** |
| 6c | «Отказаться и снять с рейса» | Сброс назначения; на биржу **не** возвращается автоматически |

**Текст перевозчику при согласовании (если similarity высокая, но не автoblock):**

> После изменения адрес совпадёт с заявкой №… ([логист], [статус]). Если это **тот же груз** — нажмите **Отказать**. Если на погрузке **действительно другой адрес** — **Принять**.

---

### С4.6. На погрузке адрес не совпадает с заявкой

**Действие водителя / перевозчика:** «Адрес на месте не совпал» + комментарий (фото — опционально, этап 5).

**Действие системы:**

1. Событие в журнале заявки.
2. Баннер логисту: «Перевозчик сообщил о несовпадении адреса на погрузке».
3. Статус рейса **не** меняется автоматически.
4. Логист инициирует С4.5 **или** закрывает вручную.
5. Событие → история пары логист–перевозчик (рейтинг).

**Текст водителю:**

> Если адрес не совпадает с заявкой — «Адрес не совпал». Не начинайте погрузку, пока диспетчер не подтвердит новые условия.

---

### С4.7. Заявка из личного кабинета заказчика (ЛК)

**Когда:** заказчик подал заявку через `/z` — есть `customerOrderId` (или аналог).

**Правила:**

- На биржу / в работу — только привязка к **этой** заявке ЛК.
- Второй логист **не может** опубликовать тот же `customerOrderId`.
- **Текст:** «Груз уже ведёт [логист] по заявке заказчика №…»

Для телефонных заказов без ЛК — только отпечаток (раздел 3) и С4.3.

---

### С4.8. Закрытая сеть партнёров (не ATI)

**Сейчас:** заявка на бирже видна **всем** админам с тарифом «Бизнес» (`canAdminSeeOrder` + `onExchange`).

**План (этап D):**

| Условие | Доступ |
|---------|--------|
| Партнёр **не** принял «Сотрудничать» | Чужие заявки **не видит** |
| Партнёр принял приглашение | Видит заявки **этого** логиста (+ свои) |
| Приглашение | Справочники → «Пригласить на биржу» → ссылка → «Принять» |

---

## 5. Сводная таблица правил

| Событие | Правило | Сообщение |
|---------|---------|-----------|
| Выставление на биржу | Точный дубль у другой фирмы | «Такой груз уже в системе… №…» |
| Выставление | Нет геокода загрузки | «Укажите адрес — иначе дубль не проверить» |
| Сохранение заявки | Похожий груз | С4.3 — выбор «не создавать / другой рейс» |
| «Забрать» / назначение | У другой фирмы уже назначен | **Стоп** (E) |
| «Забрать» | У перевозчика похожий открытый рейс | «У вас уже рейс №…» |
| «Забрать» | Уже забрано | «Заявка уже у [фирма]» |
| Сохранение карточки | Есть подпись, меняется маршрут | «Используйте „Запрос изменения маршрута“» |
| Смена маршрута | Новый отпечаток = другая активная заявка | **Автоблок** (C) |
| Смена маршрута | Новый отпечаток = свой рейс перевозчика | **Автоблок** |
| Конфликт груза | Оба «наш основной» | Эскалация супер-админ / 24 ч |

---

## 6. Этапы внедрения (код — только после «делай»)

| Этап | Содержание | Критерий готовности |
|------|------------|---------------------|
| **1** | Блок `routePoints` после подписи; снимок v1 в `transportApp`; маска адреса на бирже; тексты claim | После claim логист не меняет адрес молча; перевозчик видит полный адрес |
| **2** | `cargoFingerprint()`; проверка при publish; UI отказа + «другой рейс» с OK первого логиста | Два кабинета — второй не выставляет точный дубль |
| **3** | Проверка при **создании/сохранении** заявки; стоп назначения (E); сущность «Конфликт груза» | Второй логист не назначает перевозчика, если первый уже назначен |
| **4** | `routeChangeRequest`; экран перевозчика; L3 автoblock | Смена адреса под дубль не доходит до «Принять» |
| **5** | «Адрес не совпал» у водителя; журнал; задел под рейтинг | Событие фиксируется и видно логисту |
| **6** | Приглашение партнёра; фильтр биржи (D); снять показ всем админам | Без партнёрства чужих заявок нет |
| **7** | Привязка к `customerOrderId` из ЛК (С4.7) | Дубль из портала заказчика невозможен |

---

## 7. Решения на согласование

| ID | Вопрос | Рекомендация | Статус |
|----|--------|--------------|--------|
| **A** | Похожий груз (не точный дубль) при выставлении | OK **первого логиста** или комментарий | ⬜ |
| **B** | Один перевозчик — два похожих открытых рейса | **Всегда запрет** | ⬜ |
| **C** | Смена адреса → совпадение с чужой заявкой | **Автoblock** без экрана перевозчика | ⬜ |
| **D** | Точность geohash | **1 км** | ⬜ |
| **E** | Второе назначение, если у другой фирмы уже назначен похожий груз | **Жёсткий стоп** | ⬜ |
| **F** | При сохранении второй заявки | **Предупреждение** + «не создавать» / «другой рейс» | ⬜ |

---

## 8. Что не входит в этот план

- Открытая биржа «как ATI для всех» без партнёрства.
- GPS-контроль «стоял ли на адресе».
- Автовыбор «победителя» между двумя логистами.
- Автовозврат на биржу при отказе перевозчика от смены маршрута.

---

## 9. Текущее состояние кода (31.08.2026)

| Возможность | Статус |
|-------------|--------|
| Биржа, claim, `transportApp` | ✅ |
| Контакты водителю до «Выехал» скрыты | ✅ |
| Нельзя забрать свой же заказ | ✅ |
| Отпечаток груза / антидубль | ⬜ |
| Блок маршрута после claim | ⬜ |
| Запрос смены маршрута | ⬜ |
| Конфликт груза (два логиста — два перевозчика) | ⬜ |
| Маска адреса на доске | ⬜ |
| Биржа только для партнёров | ⬜ |
| Привязка дубля к заявке ЛК | ⬜ |

---

## 10. Связанные документы

- `ADMIN_PLAN.md` — биржа, claim, рейтинг (⬜)
- `plan-seti.html` — этап D «Перевозчик приглашает перевозчика», тексты ролей
- `CUSTOMER_PLAN.md` — самостоятельная заявка заказчика

**Версия документа:** 1.0 · 31.08.2026
