5.1 High-Level System Data Flow
┌─────────────────────────────────────────────────────────────────┐
│ EG Flow Complete Data Flow │
└─────────────────────────────────────────────────────────────────┘
External System EG Flow System (Azure)
(Utility Billing)
│
│ 1. Generate monthly
│ invoice batch (XML)
│
├──────────────────────────────────────────>
│ POST /batches ┌──────────────┐
│ (GASEL/XELLENT/ZYNERGY XML) │ Core API │
│ │ Service │
│ │ │
│ │ • Auth check │
│ │ • Validate │
│ │ • Store blob │
│ └──────┬───────┘
│ │
│ 2. Return Batch ID │
│<────────────────────────────────────────────┤
│ {batchId, status: "uploaded"} │
│ │
│ 3. Start Processing │
├──────────────────────────────────────────> │
│ POST /batches/{id}/start │
│ │
│ ┌──────▼───────┐
│ │ Blob │
│ │ Storage │
│ │ │
│ │ {org}-batches│
│ │ /2025/11/21/ │
│ └──────┬───────┘
│ │
│ 4. 202 Accepted (queued) │
│<────────────────────────────────────────────┤
│ │
│ ┌──────▼────────┐
│ │ Storage │
│ │ Queues │
│ │ │
│ │ • batch-upload│
│ │ • batch-items │
│ │ • email │
│ │ • postal-bulk │
│ └───────┬───────┘
│ │
│ ┌───────────────┼──────────────┐
│ │ │ │
│ ┌──────▼─────┐ ┌──────▼─────┐ ┌──────▼─────┐
│ │ Parser │ │ Document │ │ Email │
│ │ Service │ │ Generator │ │ Service │
│ │ │ │ │ │ │
│ │ • Detect │ │ • Render │ │ • SendGrid │
│ │ • Validate │ │ • PDF gen │ │ • Retry │
│ │ • Parse │ │ • Store │ │ • Fallback │
│ │ • Transform│ │ │ │ │
│ └──────┬─────┘ └─────┬──────┘ └─────┬──────┘
│ │ │ │
│ Creates 157 Creates PDFs Sends emails
│ JSON files & queues │
│ (5000/32) delivery │
│ │ │ │
│ ▼ ▼ ▼
│ ┌──────────────────────────────────────┐
│ │ Blob Storage │
│ │ │
│ │ {org}-invoices-2025/11/21/ │
│ │ ├── {id}.json (canonical data) │
│ │ ├── {id}.html (rendered) │
│ │ └── {id}.pdf (final invoice) │
│ └──────────────────────────────────────┘
│ │
│ 5. Poll Status │
│ GET /batches/{id} │
├───────────────────────────────────────────> │
│ │
│ 6. Status Response │
│<─────────────────────────────────────────────┤
│ {status: "processing", │
│ processedItems: 3200/5000} │
│ │
│ 7. Status: Completed │
│<─────────────────────────────────────────────┤
│ {status: "completed", │
│ successfulItems: 4950, │
│ failedItems: 50} │
│ │
│ ┌───────▼──────┐
│ │ Postal │
│ │ Service │
│ │ │
│ │ • Collect │
│ │ • Create ZIP │
│ │ • 21G SFTP │
│ └───────┬──────┘
│ │
End Customer │
│ │
│ 8a. Receive Email │
│<─────────────────────────────────────────────┤
│ (PDF attachment) │
│ │
│ 8b. Receive Postal │
│<─────────────────────────────────────────────┤
│ (via 21G print partner) │
5.2 Batch Upload Flow
┌──────────────────────────────────────────────────────────────┐
│ Batch Upload Data Flow │
└──────────────────────────────────────────────────────────────┘
Client API Gateway Core API Blob Storage
│ │ │ │
├─ POST /batches ────────>│ │ │
│ (XML file) │ │ │
│ ├─ Authenticate ────>│ │
│ │ (Entra ID) │ │
│ │<─ JWT Valid ───────┤ │
│ │ │ │
│ ├─ Authorize ───────>│ │
│ │ (Check role) │ │
│ │<─ Access OK ───────┤ │
│ │ │ │
│ ├─ Upload File ─────>│ │
│ │ ├─ Generate UUID ──────>│
│ │ │ (Batch ID) │
│ │ │ │
│ │ ├─ Store XML ──────────>│
│ │ │ {org}-batches-2025/ │
│ │ │ 11/21/{id}/source.xml│
│ │ │<─ Stored ─────────────┤
│ │ │ │
│ │ ├─ Quick Detect ───────>│
│ │ │ (Namespace peek) │
│ │ │<─ Format: GASEL ──────┤
│ │ │ │
│ │ ├─ Create Metadata ────>│
│ │ │ metadata.json │
│ │ │<─ Created ────────────┤
│ │ │ │
│ │<─ 201 Created ─────┤ │
│ │ {batchId} │ │
│<─ 201 Created ──────────┤ │ │
│ {batchId, status} │ │ │
5.3 Parser Service Detailed Flow (XML → JSON)
┌──────────────────────────────────────────────────────────────┐
│ Parser Service: XML → Canonical JSON Flow │
└──────────────────────────────────────────────────────────────┘
Queue Message Parser Service Blob Storage
│ │ │
├─ {batchId} ────────>│ │
│ │ │
│ ├─ Download XML ────────────>│
│ │ GET {org}-batches-2025/ │
│ │ 11/21/{id}/source.xml │
│ │<─ XML Content ─────────────┤
│ │ │
│ ├─ Parse Root Namespace │
│ │ xmlns="urn:ediel:..." │
│ │ → Detected: GASEL │
│ │ │
│ ├─ Load Schema ─────────────>│
│ │ GET {org}-data/schemas/ │
│ │ gasel-mapping.json │
│ │<─ Mapping Config ──────────┤
│ │ │
│ ├─ Load XSD ────────────────>│
│ │ GET {org}-data/schemas/ │
│ │ gasel-v1.0.xsd │
│ │<─ XSD Content ─────────────┤
│ │ │
│ ├─ Validate XML │
│ │ Against XSD │
│ │ Result: VALID │
│ │ │
│ ├─ Parse XML (XPath) │
│ │ Extract Invoice[1]: │
│ │ InvoiceNumber = │
│ │ "2025-11-001" │
│ │ CustomerName = │
│ │ "Medeni Schröder" │
│ │ TotalAmount = 749.28 │
│ │ ... (all fields) │
│ │ │
│ ├─ Transform to Canonical │
│ │ { │
│ │ invoiceNumber, │
│ │ invoiceDate, │
│ │ customer: {...}, │
│ │ invoiceDetails: {...}, │
│ │ delivery: {...}, │
│ │ sourceMetadata: { │
│ │ vendorCode: "GASEL" │
│ │ } │
│ │ } │
│ │ │
│ ├─ LOOP: All 5000 invoices │
│ │ │
│ ├─ Store JSON (5000×) ───> │
│ │ PUT {org}-invoices-2025/ │
│ │ 11/21/{inv-id}.json │
│ │<─ Stored (5000×) ──────────┤
│ │ │
│ ├─ Group into 32-batches │
│ │ 5000 ÷ 32 = 157 batches │
│ │ │
│ ├─ Enqueue 157 Messages │
│ │ TO batch-items-queue │
│ │ Each: 32 invoice IDs │
│ │ │
│ ├─ Update Batch Metadata ─> │
│ │ PUT {org}-batches-2025/ │
│ │ 11/21/{id}/ │
│ │ metadata.json │
│ │ { │
│ │ totalItems: 5000, │
│ │ vendorCode: "GASEL", │
│ │ status: "processing" │
│ │ } │
│ │<─ Updated ─────────────────┤
│ │ │
│<─ ACK (delete msg)──┤ │
│ │ │
5.4 Document Generator Flow (JSON → HTML → PDF)
┌──────────────────────────────────────────────────────────────┐
│ Document Generator: JSON → HTML → PDF Flow │
└──────────────────────────────────────────────────────────────┘
Queue Message Document Generator Blob Storage
│ │ │
├─ 32 Invoice IDs ───>│ │
│ │ │
│ ├─ Acquire Blob Lease ────> │
│ │ Lease: {batch}/locks/ │
│ │ {worker-id}.lock │
│ │<─ Lease Acquired ─────────┤
│ │ (5 min duration) │
│ │ │
│ ├─ LOOP: 32 invoices │
│ │ │
│ ├─ Download JSON ──────────>│
│ │ GET {org}-invoices-2025/ │
│ │ 11/21/{id}.json │
│ │<─ Canonical Invoice ──────┤
│ │ │
│ ├─ Load Org Config ────> │
│ │ GET {org}-data/ │
│ │ organization.json │
│ │<─ Branding, Settings ─────┤
│ │ │
│ ├─ Determine Template │
│ │ Category (invoice type) │
│ │ → "invoice" │
│ │ │
│ ├─ Load Template ──────────>│
│ │ GET {org}-data/ │
│ │ templates/invoice/ │
│ │ active.html │
│ │<─ Handlebars Template ────┤
│ │ │
│ ├─ Compile Template │
│ │ (cache 24h if not cached)│
│ │ Handlebars.Compile() │
│ │ │
│ ├─ Render HTML │
│ │ Apply invoice data │
│ │ + organization branding │
│ │ Output: HTML string │
│ │ Duration: ~1-2 seconds │
│ │ │
│ ├─ Generate PDF │
│ │ Playwright.NewPage() │
│ │ SetContentAsync(html) │
│ │ PdfAsync(A4, margins) │
│ │ Duration: ~3-5 seconds │
│ │ │
│ ├─ Store HTML ─────────────>│
│ │ PUT {org}-invoices-2025/ │
│ │ 11/21/{id}.html │
│ │<─ Stored ─────────────────┤
│ │ │
│ ├─ Store PDF ──────────────>│
│ │ PUT {org}-invoices-2025/ │
│ │ 11/21/{id}.pdf │
│ │<─ Stored ─────────────────┤
│ │ │
│ ├─ Update Invoice JSON ────>│
│ │ Add fileReferences, │
│ │ templateInfo, timestamp │
│ │<─ Updated ────────────────┤
│ │ │
│ ├─ Determine Distribution │
│ │ Has email? → email-queue │
│ │ Else → postal-bulk-queue │
│ │ │
│ ├─ Enqueue Delivery │
│ │ TO email-queue OR │
│ │ TO postal-bulk-queue │
│ │ │
│ │ (END OF 32 ITEMS LOOP) │
│ │ │
│ ├─ Release Blob Lease ─────>│
│ │<─ Lease Released ─────────┤
│ │ │
│ ├─ Update Batch Stats ─────>│
│ │ (ETag concurrency) │
│ │ processedItems += 32 │
│ │<─ Updated ────────────────┤
│ │ │
│<─ ACK (delete msg)──┤ │
5.5 Email Delivery Flow
┌───────────────────────────────────────────────────────────┐
│ Email Delivery Service Flow │
└───────────────────────────────────────────────────────────┘
email-queue Email Service SendGrid API Blob Storage
│ │ │ │
├─ {invoiceId} ─>│ │ │
│ │ │ │
│ ├─ Download PDF ───────────────────>│
│ │ GET {org}-invoices-2025/11/21/ │
│ │ {invoice-id}.pdf │
│ │<─ PDF Bytes ──────────────────────┤
│ │ │ │
│ ├─ Load Org Config ────────────────>│
│ │ GET {org}-data/organization.json │
│ │<─ Email settings ─────────────────┤
│ │ │ │
│ ├─ Create Email │ │
│ │ From: noreply@org.se │
│ │ To: customer@example.se │
│ │ Subject: "Faktura XXX" │
│ │ Attach: PDF │
│ │ │ │
│ ├─ Send Email ────>│ │
│ │ POST /v3/mail/send │
│ │ │ │
│ │ ├─ Process │
│ │ │ (SendGrid) │
│ │ │ │
│ │<─ 202 Accepted ──┤ │
│ │ {messageId} │ │
│ │ │ │
│ ├─ Update Metadata ────────────────>│
│ │ deliveryAttempts.push({ │
│ │ channel: "email", │
│ │ status: "delivered", │
│ │ messageId, │
│ │ timestamp │
│ │ }) │
│ │<─ Updated ────────────────────────┤
│ │ │ │
│<─ ACK (delete)─┤ │ │
│ │ │ │
│ │
│ IF SENDGRID FAILS (429 Rate Limit): │
│ │ │ │
│ │<─ 429 Too Many ──┤ │
│ │ Retry-After: 60s│ │
│ │ │ │
│ ├─ Re-queue Message │
│<─ Re-enqueue ──┤ visibilityTimeout=60s │
│ (retry) │ │ │
│ │
│ IF ALL RETRIES FAIL → FALLBACK TO POSTAL: │
│ │ │ │
│ ├─ Enqueue to postal-bulk-queue │
│ │ (fallback delivery) │
5.6 Postal Bulk Processing Flow (21G Integration)
┌──────────────────────────────────────────────────────────┐
│ Postal Bulk Service: 21G Integration Flow │
└──────────────────────────────────────────────────────────┘
Scheduled Trigger Postal Service 21G SFTP Blob Storage
(12:00, 20:00 CET) │ │ │
│ │ │ │
├─ CRON Trigger ────>│ │ │
│ │ │ │
│ ├─ Fetch All Messages from │
│ │ postal-bulk-queue │
│ │ (batch retrieval) │
│ │ Result: 150 invoices │
│ │ │ │
│ ├─ Group by Organization │
│ │ Org A: 100 invoices │
│ │ Org B: 50 invoices │
│ │ │ │
│ ├─ FOR Org A: │ │
│ │ │ │
│ ├─ Download 100 PDFs ──────────────>│
│ │ GET {org-a}-invoices-2025/ │
│ │ 11/21/{id}.pdf │
│ │<─ PDF Bytes (100×) ───────────────┤
│ │ │ │
│ ├─ Create 21G XML Metadata │
│ │ <PrintBatch> │
│ │ <TotalDocuments>100 │
│ │ <Documents> │
│ │ <Document> │
│ │ <DocumentId>001.pdf │
│ │ <Recipient> │
│ │ <Name>...</Name> │
│ │ <Address>...</Address> │
│ │ │ │
│ ├─ Create ZIP Archive │
│ │ ORGA_20251121_001.zip │
│ │ ├── metadata.xml │
│ │ ├── invoice_001.pdf │
│ │ ├── invoice_002.pdf │
│ │ └── ... (100 PDFs) │
│ │ │ │
│ ├─ Upload to 21G ─>│ │
│ │ SFTP PUT │ │
│ │ /incoming/ORGA/ │ │
│ │ ORGA_20251121_ │ │
│ │ 001.zip │ │
│ │<─ Upload Success ┤ │
│ │ │ │
│ ├─ Update Invoice Statuses ────────>│
│ │ (100 invoices) │
│ │ status="postal_sent" │
│ │<─ Updated (100×) ─────────────────┤
│ │ │ │
│ ├─ Delete Queue Messages │
│ │ (100 messages from queue) │
│ │ │ │
│ ├─ Send Notification Email │
│ │ To: ops@org-a.com │
│ │ Subject: "21G Batch Sent" │
│ │ Body: "100 invoices" │
│ │ │ │
│ ├─ Log to App Insights │
│ │ Metric: Postal.BatchSize=100 │
5.7 Error Handling & Retry Flow
┌─────────────────────────────────────────────────────────┐
│ Error Handling & Retry Flow Diagram │
└─────────────────────────────────────────────────────────┘
Processing Error Occurs Retry Logic Final State
│ │ │ │
├─ Render HTML │ │ │
│ X │ │
│ Template error │ │
│ (missing variable) │ │
│ │ │ │
│ ├─ Log Error ─────>│ │
│ │ Application │ │
│ │ Insights │ │
│ │ Level: Error │ │
│ │ CorrelationId │ │
│ │ │ │
│ ├─ Increment ─────>│ │
│ │ retryCount=1 │ │
│ │ │ │
│ ├─ Retry Attempt 1 │ │
│ │ Wait: 60 seconds│ │
│ X Still fails │ │
│ │ │ │
│ ├─ Increment ─────>│ │
│ │ retryCount=2 │ │
│ │ │ │
│ ├─ Retry Attempt 2 │ │
│ │ Wait: 5 minutes │ │
│ X Still fails │ │
│ │ │ │
│ ├─ Increment ─────>│ │
│ │ retryCount=3 │ │
│ │ │ │
│ ├─ Retry Attempt 3 │ │
│ │ Wait: 15 minutes│ │
│ X Still fails │ │
│ │ │ │
│ ├─ Max Retries ───────────────────────>│
│ │ Exceeded │ poison-queue
│ │ │ │
│ ├─ Create Poison ─────────────────────>│
│ │ Message with: │ { │
│ │ • Original msg │ retryCount: 3 │
│ │ • All errors │ lastError │
│ │ • Timestamps │ alertSent │
│ │ │ } │
│ │ │ │
│ ├─ Send Alert ────────────────────────>│
│ │ Email to: │ support@egflow.com
│ │ support team │ Subject: "Poison Queue"
│ │ │ │
│ ├─ Update Batch ──────────────────────>│
│ │ failedItems++ │ Batch metadata │
│ │ errors.push() │ updated │
│ │ │ │
│<─ Delete from ─┤ │ │
│ main queue │ │ │
5.8 Multi-Vendor Transformation Flow
┌──────────────────────────────────────────────────────────┐
│ Multi-Vendor XML → Canonical JSON Transformation │
└──────────────────────────────────────────────────────────┘
GASEL XML Parser Canonical JSON
│ │ │
<InvoiceBatch │ │
xmlns="urn:ediel:..."> │ │
<Invoice> │ │
<InvoiceHeader> │ │
<InvoiceNumber> │ │
2025-11-001 │ │
</InvoiceNumber> │ │
<CustomerParty> │ │
<PartyName> │ │
Medeni Schröder │ │
</PartyName> │ │
<MonetarySummary> │ │
<PayableAmount> │ │
749.28 │ │
</PayableAmount> │ │
├────────────────────────────>│ │
│ ├─ Detect: GASEL │
│ │ (namespace match) │
│ │ │
│ ├─ Load Mapping ──────────>│
│ │ gasel-mapping.json │
│ │ │
│ ├─ Extract via XPath: │
│ │ invoiceNumber = │
│ │ "InvoiceHeader/ │
│ │ InvoiceNumber" │
│ │ customerName = │
│ │ "CustomerParty/ │
│ │ PartyName" │
│ │ │
│ ├─ Transform ─────────────>│
│ │ { │
│ │ "invoiceNumber": "2025-11-001",
│ │ "customer": {
│ │ "fullName": "Medeni Schröder"
│ │ },
│ │ "invoiceDetails": {
│ │ "totalAmount": 749.28
│ │ },
│ │ "sourceMetadata": {
│ │ "vendorCode": "GASEL"
│ │ }
│ │ }
XELLENT XML Parser Canonical JSON
│ │ │
<InvoiceBatch │ │
xmlns="http://oio.dk..." │ │
xmlns:com="...common"> │ │
<Invoice> │ │
<com:ID> │ │
5002061556 │ │
</com:ID> │ │
<com:BuyerParty> │ │
<com:PartyName> │ │
<com:Name> │ │
Erik Svensson │ │
</com:Name> │ │
<com:LegalTotals> │ │
<com:ToBePaidTotalAmount> │ │
2 567,00 │ │
├────────────────────────────>│ │
│ ├─ Detect: XELLENT │
│ │ (oio.dk namespace) │
│ │ │
│ ├─ Load Mapping │
│ │ xellent-mapping.json │
│ │ │
│ ├─ Handle Namespaces │
│ │ (com:, main: prefixes) │
│ │ │
│ ├─ Extract via XPath: │
│ │ invoiceNumber = "com:ID"│
│ │ customerName = │
│ │ "com:BuyerParty/ │
│ │ com:PartyName/ │
│ │ com:Name" │
│ │ │
│ ├─ Normalize Amount: │
│ │ "2 567,00" → 2567.00 │
│ │ │
│ ├─ Transform ─────────────>│
│ │ { │
│ │ "invoiceNumber": "5002061556",
│ │ "customer": {
│ │ "fullName": "Erik Svensson"
│ │ },
│ │ "invoiceDetails": {
│ │ "totalAmount": 2567.00
│ │ },
│ │ "sourceMetadata": {
│ │ "vendorCode": "XELLENT"
│ │ }
│ │ }
ZYNERGY XML Parser Canonical JSON
│ │ │
<InvoiceBatch │ │
xmlns="http://eg.dk/Zynergy"> │ │
<Invoice> │ │
<InvoiceData> │ │
<InvoiceNumber> │ │
100000 │ │
</InvoiceNumber> │ │
<Customer> │ │
<ReadOnlyFullName> │ │
Alfred Asplund │ │
</ReadOnlyFullName> │ │
<InvoiceData> │ │
<InvoiceAmount> │ │
1056.00 │ │
├────────────────────────────>│ │
│ ├─ Detect: ZYNERGY │
│ │ (Zynergy namespace) │
│ │ │
│ ├─ Load Mapping │
│ │ zynergy-mapping.json │
│ │ │
│ ├─ Extract via XPath: │
│ │ invoiceNumber = │
│ │ "InvoiceData/ │
│ │ InvoiceNumber" │
│ │ customerName = │
│ │ "Customer/ │
│ │ ReadOnlyFullName" │
│ │ │
│ ├─ Transform ─────────────>│
│ │ { │
│ │ "invoiceNumber": "100000",
│ │ "customer": {
│ │ "fullName": "Alfred Asplund"
│ │ },
│ │ "invoiceDetails": {
│ │ "totalAmount": 1056.00
│ │ },
│ │ "sourceMetadata": {
│ │ "vendorCode": "ZYNERGY"
│ │ }
│ │ }
│ │
All 3 formats Single standard
produce same ───> canonical JSON
structure for downstream
5.9 Distribution Routing Decision Flow
┌─────────────────────────────────────────────────────────┐
│ Distribution Channel Routing Logic │
└─────────────────────────────────────────────────────────┘
Invoice Data Routing Logic Queue Selection
│ │ │
├─ Check Customer │ │
│ Preference │ │
│ │ │
│ preference= │ │
│ "postal"? ─────────┤ │
│ ├─ YES ───────────────────>│
│ │ postal-bulk-queue
│ │ │
│ ├─ NO │
│ │ Continue... │
│ │ │
├─ Load Org │ │
│ Channel Priority │ │
│ [email, postal] │ │
│ │ │
│ ├─ TRY Priority 1: email │
│ │ │
├─ Has email │ │
│ address? │ │
│ │ │
│ email != null? ────┤ │
│ ├─ YES │
│ │ Validate email format │
│ │ │
│ valid email? ──────┤ │
│ ├─ YES ───────────────────>│
│ │ email-queue
│ │ │
│ ├─ NO (invalid/missing) │
│ │ Try next channel... │
│ │ │
│ ├─ TRY Priority 2: postal │
│ │ │
├─ Complete │ │
│ address? │ │
│ │ │
│ address valid? ────┤ │
│ ├─ YES ───────────────────>│
│ │ postal-bulk-queue
│ │ │
│ ├─ NO (incomplete) │
│ │ Log error │
│ │ Skip invoice │
│ │ Alert organization │
│ │ │
│ Swedish Law: │ │
│ "Rätt till │ │
│ pappersfaktura" │ │
│ Always ensure │ │
│ postal available ──┤ │
