Bot Telegram
Botul Telegram al ASYDATA e un bot multi-tenant care leagă un chat de o firmă (prin token), trimite
carduri interactive de e-Factură (primite + emise) cu butoane de acțiune și rulează un flux de
aprobare de permisiuni. Sursa de referință e docs/modules/telegram.md; codul trăiește în worker-ul
src/Workers/Asy.Workers.Telegram/, domeniul în src/Asy.Domain/Telegram/, iar webhook-ul + REST-ul de
management în src/Asy.WebApi/Endpoints/TelegramEndpoints.cs.
Principiul cheie: decuplare prin evenimente. Sync-ul de e-Factură nu cheamă direct Telegram — ridică un eveniment de domeniu care trece prin outbox → Kafka. Vezi Flux event-driven.
Decuplarea event-driven
Secțiune intitulată „Decuplarea event-driven”EfacturaService.SyncInvoicesAsync (factură nouă / status schimbat) └─ invoice.RaiseEFacturaReceived() / RaiseEFacturaStatusChanged() / RaiseIssued() └─ OutboxSaveChangesInterceptor → tabela outbox_events └─ OutboxRelayService (poll 500ms) → Kafka topic `firm.events` ├─ FirmEventsConsumer (group `telegram-worker`) → card Telegram └─ WebPushConsumer (group `webpush-worker`) → web pushFirmEventsConsumer (src/Workers/Asy.Workers.Telegram/FirmEventsConsumer.cs, ConsumerGroup = "telegram-worker") consumă firm.events și dispatch-uiește pe tipul evenimentului. Evenimentele de
factură și de permisiuni sunt în src/Asy.Domain/Telegram/Events/PermissionEvents.cs:
InvoiceReceivedEvent, InvoiceEFacturaStatusChangedEvent, InvoiceIssuedEvent,
Permission{Requested,Approved,Denied}Event.
Legarea chat ↔ firmă (bind)
Secțiune intitulată „Legarea chat ↔ firmă (bind)”Un chat se leagă de o firmă printr-un token de unică folosință. Comanda /start <token> (în grup
/start@botname <token>, normalizat prin strip @botname):
- token fără prefix → bind de firmă (
TelegramBindToken); - token cu prefix
P-→ bind de partener (PartnerTelegramBindToken).
1 chat = 1 firmă, last-bind-wins. Entitățile sunt în src/Asy.Domain/Telegram/TelegramChat.cs
(TelegramChat, TelegramBindToken).
Cardul de e-Factură
Secțiune intitulată „Cardul de e-Factură”Cardul are două variante (renderere separate):
- primit —
src/Asy.Infrastructure/Telegram/EfacturaCardRenderer.cs; - emis —
src/Asy.Infrastructure/Telegram/InvoiceIssuedCardRenderer.cs.
Callback data are formatul efactura:{invoiceId}:{action}, cu action ∈ approve | decline | unset | pdf | attach | queue. Dispatch-ul partajat (poller + webhook) e în
src/Asy.Infrastructure/Telegram/TelegramEfacturaCallbackHandler.cs.
| Buton | Acțiune | Efect |
|---|---|---|
| Aprobă / Respinge | approve / decline | aprobare internă (toggle) pe Invoice.InternalApprovalStatus; nu atinge SFS |
| (aprobat/respins) | unset | revine la cele 2 butoane |
| Vezi PDF | pdf | SendDocument ca reply la card |
| Vezi atașament | attach | SendDocument (dacă există anexă) |
| Semnează (MSign) | buton-URL | redirect spre semnare (vezi mai jos) |
| Plătește | queue | pune plata în coadă |
Aprobatorul și semnatarul pot diferi: cardul arată „Aprobată intern de X (Telegram)” + „Semnată la SFS de
Y” (din Invoice.EFacturaSignedByName).
Butonul MSign
Secțiune intitulată „Butonul MSign”MSign nu e callback, ci buton-URL (WithUrl) spre
GET /api/telegram/msign/{invoiceId}?t={hmac}. Endpoint-ul (TelegramEndpoints.cs) validează token-ul
HMAC (MsignLinkToken, HMAC-SHA256 cu Telegram:WebhookSecret, comparație în timp constant), inițiază
LAZY cererea MSign și face 302 spre MSign. Un tap, zero mesaje noi în chat.
Re-editarea cardului
Secțiune intitulată „Re-editarea cardului”La schimbarea statusului (semnat / respins / plătit), InvoiceEFacturaStatusChangedEvent ajunge la
consumer, care re-editează cardul existent (EditMessageText), direcție-aware: card primit
(EfacturaCardRenderer) vs card emis (InvoiceIssuedCardRenderer). Asociația
(InvoiceId, ChatId) → MessageId permite editarea din orice sursă.
Webhook (prod) vs poller (dev)
Secțiune intitulată „Webhook (prod) vs poller (dev)”| Mediu | Mecanism | Config |
|---|---|---|
| dev | long-poll | Telegram:PollingEnabled = true (implicit) — TelegramPollerService |
| prod | webhook | Telegram:PollingEnabled = false, Telegram:WebhookSecret obligatoriu |
Webhook-ul e POST /api/telegram/webhook, validat cu header-ul X-Telegram-Bot-Api-Secret-Token.
Callback-urile pe ambele căi folosesc același ITelegramEfacturaCallbackHandler.
Entitățile
Secțiune intitulată „Entitățile”| Entitate / tabel | Rol |
|---|---|
telegram_chats (TelegramChat) | chat legat de firmă + EventsEnabledJson (toggle per tip de eveniment) |
telegram_card_messages (TelegramCardMessage) | (InvoiceId, ChatId) unic → MessageId, pentru editarea cardului |
telegram_bind_tokens (TelegramBindToken) | token de bind de firmă |
telegram_chat_members (TelegramChatMember) | membri + rol + permisiuni granulare (PermissionsJson) |
permission_requests (PermissionRequest) | starea fluxului de aprobare |
partner_telegram_* | bind + chat-uri de partener (prefix P-) |
EventsEnabledJson e un JSON de tip { "invoiceNew": true, "syncError": false, … }; cheia lipsă =
implicit activată pentru tipurile noi de eveniment.
Fluxul de aprobare a permisiunilor
Secțiune intitulată „Fluxul de aprobare a permisiunilor”Când un user fără drept declanșează o acțiune sensibilă (ex. efactura.sign), se ridică
PermissionRequestedEvent → card de aprobare cu butoanele Aprob/Refuz (approval:{requestId}:{approve|deny}).
Decizia ridică PermissionApprovedEvent / PermissionDeniedEvent, iar consumerul re-editează cardul cu
rezultatul.
- Flux event-driven — outbox → Kafka → consumeri
- MSign — semnarea de pe card
- e-Factură (SFS) — de unde vin facturile
- Notificări — celelalte canale alimentate de
firm.events - Comunicare și semnare — locul botului în business flow
- Schema vizuală pe Figma