Integračná príručka: Napojenie tretej strany na QHUB a prijímanie/odosielanie Peppol dokumentov cez APEX

Verzia: 1.0  •  Dátum: 2026-04-16
Určené pre: Vývojárov externých systémov (ERP, účtovné systémy, portály), ktorí sa chcú napojiť na QCloud HUB a vymieňať Peppol dokumenty (faktúry, …) prostredníctvom APEX Access Pointu.

1. Ako funguje integrácia

Celkový pohľad

☁ ASOL CLOUD 🖥️ Váš systém ERP · Účtovný systém · Portál konzumer & producent správ 🐇 RabbitMQ AMQPS · async správy QCloud HUB Synchronization Service Smerovanie · Retry & DLQ · Audit · Transakcie HUB APEX Peppol Access Point (Java) SMP Lookup · AS4 · Validácia UBL AS4 AS4 / HTTPS 🌐 Peppol Network Európska eInvoicing sieť response / delivery

Čo QHUB robí za vás

Čo musíte urobiť vy

SmerVaša zodpovednosť
Prijímanie (Peppol → váš systém)Konzumovať správy z vašej delivery queue; spracovať UBL XML; odoslať response naspäť.
Odosielanie (váš systém → Peppol)Pripraviť UBL XML dokument; odoslať synchronizačnú správu do sync queue; sledovať stav.

2. Predpoklady a onboarding

2.1 Čo potrebujete pred začatím

  1. Peppol participant ID – váš klient musí byť zaregistrovaný v Peppol sieti. Identifikátor má formát {schemeId}:{participantId}, napríklad 0245:2024000001.

    Schéma 0245 = slovenský DIČ v Peppol. Ak neviete svoje Peppol ID, kontaktujte vášho Peppol Service Provider (SMP).

  2. ASOL kontakt – požiadajte ASOL tím o:
    • pridelenie SystemCode vášho systému (napr. MYERP),
    • pridelenie TenantId (UUID) vášho tenanta v QHUB,
    • prístup k QHUB Registration API (URL + API kľúč).
  3. RabbitMQ klient – váš systém musí vedieť komunikovať s RabbitMQ (AMQP 0-9-1 protokol). Odporúčame:
    • Java: Spring AMQP / RabbitMQ Java Client
    • .NET: MassTransit / RabbitMQ.Client
    • Python: pika
    • Node.js: amqplib

2.2 Onboarding kroky (prehľad)

postup
1. Dohodnutie SystemCode + TenantId s ASOL
         ↓
2. Odoslanie popisu entity PeppolDocument do QHUB Registration API
         ↓
3. Administrátor ASOL vygeneruje jednorazový registračný kľúč
         ↓
4. Váš systém odošle kľúč na Registration API → získa RabbitMQ credentials
         ↓
5. Nakonfigurujete RabbitMQ v svojom systéme
         ↓
6. Otestujete end-to-end komunikáciu

3. Registrácia systému v QHUB

Registrácia prebieha v dvoch krokoch cez HTTP REST API.

3.1 Odoslanie popisu entity

Pred vytvorením registrácie musíte QHUB informovať, aké entity váš systém podporuje. Pre Peppol dokumenty ide o entitu PeppolDocument.

Endpoint:

http
POST {QHUB_REGISTRATION_URL}/api/v1/SyncEntityCatalogItem/StoreSyncEntityItems
Content-Type: application/json
X-Api-Key: {váš API kľúč}

Request body:

json
{
  "System": "MYERP",
  "SyncEntityDatas": [
    {
      "EntityCode": "PeppolDocument",
      "EntityType": 100,
      "DefaultAllowed": true,
      "Version": "1.0.0",
      "SyncEntityDefinitionData": {
        "SyncPropertiesData": [
          { "Name": "SenderParticipantId",   "OriginalName": "senderParticipantId",   "ProperyType": "String",   "Required": true,  "IsBusinessKey": false },
          { "Name": "ReceiverParticipantId", "OriginalName": "receiverParticipantId", "ProperyType": "String",   "Required": true,  "IsBusinessKey": false },
          { "Name": "DocumentTypeId",        "OriginalName": "documentTypeId",        "ProperyType": "String",   "Required": true,  "IsBusinessKey": false },
          { "Name": "ProcessId",             "OriginalName": "processId",             "ProperyType": "String",   "Required": true,  "IsBusinessKey": false },
          { "Name": "UblDocument",           "OriginalName": "ublDocument",           "ProperyType": "String",   "Required": true,  "IsBusinessKey": false },
          { "Name": "As4MessageId",          "OriginalName": "as4MessageId",          "ProperyType": "String",   "Required": false, "IsBusinessKey": true  },
          { "Name": "ReceivedAt",            "OriginalName": "receivedAt",            "ProperyType": "DateTime", "Required": false, "IsBusinessKey": false }
        ],
        "SyncForeignKeysData": []
      }
    }
  ]
}

