# Bilko HR eRačun — sveRačun (PostLink) Integration & Status Model

## Overview

**Provider:** sveRačun by PostLink d.o.o. (Velika Gorica, HR).  
Contact: David Krišto <david.kristo@sveracun.hr>, Ivan Jurić <ivan.juric@sveracun.hr>.  
API docs: [https://sveracun-public-api.redocly.app/](https://sveracun-public-api.redocly.app/)

Replaces the abandoned Storecove plan (MC #8675, not approved). CEO accepted sveRačun partnership 2026-06-11.

**Current state:** adapter MERGED to main, GATED/DISABLED (`SVERACUN_HR_LIVE=false`, adapter\_config `sveracun-hr-fisk` `enabled=FALSE`). Live activation pending (MC #103443).

---

## API

<table id="bkmrk-operationendpointnot"><thead><tr><th>Operation</th><th>Endpoint</th><th>Notes</th></tr></thead><tbody><tr><td>Submit document</td><td>`POST https://test.sveracun.hr/api/rest/v1/documents/send`</td><td>Auth header: `Authorization: <raw-api-key>` (apiKey scheme, NOT Bearer). Body: raw UBL 2.1 XML (`application/octet-stream`). Response: `{"documentId":"<hex>"}`. PROD base URL differs from TEST.</td></tr><tr><td>Internal status</td><td>`POST /rest/v1/documents/getInternalStatus`</td><td>Poll only — no webhook.</td></tr><tr><td>External status</td><td>`POST /rest/v1/documents/getExternalStatus`</td><td>Provider also references `documents/getStatus`.</td></tr></tbody></table>

---

## Two-Stage Processing (per David Krišto / PostLink)

**Etapa 1 — Basic parse:** the initiator OIB (the OIB that calls send, tied to the API key) MUST equal the sender OIB inside the UBL XML, plus recipient/document-type/process checks. Mismatch results in status `FAILED`.

**Etapa 2 — Full routing:** validation vs Porezna uprava validator; recipient access-point lookup; SBD preparation; AS4 exchange (waits 5 min for recipient access point; on unavailability retries up to 24× every 30 min); outbound fiscalization; archiving.

---

## Status Model (Authoritative — correct as of 2026-06-11)

### Internal statuses

<table id="bkmrk-statusmeaningnewinbo"><thead><tr><th>Status</th><th>Meaning</th></tr></thead><tbody><tr><td>`NEW`</td><td>Inbound-only — document available to pick up.</td></tr><tr><td>`OK`</td><td>Processed successfully by sveRačun.</td></tr><tr><td>`FAILED`</td><td>Processing interrupted (e.g. sender OIB mismatch, parse error).</td></tr><tr><td>`UNKNOWN`</td><td>Currently being processed.</td></tr><tr><td>`UNDELIVERABLE`</td><td>Recipient not registered in AMS (access-point lookup failed).</td></tr></tbody></table>

### External (fiscalization) statuses

<table id="bkmrk-statusmeaningfiscali"><thead><tr><th>Status</th><th>Meaning</th></tr></thead><tbody><tr><td>`FISCALIZATION:OK`</td><td>Fiscalization succeeded.</td></tr><tr><td>`FISCALIZATION:ERROR`</td><td>Fiscalization failed.</td></tr><tr><td>`null`</td><td>Not yet forwarded to fiscalization.</td></tr><tr><td>`FISCALIZATION_PAYMENT_REPORT:OK|ERROR`</td><td>Payment report fiscalization result.</td></tr><tr><td>`FISCALIZATION_REJECTION_REPORT:OK|ERROR`</td><td>Rejection report fiscalization result.</td></tr><tr><td>`FISCALIZATION_NOT_DELIVERED_REPORT:OK|ERROR`</td><td>Not-delivered report fiscalization result.</td></tr></tbody></table>

### Bilko decision: composite outcome classification

<table id="bkmrk-outcomeconditionsucc"><thead><tr><th>Outcome</th><th>Condition</th></tr></thead><tbody><tr><td>**SUCCESS**</td><td>internal = `OK` AND external = `FISCALIZATION:OK`</td></tr><tr><td>**FAILURE**</td><td>internal = `FAILED` | `UNDELIVERABLE`, OR external = `FISCALIZATION:ERROR` | any `REJECTION_REPORT` | any `NOT_DELIVERED_REPORT`</td></tr><tr><td>**PENDING**</td><td>internal = `UNKNOWN` | `NEW`, OR external = `null`</td></tr></tbody></table>

> **How do we know the invoice arrived?** Poll until external status is `FISCALIZATION:OK` (success) or any `*_REPORT`/`ERROR` terminal state (failure). There is NO webhook from sveRačun.

---

## Implementation

### Source files

- `apps/api/src/main/kotlin/no/alai/bilko/country/hr/SveRacunHttpClient.kt` — HTTP client (submit, getInternalStatus, getExternalStatus).
- `apps/api/src/main/kotlin/no/alai/bilko/country/hr/SveRacunHrEInvoiceAdapter.kt` — serialize, submit, pollStatus; unified `mapStatusPair(internal, external)` logic.
- `PluginHR.kt` — `EINVOICE_SUBMIT` = BETA, gated by `SVERACUN_HR_LIVE` flag.
- `db/migrations/V74__sveracun_adapter_config.sql` — Flyway migration; registers adapter\_config record `sveracun-hr-fisk`.

### Key implementation detail

`serialize()` forces UBL `AccountingSupplierParty` OIB to equal `SVERACUN_SENDER_VAT` (e.g. `HR91276104352`). If this env var is unset the method is fail-closed (throws before submitting). This prevents Etapa-1 sender-OIB mismatch failures.

### Configuration &amp; secrets

<table id="bkmrk-variablepurposewhere"><thead><tr><th>Variable</th><th>Purpose</th><th>Where stored</th></tr></thead><tbody><tr><td>`SVERACUN_API_KEY`</td><td>API authentication (raw key, not Bearer)</td><td>GCP Secret: `bilko-sveracun-test-api-key` (TEST); separate PROD secret to be provisioned.</td></tr><tr><td>`SVERACUN_BASE_URL`</td><td>Base URL (test vs prod differs)</td><td>Cloud Run env</td></tr><tr><td>`SVERACUN_SENDER_VAT`</td><td>Sender OIB for UBL XML + Etapa-1 initiator match</td><td>Cloud Run env</td></tr><tr><td>`SVERACUN_HR_LIVE`</td><td>Feature gate (false = adapter disabled)</td><td>Cloud Run env</td></tr></tbody></table>

> **SECURITY:** Never commit or log the actual API key value. Always retrieve from GCP Secret Manager at runtime.

### PRs &amp; tests

- PR #346 — initial integration.
- PR #348 — status-model correction (MC #103445).
- 42 unit tests: `SveRacunHrEInvoiceAdapterTest`.

### Verification

Proveo independent PASS — live TEST submit returned HTTP 200, `internalStatus=UNKNOWN` (processing) after Etapa-1 sender-OIB fix. Prior `FAILED` result was caused by sender/initiator OIB mismatch before the `serialize()` fix. Evidence: `/tmp/evidence-103445/`.

---

## Activation Checklist (MC #103443 — NOT yet done)

1. PostLink confirms our OIB `91276104352` is a registered TEST sender (and separately, PROD sender).
2. Independent green re-test: `internalStatus=OK` end-to-end (both Etapa 1 and 2).
3. Provision PROD sveRačun API key as GCP Secret.
4. Flip adapter\_config `enabled=true` + `SVERACUN_HR_LIVE=true` + `SVERACUN_SENDER_VAT` in Cloud Run (PROD environment).
5. ZAKON PI2 deploy + Proveo live-activation verification.
6. GA compliance review before statutory HR eRačun filing obligations take effect.

---

## Related MC Tasks

- **MC #103434** — sveRačun integration (build).
- **MC #103445** — Corrected status model (PR #348).
- **MC #103443** — Live activation (pending — do NOT mark done until activation checklist complete).
- **MC #8675** — Abandoned Storecove plan (not approved; superseded).

---

## Document Metadata

- **Author:** ALAI / Skillforge
- **Created:** 2026-06-11
- **Status:** LIVE REFERENCE — update when activation checklist progresses or API contract changes.