Перевести ядро на единый контракт источника

Карточка, очередь и отсев работают с SourceItem (SourceRef + SourceContent); Telegram-поля убраны из домена, персистентности, конвейера и wire, остались только в адаптере приёма. Tenant-миграции пересозданы с нуля. Фронт переведён на generic source/content с просмотрщиком вложений.
This commit is contained in:
Rustam Khalimov
2026-09-11 14:58:54 +03:00
parent 770dba7257
commit ebc761d40e
94 changed files with 4150 additions and 9398 deletions
+8 -7
View File
@@ -232,16 +232,13 @@
"local": false, // создана локально, без внешнего источника
"title": "Разработка интернет-магазина", // string ≤140
"summary": "Компания: …\nЗадача: …",// блок «О заявке»
"source": { "kind": "telegram", "displayName": "Канал заказов", "originRef": "123456789", "receivedAt": 1726000000000 },
"sourceMsg": "Ищу разработчика…", // исходное сообщение
"sourceDialogId": "123456789",
"sourceMsgId": 4242,
"source": { "kind": "telegram", "externalId": "4242", "displayName": "Канал заказов", "originRef": "123456789", "author": "…", "receivedAt": "2026-09-11T10:00:00+00:00", "extra": {"hue": "#8b8ff8"} },
"content": { "text": "Ищу разработчика…", "html": null, "author": "…", "subject": null, "data": [], "links": [], "contacts": [] },
"stack": ["vue", "dotnet"],
"budget": {"from": 100000, "to": 200000, "cur": "RUB"},
"converted": {"from": 100000, "to": 200000, "cur": "RUB"},
"contact": "@client",
"contacts": [{"type": "tg", "value": "@client"}],
"channel": {"name": "Канал заказов", "handle": "@orders", "hue": "#8b8ff8"}, // прежний ch
"matchHits": [{"label": "Стек", "term": "vue", "word": null}],
"comments": [{"id": "cm_…", "by": "Вы", "text": "Позвонил", "time": "5 мин"}],
"links": [{"id": "pl_…", "name": "Бриф", "url": "https://example.com"}],
@@ -257,11 +254,15 @@
}
```
Ключевые поля: `id/containerId/(col)` — принадлежность; `source/sourceMsg/sourceDialogId/sourceMsgId`
происхождение; `stack/budget/converted/contact/contacts/channel/matchHits` — данные заявки; `comments/
Ключевые поля: `id/containerId/(col)` — принадлежность; `source` (`SourceRef`: вид, внешний id, подпись,
ссылка на оригинал, цвет в `extra.hue`) и `content` (`SourceContent`: текст, разметка, ссылки, контакты,
вложения `data`) — происхождение; `stack/budget/converted/contact/contacts/matchHits` — данные заявки; `comments/
links/files/history/tzText/reminder` — модули; `isNew/prevCol/isVacancy/isVacancyKnown` — маркеры. Ровно
один из полей `history[].type`/`history[].stage` задан.
Строки очереди и отсева «Обработки» отдают тот же generic `source`/`content` (раньше — `ch`); решение
отсева — `decidedBy`/`decidedByLabel` (stop|ml|ai|stale|dup).
**counts** (`GET /api/cards/counts`): `{new: int, "<containerId>": {count: int, new: int}, learning: int, ml: int, ai: int}` — плоская форма (совместима с прежним `/api/leads/counts`).
### 4.2 Контейнер (container) — `GET /api/containers`, `POST/PATCH` тела