EntityType: 100 – presná hodnota bude pridelená ASOL tímom. Použite hodnotu, ktorú dostanete od ASOL pri onboardingu. Číslo musí byť konzistentné medzi všetkými systémami.

Úspešná odpoveď: HTTP 200, body true

Toto volanie opakujte pri každom štarte vašej aplikácie aj pri každej zmene schémy (a zvýšte pritom Version).

3.2 Validácia registračného kľúča

Administrátor ASOL vám cez zabezpečený kanál (napr. email, Jira) odovzdá jednorazový registračný kľúč (platný 24 hodín).

Endpoint:

http
POST {QHUB_REGISTRATION_URL}/api/v1/Registration/Validate
Content-Type: application/json

Request body:

json
{
  "ParticipantSystem": "MYERP",
  "ParticipantTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "RegistrationKey": "REG-KEY-ODOSLANY-ADMINOM",
  "RoutingKey": "MYERP.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

RoutingKey – pre systémy mimo ASOL Cloud (3ST) má formát {SystemCode}.{TenantId}. Tento kľúč identifikuje vašu delivery queue.

Úspešná odpoveď (HTTP 200):

json
{
  "ParticipantSystem": "MYERP",
  "ParticipantTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "RabbitMqUrl": "amqps://rmq.qcloud.assecosoldevsk.com:5671",
  "RabbitMqName": "myerp_tenant_abc123",
  "RabbitMqPass": "••••••••••••",
  "ErrorCode": null,
  "Success": true,
  "RegisteredEntities": [
    {
      "EntityType": 100,
      "OwnerSystem": "APEX",
      "OwnerTenantId": "...",
      "RoutingKey": "MYERP.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "Operations": 1,
      "Direction": 0,
      "SyncMode": 0,
      "AllowedFrom": "2026-04-16T00:00:00Z",
      "AllowedTo": "2027-04-16T00:00:00Z",
      "DeliverySystem": "MYERP",
      "DeliveryTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }
  ]
}

⚠️ Bezpečnosť: RabbitMqPass je citlivý údaj. Uložte ho do Kubernetes Secret, HashiCorp Vault, alebo iného bezpečného úložiska. Nikdy ho nevkladajte priamo do zdrojového kódu ani do konfiguračných súborov v repozitári.

Chybové kódy:

KódPopis
RegCodeNotFoundKľúč neexistuje alebo bolo nájdených viacero záznamov
RegCodeExpiredKľúč expiroval (platný 24h)
SystemMismatchParticipantSystem sa nezhoduje s registráciou
TenantMismatchParticipantTenantId sa nezhoduje s registráciou
HubInternalErrorInterná chyba QHUB

4. Nastavenie RabbitMQ pripojenia

Po úspešnej registrácii získate prihlasovacie údaje k RabbitMQ. Pripojenie prebieha cez AMQPS (TLS, port 5671).

4.1 Queues a exchanges

Musíte pracovať s týmito frontami a výmennými miestami:

NázovTypÚčel
clouderp-delivery-queue.MYERP.{TenantId}Direct queuePríjem správ z QHUB do vášho systému
clouderp.syncFanout exchangeOdosielanie správ z vášho systému do QHUB
clouderp.sync.responseFanout exchangeOdosielanie odpovede o výsledku spracovania
clouderp.deliveryDirect exchangeZdrojová exchange, z ktorej QHUB plní delivery queue

4.2 Oprávnenia vášho RabbitMQ účtu

Váš účet má oprávnenia:

OperáciaZdroj
Configure + Readclouderp-delivery-queue.MYERP.{TenantId}
Writeclouderp.sync (na odosielanie dokumentov do Peppolu)
Writeclouderp.sync.response (na odosielanie response)

4.3 Deklarácia vašej delivery queue

Vašu delivery queue musí deklarovať váš systém (nie QHUB). Deklarujte ju pred prvým consume:

config
Meno:       clouderp-delivery-queue.MYERP.{TenantId}
Durable:    true
Auto-delete: false
Arguments:
  x-dead-letter-exchange: clouderp.dlq   (odporúčané)

Binding na exchange:

config
Exchange:    clouderp.delivery
Type:        direct
Routing key: MYERP.{TenantId}

5. Prijímanie dokumentov z Peppolu

Keď APEX prijme Peppol dokument adresovaný vášmu klientovi, QHUB ho doručí na vašu delivery queue ako správu SynchronizationMessage.

5.1 Tok prijímania

diagram
Peppol odosielateľ
    → AS4 → APEX
        → QHUB (SynchronizationMessage, EntityType=PeppolDocument)
            → clouderp-delivery-queue.MYERP.{TenantId}
                → VÁŠ KONZUMER
                    → spracujete UBL XML (importujete faktúru)
                        → SynchronizationResponseMessage → clouderp.sync.response

5.2 Čo dostanete – SynchronizationMessage

Správa prichádza ako JSON objekt (pozri sekciu 7):

json
{
  "TransactionId": 123456,
  "EventType": 1,
  "EntityType": 100,
  "RoutingKey": "MYERP.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "OwnerTenantId": "apex-tenant-uuid",
  "OwnerSystem": "APEX",
  "DeliverySystem": "MYERP",
  "DeliveryTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "SyncKey": "550e8400-e29b-41d4-a716-446655440000",
  "SchemaVersion": "1.0.0",
  "Environment": "prod",
  "DomainSyncVersion": 1,
  "OccurredAt": "2026-04-16T10:30:00+02:00",
  "Data": "{\"SenderParticipantId\":\"0245:2024000001\",\"ReceiverParticipantId\":\"0245:9999999999\",\"DocumentTypeId\":\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1\",\"ProcessId\":\"urn:fdc:peppol.eu:2017:poacc:billing:01:1.0\",\"UblDocument\":\"<?xml version=\\\"1.0\\\"...>\",\"As4MessageId\":\"uuid@peppol\",\"ReceivedAt\":\"2026-04-16T10:29:58+02:00\"}",
  "IgnoredProperties": null
}

Pole Data je JSON string (serializovaný ešte raz) obsahujúci entitu PeppolDocument. Je nutné ho deserializovať zvlášť.

5.3 Čo musíte urobiť po prijatí

  1. Deserializujte Data na PeppolDocument.
  2. Skontrolujte DeliverySystem a DeliveryTenantId – mali by zodpovedať vášmu systému.
  3. Importujte UblDocument (UBL 2.1 XML) do vášho systému.
  4. Odošlite odpoveď SynchronizationResponseMessage na exchange clouderp.sync.response.

5.4 Odoslanie odpovede (povinné)

Po každom spracovaní správy musíte odoslať odpoveď späť do QHUB. Bez odpovede QHUB nevie, či bola správa spracovaná.

json
{
  "TransactionId": 123456,
  "Result": {
    "Success": true,
    "ErrorCode": null,
    "ErrorParams": null
  }
}

Pri chybe:

json
{
  "TransactionId": 123456,
  "Result": {
    "Success": false,
    "ErrorCode": "ImportFailed",
    "ErrorParams": ["Duplicitná faktúra INV-2026-001"]
  }
}

Dôležité: Odpoveď odošlite vždy – aj pri chybe. Ak odpoveď neodošlete, QHUB môže správu po čase znovu doručiť (retry).


6. Odosielanie dokumentov do Peppolu

Ak chcete odoslať dokument (napr. faktúru) cez Peppol, odošlite SynchronizationMessage s entitou PeppolDocument do exchange clouderp.sync. QHUB správu doručí APEX-u, ktorý vykoná AS4 odoslanie.

6.1 Tok odosielania

diagram
VÁŠ SYSTÉM
    → SynchronizationMessage (EntityType=PeppolDocument)
        → clouderp.sync (fanout exchange)
            → QHUB Synchronization Service
                → APEX (clouderp-delivery-queue.APEX)
                    → AS4 / HTTPS
                        → Peppol príjemca

QHUB → SynchronizationResponseMessage → clouderp.sync.response
    → VÁŠ konzumer odpovede (voliteľné, ale odporúčané)

6.2 Čo musíte odoslať

Odošlite SynchronizationMessage na exchange clouderp.sync (bez routing key – ide o fanout):

json
{
  "TransactionId": 0,
  "EventType": 1,
  "EntityType": 100,
  "RoutingKey": "MYERP.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "OwnerTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "OwnerSystem": "MYERP",
  "DeliverySystem": null,
  "DeliveryTenantId": null,
  "SyncKey": "novy-uuid-pre-tuto-spravu",
  "SchemaVersion": "1.0.0",
  "Environment": "prod",
  "DomainSyncVersion": 1,
  "OccurredAt": "2026-04-16T10:30:00+02:00",
  "Data": "{\"SenderParticipantId\":\"0245:2024000001\",\"ReceiverParticipantId\":\"0245:9999999999\",\"DocumentTypeId\":\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1\",\"ProcessId\":\"urn:fdc:peppol.eu:2017:poacc:billing:01:1.0\",\"UblDocument\":\"<?xml version=\\\"1.0\\\"...>\"}",
  "IgnoredProperties": null
}

TransactionId: 0 – pri odosielaní ho nastavte na 0; QHUB pridelí vlastný TransactionId pri spracovaní.

SyncKey – vygenerujte nové UUID pre každú správu. Je to trvalý identifikátor entity v QHUB synchronizácii.

DomainSyncVersion – začnite od 1. Pri každej ďalšej zmene tej istej entity zvýšte o 1 (Lamport timestamp).

6.3 Sledovanie výsledku odoslania (voliteľné)

QHUB vám odpíše výsledok AS4 odoslania cez clouderp.sync.response. Ak chcete sledovať úspech/neúspech doručenia, musíte konzumovať odpovede.

Poznámka: Odpoveď od QHUB dostanete dvakrát – raz keď APEX zaradí dokument do fronty (optimistický úspech), a druhýkrát po skutočnom AS4 odoslaní. Záleží na tom, ako APEX implementuje callback.


7. Štruktúry správ

Všetky správy sú JSON s PascalCase názvami vlastností (kompatibilné s .NET serializáciou).

7.1 SynchronizationMessage

Správa používaná v oboch smeroch (odosielanie aj prijímanie).

PoleTypPopisPovinné
TransactionIdlongIdentifikátor záznamu v QHUB transakčnej tabuľkeáno
EventTypeintTyp udalosti: 1=CREATE, 2=UPDATE, 4=DELETEáno
EntityTypeintTyp entity: 100=PeppolDocument (TBD)áno
RoutingKeystringRouting key príjemcu (MYERP.{TenantId})nie
OwnerTenantIdstring (UUID)Tenant zdrojového systémuáno
OwnerSystemstringKód zdrojového systému (napr. APEX, MYERP)áno
DeliverySystemstringKód cieľového systému (vypĺňa QHUB)nie
DeliveryTenantIdstring (UUID)Tenant cieľového systému (vypĺňa QHUB)nie
SyncKeystring (UUID)Unikátny trvalý identifikátor entityáno
SchemaVersionstringVerzia schémy entity (SemVer, napr. "1.0.0")áno
EnvironmentstringProstredie: "prod", "test"áno
DomainSyncVersionintVerzia zmeny entity (Lamport timestamp, ≥1)áno
OccurredAtISO 8601 datetimeČas vzniku udalosti vo zdrojovom systémeáno
Datastring (JSON)Serializovaná entita PeppolDocumentáno
IgnoredPropertiesstring[]Vlastnosti, ktoré cieľ nemá ignorovať (vypĺňa QHUB)nie

7.2 SynchronizationResponseMessage

Odpoveď, ktorú váš systém odošle po spracovaní prijatej správy.

PoleTypPopis
TransactionIdlongSkopírované z prijatej SynchronizationMessage
Result.Successbooleantrue = spracovanie prebehlo úspešne
Result.ErrorCodestringKód chyby (ak Success=false)
Result.ErrorParamsstring[]Doplňujúce parametre chyby

8. Entita PeppolDocument

Entita, ktorá sa prenáša v poli Data správy SynchronizationMessage.

8.1 Schéma (verzia 1.0.0)

PoleTypPovinnéBusiness KeyPopis
SenderParticipantIdstringánoniePeppol ID odosielateľa. Formát: {schemeId}:{id}
ReceiverParticipantIdstringánoniePeppol ID príjemcu. Formát: {schemeId}:{id}
DocumentTypeIdstringánoniePeppol Document Type URN (viď prílohu A)
ProcessIdstringánoniePeppol Process URN (viď prílohu A)
UblDocumentstringánonieKompletné UBL 2.1 XML ako string
As4MessageIdstringnieánoAS4 MessageId (prítomné pri inbound správach)
ReceivedAtISO 8601nienieČas príjmu z Peppol siete (inbound)

8.2 Príklad entity

json
{
  "SenderParticipantId": "0245:2024000001",
  "ReceiverParticipantId": "0245:9999999999",
  "DocumentTypeId": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1",
  "ProcessId": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
  "UblDocument": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\">...</Invoice>",
  "As4MessageId": "abc123@receiver.peppol.ap",
  "ReceivedAt": "2026-04-16T10:29:58+02:00"
}

8.3 Peppol Participant ID formáty (najčastejšie)

KrajinaSchémaPríklad
Slovensko (DIČ)02450245:2024000001
Česká republika00600060:12345678
GLN00880088:1234567890123

9. Chybové kódy a spracovanie chýb

9.1 Chyby registrácie

KódPríčinaRiešenie
RegCodeNotFoundNeplatný alebo neexistujúci registračný kľúčVyžiadajte nový kľúč od ASOL admina
RegCodeExpiredKľúč expiroval (>24h)Vyžiadajte nový kľúč
SystemMismatchParticipantSystem nesedíSkontrolujte SystemCode
TenantMismatchParticipantTenantId nesedíSkontrolujte TenantId

9.2 Chyby synchronizácie (QHUB)

Ak QHUB nedokáže spracovať vašu správu, nastaví stav na Failed a zaznamená:

KódPríčina
EventTypeMissing / EventTypeWrongNeplatný EventType
EntityDataMissingChýba pole Data pri CREATE/UPDATE
TenantIdMissingChýba OwnerTenantId
SchemaVersionMissingChýba SchemaVersion
DomainSyncVersionWrongDomainSyncVersion < 1
DeliverySystemWrongDeliverySystem nezodpovedá registrácii

9.3 Vaše chybové kódy (odosielané v odpovedi)

V SynchronizationResponseMessage.Result.ErrorCode môžete použiť ľubovoľný string kód (odporúčame angličtinu, bez medzier). Príklady:

KódKedy použiť
ImportFailedNepodarilo sa importovať dokument do vášho systému
DuplicateDocumentDokument s týmto ID už existuje
ValidationFailedUBL XML zlyhalo vašu biznis validáciu
UnsupportedDocumentTypeTyp dokumentu váš systém nepodporuje
UnexpectedErrorNečakaná chyba

9.4 Dead-Letter Queue

Ak správu nedokážete spracovať ani po opakovaných pokusoch, QHUB ju presunie do Dead-Letter Queue. ASOL admin dostane notifikáciu. Kontaktujte ASOL support na riešenie.


10. Príklady implementácie

10.1 Java (Spring AMQP)

Konfigurácia

java
@Configuration
public class QhubRabbitConfig {

    @Value("${qhub.rabbitmq.host}")      private String host;
    @Value("${qhub.rabbitmq.port:5671}") private int port;
    @Value("${qhub.rabbitmq.username}")  private String username;
    @Value("${qhub.rabbitmq.password}")  private String password;
    @Value("${myapp.tenant-id}")         private String tenantId;

    @Bean
    public ConnectionFactory qhubConnectionFactory() {
        CachingConnectionFactory cf = new CachingConnectionFactory(host, port);
        cf.setUsername(username);
        cf.setPassword(password);
        cf.setVirtualHost("/");
        cf.getRabbitConnectionFactory().useSslProtocol(); // TLS povinné
        return cf;
    }

    @Bean
    public RabbitTemplate qhubRabbitTemplate(ConnectionFactory qhubConnectionFactory) {
        RabbitTemplate rt = new RabbitTemplate(qhubConnectionFactory);
        rt.setMessageConverter(new Jackson2JsonMessageConverter());
        return rt;
    }

    @Bean
    public Queue myDeliveryQueue() {
        return QueueBuilder
            .durable("clouderp-delivery-queue.MYERP." + tenantId)
            .withArgument("x-dead-letter-exchange", "clouderp.dlq")
            .build();
    }

    @Bean
    public DirectExchange deliveryExchange() {
        return new DirectExchange("clouderp.delivery", true, false);
    }

    @Bean
    public Binding myDeliveryBinding(Queue myDeliveryQueue, DirectExchange deliveryExchange) {
        return BindingBuilder.bind(myDeliveryQueue)
            .to(deliveryExchange)
            .with("MYERP." + tenantId);
    }
}

Konzumer (prijímanie)

java
@Slf4j
@Component
@RequiredArgsConstructor
public class PeppolDocumentConsumer {

    private static final int PEPPOL_DOCUMENT_ENTITY_TYPE = 100; // dohodnuté s ASOL

    private final ObjectMapper objectMapper;
    private final RabbitTemplate qhubRabbitTemplate;
    private final MyInvoiceImportService importService; // vaša biznis logika

    @RabbitListener(queues = "#{myDeliveryQueue.name}")
    public void onMessage(SynchronizationMessage message) {
        if (message.getEntityType() != PEPPOL_DOCUMENT_ENTITY_TYPE) {
            return; // neznáma entita, ignorovať
        }

        SyncResult result;
        try {
            PeppolDocument doc = objectMapper.readValue(
                message.getData(), PeppolDocument.class);

            // Importujte UBL XML do vášho systému
            importService.importInvoice(doc.getUblDocument(), doc.getSenderParticipantId());

            result = new SyncResult(true, null, null);
        } catch (DuplicateDocumentException e) {
            log.warn("Duplicate document, transactionId={}", message.getTransactionId());
            result = new SyncResult(false, "DuplicateDocument", new String[]{e.getMessage()});
        } catch (Exception e) {
            log.error("Failed to process PeppolDocument, transactionId={}",
                message.getTransactionId(), e);
            result = new SyncResult(false, "UnexpectedError", new String[]{e.getMessage()});
        }

        // Vždy odošlite odpoveď
        sendResponse(message.getTransactionId(), result);
    }

    private void sendResponse(long transactionId, SyncResult result) {
        SynchronizationResponseMessage response =
            new SynchronizationResponseMessage(transactionId, result);
        qhubRabbitTemplate.convertAndSend("clouderp.sync.response", "", response);
    }
}

Odosielanie dokumentu

java
@Slf4j
@Component
@RequiredArgsConstructor
public class PeppolDocumentSender {

    private static final int    PEPPOL_DOCUMENT_ENTITY_TYPE = 100;
    private static final int    EVENT_TYPE_CREATE            = 1;
    private static final String SCHEMA_VERSION               = "1.0.0";

    private final RabbitTemplate qhubRabbitTemplate;
    private final ObjectMapper objectMapper;

    @Value("${myapp.system-code:MYERP}")
    private String systemCode;

    @Value("${myapp.tenant-id}")
    private String tenantId;

    /**
     * Odošle UBL XML dokument do QHUB → APEX → Peppol.
     */
    public void send(String senderParticipantId,
                     String receiverParticipantId,
                     String documentTypeId,
                     String processId,
                     String ublXml) throws Exception {

        PeppolDocument doc = new PeppolDocument();
        doc.setSenderParticipantId(senderParticipantId);
        doc.setReceiverParticipantId(receiverParticipantId);
        doc.setDocumentTypeId(documentTypeId);
        doc.setProcessId(processId);
        doc.setUblDocument(ublXml);

        SynchronizationMessage msg = new SynchronizationMessage();
        msg.setEventType(EVENT_TYPE_CREATE);
        msg.setEntityType(PEPPOL_DOCUMENT_ENTITY_TYPE);
        msg.setRoutingKey(systemCode + "." + tenantId);
        msg.setOwnerSystem(systemCode);
        msg.setOwnerTenantId(tenantId);
        msg.setSyncKey(UUID.randomUUID().toString());
        msg.setSchemaVersion(SCHEMA_VERSION);
        msg.setEnvironment("prod");
        msg.setDomainSyncVersion(1);
        msg.setOccurredAt(OffsetDateTime.now());
        msg.setData(objectMapper.writeValueAsString(doc));

        qhubRabbitTemplate.convertAndSend("clouderp.sync", "", msg);
        log.info("Document sent to QHUB: sender={}, receiver={}", senderParticipantId, receiverParticipantId);
    }
}

10.2 .NET (MassTransit / RabbitMQ.Client)

Konfigurácia MassTransit

csharp
services.AddMassTransit(config =>
{
    config.AddConsumer<PeppolDocumentConsumer>();

    config.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host(configuration["Qhub:RabbitMq:Host"], "/", h =>
        {
            h.Username(configuration["Qhub:RabbitMq:Username"]);
            h.Password(configuration["Qhub:RabbitMq:Password"]);
            h.UseSsl(s => s.Protocol = SslProtocols.Tls12);
        });

        // Konfigurácia odosielania (sync queue)
        cfg.Message<SynchronizationMessage>(p =>
            p.SetEntityName("clouderp.sync"));

        // Konfigurácia odposielania odpovede
        cfg.Message<SynchronizationResponseMessage>(p =>
            p.SetEntityName("clouderp.sync.response"));

        // Delivery queue s binding na exchange
        cfg.ReceiveEndpoint(
            $"clouderp-delivery-queue.MYERP.{tenantId}", e =>
        {
            e.ConfigureConsumeTopology = false;
            e.UseMessageRetry(r => r.Immediate(1));
            e.Bind("clouderp.delivery", x =>
            {
                x.RoutingKey  = $"MYERP.{tenantId}";
                x.ExchangeType = ExchangeType.Direct;
            });
            e.ConfigureConsumer<PeppolDocumentConsumer>(ctx);
        });
    });
});

