Files
Deal/src/core/Deal.Modules.Pipeline/Application/PipelineCardWriter.cs
T
Rustam Khalimov 9e07568ddd Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы
ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ,
инструкция пользователя, техдокументация, код-стайл), бэклог,
скрипты развёртывания и архив прототипа LeadRadar.
2026-09-11 02:50:17 +03:00

50 lines
3.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}