Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
using Deal.Contracts.Integrations.Models;
|
||||
using Deal.Modules.Kanban.Application;
|
||||
using Deal.Modules.Kanban.Application.Models;
|
||||
using Deal.Modules.Pipeline.Application.Models;
|
||||
|
||||
namespace Deal.Modules.Pipeline.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Создание карточки пайплайна через публичный интерфейс Kanban (план Task 7 L400–402, Ruling 3/4;
|
||||
/// python _store_lead L483–514: INSERT карточки → UPDATE dedup.lead_id → чтение после записи).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Тонкая обёртка создания: генерирует id карточки (<c>c_</c>, <see cref="PrefixId"/>/<see cref="KanbanIdPrefixes"/> —
|
||||
/// переиспользуем генератор владельца), собирает полный снимок через <see cref="CardComposer"/> и пишет его
|
||||
/// <see cref="ICardStore.AddCardAsync"/>, после чего связывает заявку дедупа с карточкой
|
||||
/// (<see cref="IPipelineStore.LinkAsync"/> — порядок AddCard → Link 1:1 с Ruling 4, L512–513) и перечитывает
|
||||
/// полный <see cref="CardDto"/> (для SSE new_card, Ruling 8/9).
|
||||
/// <para>
|
||||
/// Проверку «уже есть карточка по дедупу» обёртка НЕ дублирует — её делает воркер на «new»-проходе pump
|
||||
/// (Ruling 8, L940–951): к моменту записи сообщение уже прошло дедуп-гвард и его хэш заявлен (LeadId=null).
|
||||
/// Транзакционности карточка + dedup-link нет (как в прототипе — два отдельных statement'а адаптеров на общем
|
||||
/// TenantDbContext, Ruling 3/4): сбой LinkAsync оставляет карточку без связи и пробрасывается вызывающему.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class PipelineCardWriter(ICardStore kanjStore, IPipelineStore pipelineStore, CardComposer composer)
|
||||
{
|
||||
/// <summary>
|
||||
/// Создаёт карточку из разобранного сообщения и связывает её с заявкой дедупа.
|
||||
/// </summary>
|
||||
/// <param name="parsed">Разбор сообщения (ИИ/локальный путь; колонка разбора пройдёт ContainerAccepts-страховку).</param>
|
||||
/// <param name="message">Строка очереди с сообщением-источником (метаданные канала/текст/время).</param>
|
||||
/// <param name="dedupHash">SHA1-hex хэша текста (заявка дедупа уже создана воркером, Ruling 8).</param>
|
||||
/// <param name="ct">Токен отмены.</param>
|
||||
/// <returns>Полная карточка (чтение после записи — §4.1, как lead_to_dict после INSERT, L514).</returns>
|
||||
/// <exception cref="InvalidOperationException">Карточка не прочиталась сразу после создания.</exception>
|
||||
public async Task<CardDto> CreateCardAsync(
|
||||
AiParsedCardDto parsed,
|
||||
QueueItemDto message,
|
||||
string dedupHash,
|
||||
CancellationToken ct)
|
||||
{
|
||||
string cardId = PrefixId.New(KanbanIdPrefixes.Card);
|
||||
CardSnapshot snapshot = await composer.BuildAsync(parsed, message, cardId, ct);
|
||||
await kanjStore.AddCardAsync(snapshot, ct);
|
||||
await pipelineStore.LinkAsync(dedupHash, cardId, ct);
|
||||
return await kanjStore.GetCardAsync(cardId, ct)
|
||||
?? throw new InvalidOperationException("Карточка не прочиталась после создания: " + cardId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user