Konzumer

csharp
public class PeppolDocumentConsumer : IConsumer<SynchronizationMessage>
{
    private const int PeppolDocumentEntityType = 100;
    private readonly IInvoiceImportService _importService;
    private readonly IPublishEndpoint _publishEndpoint;

    public PeppolDocumentConsumer(IInvoiceImportService importService,
                                   IPublishEndpoint publishEndpoint)
    {
        _importService   = importService;
        _publishEndpoint = publishEndpoint;
    }

    public async Task Consume(ConsumeContext<SynchronizationMessage> context)
    {
        var message = context.Message;
        if (message.EntityType != PeppolDocumentEntityType)
            return;

        SyncResult result;
        try
        {
            var doc = JsonSerializer.Deserialize<PeppolDocument>(message.Data!);

            await _importService.ImportAsync(doc.UblDocument, doc.SenderParticipantId);

            result = SyncResult.Ok();
        }
        catch (DuplicateDocumentException ex)
        {
            result = SyncResult.Fail("DuplicateDocument", new[] { ex.Message });
        }
        catch (Exception ex)
        {
            result = SyncResult.Fail("UnexpectedError", new[] { ex.Message });
        }

        await _publishEndpoint.Publish(
            new SynchronizationResponseMessage(message.TransactionId, result));
    }
}

Odosielanie dokumentu

csharp
public class PeppolDocumentSender
{
    private const int    PeppolDocumentEntityType = 100;
    private const string SchemaVersion            = "1.0.0";

    private readonly IPublishEndpoint _bus;
    private readonly string _systemCode;
    private readonly string _tenantId;

    public PeppolDocumentSender(IPublishEndpoint bus,
                                 IConfiguration config)
    {
        _bus        = bus;
        _systemCode = config["MyApp:SystemCode"]!;
        _tenantId   = config["MyApp:TenantId"]!;
    }

    public async Task SendAsync(string senderParticipantId,
                                string receiverParticipantId,
                                string documentTypeId,
                                string processId,
                                string ublXml,
                                CancellationToken ct = default)
    {
        var doc = new PeppolDocument
        {
            SenderParticipantId   = senderParticipantId,
            ReceiverParticipantId = receiverParticipantId,
            DocumentTypeId        = documentTypeId,
            ProcessId             = processId,
            UblDocument           = ublXml
        };

        var message = new SynchronizationMessage
        {
            EventType         = 1, // CREATE
            EntityType        = PeppolDocumentEntityType,
            RoutingKey        = $"{_systemCode}.{_tenantId}",
            OwnerSystem       = _systemCode,
            OwnerTenantId     = _tenantId,
            SyncKey           = Guid.NewGuid().ToString(),
            SchemaVersion     = SchemaVersion,
            Environment       = "prod",
            DomainSyncVersion = 1,
            OccurredAt        = DateTimeOffset.UtcNow,
            Data              = JsonSerializer.Serialize(doc)
        };

        await _bus.Publish(message, ct);
    }
}

10.3 Python (pika)

python
import json
import ssl
import uuid
from datetime import datetime, timezone
import pika

QHUB_HOST      = "rmq.qcloud.assecosoldevsk.com"
QHUB_PORT      = 5671
QHUB_USER      = "myerp_tenant_abc123"
QHUB_PASS      = "••••••••"
MY_SYSTEM_CODE = "MYERP"
MY_TENANT_ID   = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
ENTITY_TYPE    = 100  # PeppolDocument

DELIVERY_QUEUE  = f"clouderp-delivery-queue.{MY_SYSTEM_CODE}.{MY_TENANT_ID}"
SYNC_EXCHANGE   = "clouderp.sync"
RESPONSE_EXCHANGE = "clouderp.sync.response"


def connect():
    ssl_ctx = ssl.create_default_context()
    credentials = pika.PlainCredentials(QHUB_USER, QHUB_PASS)
    params = pika.ConnectionParameters(
        host=QHUB_HOST, port=QHUB_PORT,
        credentials=credentials,
        ssl_options=pika.SSLOptions(ssl_ctx, QHUB_HOST)
    )
    return pika.BlockingConnection(params)


# ── Prijímanie dokumentov ──────────────────────────────────────────────────

def on_message(channel, method, properties, body):
    msg = json.loads(body)
    if msg.get("EntityType") != ENTITY_TYPE:
        channel.basic_ack(delivery_tag=method.delivery_tag)
        return

    transaction_id = msg["TransactionId"]
    try:
        doc = json.loads(msg["Data"])
        ubl_xml = doc["UblDocument"]

        # --- vaša biznis logika ---
        import_invoice(ubl_xml, doc["SenderParticipantId"])

        result = {"Success": True, "ErrorCode": None, "ErrorParams": None}
    except Exception as e:
        result = {"Success": False, "ErrorCode": "UnexpectedError", "ErrorParams": [str(e)]}

    response = {"TransactionId": transaction_id, "Result": result}
    channel.basic_publish(
        exchange=RESPONSE_EXCHANGE, routing_key="",
        body=json.dumps(response),
        properties=pika.BasicProperties(content_type="application/json")
    )
    channel.basic_ack(delivery_tag=method.delivery_tag)


def start_consuming():
    conn    = connect()
    channel = conn.channel()
    channel.queue_declare(queue=DELIVERY_QUEUE, durable=True,
                          arguments={"x-dead-letter-exchange": "clouderp.dlq"})
    channel.queue_bind(queue=DELIVERY_QUEUE,
                       exchange="clouderp.delivery",
                       routing_key=f"{MY_SYSTEM_CODE}.{MY_TENANT_ID}")
    channel.basic_consume(queue=DELIVERY_QUEUE, on_message_callback=on_message)
    channel.start_consuming()


# ── Odosielanie dokumentov ──────────────────────────────────────────────────

def send_document(sender_id, receiver_id, document_type_id, process_id, ubl_xml):
    doc = {
        "SenderParticipantId":   sender_id,
        "ReceiverParticipantId": receiver_id,
        "DocumentTypeId":        document_type_id,
        "ProcessId":             process_id,
        "UblDocument":           ubl_xml,
    }
    message = {
        "TransactionId":     0,
        "EventType":         1,
        "EntityType":        ENTITY_TYPE,
        "RoutingKey":        f"{MY_SYSTEM_CODE}.{MY_TENANT_ID}",
        "OwnerSystem":       MY_SYSTEM_CODE,
        "OwnerTenantId":     MY_TENANT_ID,
        "SyncKey":           str(uuid.uuid4()),
        "SchemaVersion":     "1.0.0",
        "Environment":       "prod",
        "DomainSyncVersion": 1,
        "OccurredAt":        datetime.now(timezone.utc).isoformat(),
        "Data":              json.dumps(doc),
    }
    conn    = connect()
    channel = conn.channel()
    channel.basic_publish(
        exchange=SYNC_EXCHANGE, routing_key="",
        body=json.dumps(message),
        properties=pika.BasicProperties(content_type="application/json",
                                        delivery_mode=2)  # persistent
    )
    conn.close()


def import_invoice(ubl_xml: str, sender_id: str):
    # Vaša implementácia importu
    pass

11. Časté otázky

Ako zistím, že mi prišla faktúra?
Nastavte konzumera na delivery queue clouderp-delivery-queue.MYERP.{TenantId}. Správa s EntityType=100 a OwnerSystem=APEX je dokument z Peppol siete.

Môžem odoslať aj iné typy dokumentov ako faktúra?
Áno. UblDocument môže obsahovať akýkoľvek Peppol BIS 3.0 dokument (kreditnú nótu, objednávku, …). Stačí správne nastaviť DocumentTypeId a ProcessId (viď prílohu A).

Čo ak odoslanie cez Peppol zlyhá?
APEX automaticky zopakuje odoslanie (exponenciálny backoff, max 5 pokusov). Výsledok dostanete cez SynchronizationResponseMessage so Success=false a príslušným ErrorCode.

Je UBL XML validované?
Áno, APEX validuje každý dokument pred odoslaním (XSD schéma, EN 16931 Schematron, Peppol BIS 3.0 pravidlá). Ak dokument nie je validný, dostanete Success=false.

Aký je Peppol schéma kód pre Slovensko?
Slovenský IČO v Peppol sieti používa schému 0245, teda ID má formát 0245:{DIČ}, napr. 0245:2024000001.

Ako overím, či je príjemca zaregistrovaný v Peppol sieti?
APEX to overuje automaticky (SMP lookup) pred odoslaním. Ak príjemca nie je registrovaný, dostanete chybu PARTICIPANT_NOT_FOUND.

Musím zachovávať poradie správ?
Pre samostatné dokumenty (každá faktúra je nová správa) nie je poradie kritické. Ak posielate viacero verzií toho istého dokumentu (UPDATE operácie), použite DomainSyncVersion na riešenie konfliktov.


Príloha A: Peppol Document Type identifikátory

Najčastejšie typy dokumentov

Typ dokumentuDocumentTypeIdProcessId
Faktúra (Invoice)urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1urn:fdc:peppol.eu:2017:poacc:billing:01:1.0
Dobropis (Credit Note)urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2::CreditNote##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1urn:fdc:peppol.eu:2017:poacc:billing:01:1.0
MLR (Message Level Response)urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:2017:pracc:t111:ver1.0::2.1urn:fdc:peppol.eu:2017:pracc:messagelevels:01:1.0
IMR (Invoice Message Response)urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:2017:poacc:billing:08:1.0::2.1urn:fdc:peppol.eu:2017:poacc:billing:08:1.0

Príloha B: Kontrolný zoznam integrácie


Príloha C: Kontakty a podpora

Typ požiadavkyKontakt
Onboarding (SystemCode, TenantId, API kľúč)ASOL integračný tím
Technická podpora QHUBASOL DevOps / Cloud tím
Technická podpora Peppol (certifikáty, SMP registrácia)ASOL Peppol tím / váš SMP provider
Nahlásenie chybyCustomer Portal