schemas: add ds/receipts v1.0.0, ds/per_diem v1.0.0, expense.canonical v1.2.0, sink.db.expense v1.2.0 (+ examples)
This commit is contained in:
parent
95e50aff7d
commit
27c45b356a
9 changed files with 660 additions and 3 deletions
|
|
@ -9,12 +9,16 @@ Versionierte JSON-Schemas für die kanonischen Business-, Datenquellen- und Senk
|
||||||
| Pfad | Rolle | Status | Verwendet in |
|
| Pfad | Rolle | Status | Verwendet in |
|
||||||
| ----------------------------------------------- | ------------------------------ | --------- | -------------------------------------------------- |
|
| ----------------------------------------------- | ------------------------------ | --------- | -------------------------------------------------- |
|
||||||
| `expense/canonical/v1.0.0.json` | Kanonische Business-Form | legacy | Approval-Attestierung (`subject`, `materials`), Agent-A-Transform-Output |
|
| `expense/canonical/v1.0.0.json` | Kanonische Business-Form | legacy | Approval-Attestierung (`subject`, `materials`), Agent-A-Transform-Output |
|
||||||
| `expense/canonical/v1.1.0.json` | Kanonische Business-Form | **aktiv** | wie v1.0.0, zusätzlich mit Zeilen-Allokationen (Multi-Projekt pro Item) |
|
| `expense/canonical/v1.1.0.json` | Kanonische Business-Form | legacy | wie v1.0.0, zusätzlich mit Zeilen-Allokationen (Multi-Projekt pro Item) |
|
||||||
|
| `expense/canonical/v1.2.0.json` | Kanonische Business-Form | **aktiv** | wie v1.1.0, zusätzlich mit `receipt_content_hash`/`receipt_attestation_ref` je Item und neuen Item-Typen `PER_DIEM_*` |
|
||||||
| `ds/expense/project_assignment/v1.0.0.json` | Datenquellen-Rückgabe | aktiv | Query Attestation von `ds:expense-db-app` / `project_assignments` |
|
| `ds/expense/project_assignment/v1.0.0.json` | Datenquellen-Rückgabe | aktiv | Query Attestation von `ds:expense-db-app` / `project_assignments` |
|
||||||
|
| `ds/receipts/v1.0.0.json` | Datenquellen-Rückgabe | **aktiv** | Query Attestation von `ds:receipts` / `receipt` (Metadaten; Blob kommt separat) |
|
||||||
|
| `ds/per_diem/v1.0.0.json` | Datenquellen-Rückgabe | **aktiv** | Query Attestation von `ds:per-diem-rates` / `rates` |
|
||||||
| `sink/db/expense/v1.0.0.json` | Senken-Payload | legacy | Transform-Attestierung `expense.map_to_sink` → `sink:expense-db-app` (ohne Splits) |
|
| `sink/db/expense/v1.0.0.json` | Senken-Payload | legacy | Transform-Attestierung `expense.map_to_sink` → `sink:expense-db-app` (ohne Splits) |
|
||||||
| `sink/db/expense/v1.1.0.json` | Senken-Payload | **aktiv** | wie v1.0.0, zusätzlich mit per-Item-Allocation-Rows für die DB-App |
|
| `sink/db/expense/v1.1.0.json` | Senken-Payload | legacy | wie v1.0.0, zusätzlich mit per-Item-Allocation-Rows für die DB-App |
|
||||||
|
| `sink/db/expense/v1.2.0.json` | Senken-Payload | **aktiv** | wie v1.1.0, zusätzlich mit Receipts- und PER_DIEM_*-Item-Support |
|
||||||
|
|
||||||
Neue Workflows SOLLEN die v1.1.0-Fassungen nutzen. v1.0.0 bleibt parallel bestehen, damit Attestierungen aus deren Laufzeit verifizierbar bleiben.
|
Neue Workflows SOLLEN die v1.2.0-Fassungen nutzen. Ältere Versionen bleiben parallel bestehen, damit Attestierungen aus deren Laufzeit verifizierbar bleiben.
|
||||||
|
|
||||||
## Zeilen-Allokationen (v1.1.0)
|
## Zeilen-Allokationen (v1.1.0)
|
||||||
|
|
||||||
|
|
@ -24,6 +28,14 @@ Die Senkenfassung (`sink.db.expense` v1.1.0) erwartet immer absolute Beträge
|
||||||
|
|
||||||
Beispiele unter `*/examples/vX.Y.Z/*.json` (werden in CI gegen das Schema derselben Version validiert).
|
Beispiele unter `*/examples/vX.Y.Z/*.json` (werden in CI gegen das Schema derselben Version validiert).
|
||||||
|
|
||||||
|
## Receipts (v1.2.0)
|
||||||
|
|
||||||
|
Jedes `expense_items[]` darf ein `receipt_content_hash` + `receipt_attestation_ref` tragen. Der eigentliche Beleg liegt im PII-Store, nur Hash und Attestations-Referenz landen in der kanonischen Fassung. Lesender Zugriff auf den Blob erfolgt über `ds:receipts` + Session-Scope-Token nach OPA-Prüfung (siehe `policies/data_sources/receipts.rego` und `docs/architecture/v1.1.md` §4.7).
|
||||||
|
|
||||||
|
## Per-Diem-Items (v1.2.0)
|
||||||
|
|
||||||
|
Neue Item-Typen `PER_DIEM_FULL`, `PER_DIEM_PARTIAL`, `PER_DIEM_ARRIVAL`, `PER_DIEM_DEPARTURE` kennzeichnen vom `agent-per-diem-calculator` deterministisch erzeugte Zeilen. Diese Items MÜSSEN ein `computed_from.transform_attestation_ref` tragen (JSON-Schema erzwingt es per `if/then`), damit die Rückverfolgung zur Berechnungs-Attestation und Rate-Query-Attestation gewährleistet ist (siehe `docs/architecture/v1.1.md` §6.2).
|
||||||
|
|
||||||
## Namensschema
|
## Namensschema
|
||||||
|
|
||||||
- `expense/canonical` — kanonische (senken-unabhängige) Business-Form
|
- `expense/canonical` — kanonische (senken-unabhängige) Business-Form
|
||||||
|
|
|
||||||
25
schemas/ds/per_diem/examples/v1.0.0/berlin-day.json
Normal file
25
schemas/ds/per_diem/examples/v1.0.0/berlin-day.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"schema": { "name": "ds.per_diem.rate", "version": "1.0.0" },
|
||||||
|
"data_source": "ds:per-diem-rates",
|
||||||
|
"resource": "rates",
|
||||||
|
"filters": {
|
||||||
|
"country": "DE",
|
||||||
|
"valid_on": "2026-10-02"
|
||||||
|
},
|
||||||
|
"returned_at": "2026-10-25T18:05:00Z",
|
||||||
|
"rates": [
|
||||||
|
{
|
||||||
|
"rate_id": "PDR-42",
|
||||||
|
"country": "DE",
|
||||||
|
"city": null,
|
||||||
|
"currency": "EUR",
|
||||||
|
"full_day": "28.00",
|
||||||
|
"partial_day_min8h": "14.00",
|
||||||
|
"arrival_day": "14.00",
|
||||||
|
"departure_day": "14.00",
|
||||||
|
"valid_from": "2026-01-01",
|
||||||
|
"valid_to": null,
|
||||||
|
"source_regulation": "BMF-Schreiben 2025-11-21 Az. IV C 5 - S 2353/19/10011 :005, Anlage 1, Deutschland Inland (Beispielwerte)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
87
schemas/ds/per_diem/v1.0.0.json
Normal file
87
schemas/ds/per_diem/v1.0.0.json
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://basicstack.de/schemas/ds/per_diem/v1.0.0.json",
|
||||||
|
"title": "DataSource Response: ds:per-diem-rates /rates v1.0.0",
|
||||||
|
"description": "Rückgabe-Schema für den Lesezugriff auf versionierte Verpflegungsmehraufwands-Pauschalen (z.B. BMF-Sätze für Deutschland, analog für andere Länder). Query-Filter kombinieren country (Pflicht), optional city und valid_on (Pflicht); Rückgabe listet alle am Stichtag gültigen passenden Rates (meistens eine, bei großen Städten mit abweichenden Sätzen gelegentlich mehrere).",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["schema", "data_source", "resource", "filters", "rates", "returned_at"],
|
||||||
|
"properties": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "version"],
|
||||||
|
"properties": {
|
||||||
|
"name": { "const": "ds.per_diem.rate" },
|
||||||
|
"version": { "const": "1.0.0" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data_source": { "const": "ds:per-diem-rates" },
|
||||||
|
"resource": { "const": "rates" },
|
||||||
|
"filters": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["country", "valid_on"],
|
||||||
|
"properties": {
|
||||||
|
"country": { "type": "string", "pattern": "^[A-Z]{2}$", "description": "ISO 3166-1 alpha-2." },
|
||||||
|
"city": { "type": "string", "minLength": 1, "maxLength": 100 },
|
||||||
|
"valid_on": { "type": "string", "format": "date" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"returned_at": { "type": "string", "format": "date-time" },
|
||||||
|
"rates": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 50,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"rate_id",
|
||||||
|
"country",
|
||||||
|
"currency",
|
||||||
|
"full_day",
|
||||||
|
"partial_day_min8h",
|
||||||
|
"arrival_day",
|
||||||
|
"departure_day",
|
||||||
|
"valid_from",
|
||||||
|
"source_regulation"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"rate_id": { "type": "string", "pattern": "^PDR-[0-9]+$" },
|
||||||
|
"country": { "type": "string", "pattern": "^[A-Z]{2}$" },
|
||||||
|
"city": { "type": ["string", "null"], "maxLength": 100 },
|
||||||
|
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
|
||||||
|
"full_day": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+\\.[0-9]{2}$",
|
||||||
|
"description": "Pauschale für vollen Reisetag (24h Abwesenheit)."
|
||||||
|
},
|
||||||
|
"partial_day_min8h": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+\\.[0-9]{2}$",
|
||||||
|
"description": "Pauschale bei >8h bis <24h Abwesenheit (sofern anwendbar)."
|
||||||
|
},
|
||||||
|
"arrival_day": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+\\.[0-9]{2}$",
|
||||||
|
"description": "Pauschale Anreisetag (unabhängig von der Abwesenheitsdauer)."
|
||||||
|
},
|
||||||
|
"departure_day": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+\\.[0-9]{2}$",
|
||||||
|
"description": "Pauschale Abreisetag (unabhängig von der Abwesenheitsdauer)."
|
||||||
|
},
|
||||||
|
"valid_from": { "type": "string", "format": "date" },
|
||||||
|
"valid_to": { "type": ["string", "null"], "format": "date" },
|
||||||
|
"source_regulation": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 500,
|
||||||
|
"description": "Freitext-Zitierung der Rechtsquelle, z.B. 'BMF-Schreiben 2026-11-15 Az. IV C 5 - S 2353/19/10011 :005'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
schemas/ds/receipts/examples/v1.0.0/single-receipt.json
Normal file
23
schemas/ds/receipts/examples/v1.0.0/single-receipt.json
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"schema": { "name": "ds.receipts", "version": "1.0.0" },
|
||||||
|
"data_source": "ds:receipts",
|
||||||
|
"resource": "receipt",
|
||||||
|
"filters": {
|
||||||
|
"content_hash": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
|
||||||
|
},
|
||||||
|
"returned_at": "2026-10-25T18:00:00Z",
|
||||||
|
"receipt": {
|
||||||
|
"content_hash": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
|
||||||
|
"pii_store_ref": "pii://receipts/WF-2026-0042/01HFXYZ.pdf",
|
||||||
|
"mime_type": "application/pdf",
|
||||||
|
"size_bytes": 184723,
|
||||||
|
"uploaded_by_agent": "agent-travel",
|
||||||
|
"on_behalf_of_user": {
|
||||||
|
"iss": "https://auth.basicstack.de",
|
||||||
|
"sub": "user-12345",
|
||||||
|
"session_id": "SES-2026-10-25-abc"
|
||||||
|
},
|
||||||
|
"uploaded_at": "2026-10-25T17:55:12Z",
|
||||||
|
"attestation_ref": "sha256:deadbeefcafefeeddeadbeefcafefeeddeadbeefcafefeeddeadbeefcafefeed"
|
||||||
|
}
|
||||||
|
}
|
||||||
88
schemas/ds/receipts/v1.0.0.json
Normal file
88
schemas/ds/receipts/v1.0.0.json
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://basicstack.de/schemas/ds/receipts/v1.0.0.json",
|
||||||
|
"title": "DataSource Response: ds:receipts /receipt v1.0.0",
|
||||||
|
"description": "Metadaten-Rückgabe des Receipt-Adapters für einen bekannten Beleg (identifiziert über content_hash). Der eigentliche Beleg-Blob wird separat per /api/v1/receipts/{content_hash}/content geladen (nur nach OPA-Prüfung). Diese Response enthält ausschließlich Metadaten und Referenzen — keine PII-Inhalte.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["schema", "data_source", "resource", "filters", "receipt", "returned_at"],
|
||||||
|
"properties": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "version"],
|
||||||
|
"properties": {
|
||||||
|
"name": { "const": "ds.receipts" },
|
||||||
|
"version": { "const": "1.0.0" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data_source": { "const": "ds:receipts" },
|
||||||
|
"resource": { "const": "receipt" },
|
||||||
|
"filters": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["content_hash"],
|
||||||
|
"properties": {
|
||||||
|
"content_hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"returned_at": { "type": "string", "format": "date-time" },
|
||||||
|
"receipt": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"content_hash",
|
||||||
|
"pii_store_ref",
|
||||||
|
"mime_type",
|
||||||
|
"size_bytes",
|
||||||
|
"uploaded_by_agent",
|
||||||
|
"on_behalf_of_user",
|
||||||
|
"uploaded_at",
|
||||||
|
"attestation_ref"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"content_hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
||||||
|
"pii_store_ref": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^pii://[A-Za-z0-9/._-]+$",
|
||||||
|
"description": "Logischer Pfad im PII-Store (verschlüsselter Bucket). KEIN Download-Link; der Blob kommt über einen separaten authorisierten Endpunkt."
|
||||||
|
},
|
||||||
|
"mime_type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["application/pdf", "image/png", "image/jpeg", "image/webp"]
|
||||||
|
},
|
||||||
|
"size_bytes": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 20971520,
|
||||||
|
"description": "Beleg-Blob-Grösse in Bytes. Hartes Oberlimit: 20 MiB."
|
||||||
|
},
|
||||||
|
"uploaded_by_agent": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "agent_id der uploadenden Komponente (typischerweise agent-travel)."
|
||||||
|
},
|
||||||
|
"on_behalf_of_user": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["iss", "sub", "session_id"],
|
||||||
|
"properties": {
|
||||||
|
"iss": { "type": "string" },
|
||||||
|
"sub": { "type": "string" },
|
||||||
|
"session_id": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uploaded_at": { "type": "string", "format": "date-time" },
|
||||||
|
"attestation_ref": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^sha256:[0-9a-f]{64}$",
|
||||||
|
"description": "SHA-256 der Receipt Attestation (predicateType https://basicstack.de/attestations/receipt_ingest/v1)."
|
||||||
|
},
|
||||||
|
"deleted_at": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Wenn gesetzt: der Beleg wurde gelöscht (Retention oder DSGVO-Antrag). GET auf den Blob-Endpunkt liefert dann 410 Gone."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"schema": { "name": "expense.canonical", "version": "1.2.0" },
|
||||||
|
"workflow_id": "WF-2026-0044",
|
||||||
|
"employee_id": "EMP-12345",
|
||||||
|
"trip": {
|
||||||
|
"start_date": "2026-10-01",
|
||||||
|
"end_date": "2026-10-03",
|
||||||
|
"destination": "Berlin",
|
||||||
|
"purpose": "Kundentermin + Workshop",
|
||||||
|
"countries_visited": [
|
||||||
|
{ "country": "DE", "city": "Berlin", "from_date": "2026-10-01", "to_date": "2026-10-03" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"currency": "EUR",
|
||||||
|
"total_amount": "484.50",
|
||||||
|
"project_assignment": {
|
||||||
|
"project_code": "P-42",
|
||||||
|
"cost_center": "CC-100",
|
||||||
|
"source_attestation_ref": "sha256:1111000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
},
|
||||||
|
"expense_items": [
|
||||||
|
{
|
||||||
|
"type": "HOTEL",
|
||||||
|
"amount": "328.50",
|
||||||
|
"incurred_on": "2026-10-01",
|
||||||
|
"description": "Hotel Berlin Mitte, 2 Nächte",
|
||||||
|
"receipt_ref": "pii://receipts/WF-2026-0044/01HFXYZ.pdf",
|
||||||
|
"receipt_content_hash": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
|
||||||
|
"receipt_attestation_ref": "sha256:deadbeefcafefeeddeadbeefcafefeeddeadbeefcafefeeddeadbeefcafefeed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PER_DIEM_ARRIVAL",
|
||||||
|
"amount": "14.00",
|
||||||
|
"incurred_on": "2026-10-01",
|
||||||
|
"computed_from": {
|
||||||
|
"transform_attestation_ref": "sha256:2222000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"rate_query_attestation_ref": "sha256:3333000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
"rate_id": "PDR-42"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PER_DIEM_FULL",
|
||||||
|
"amount": "28.00",
|
||||||
|
"incurred_on": "2026-10-02",
|
||||||
|
"computed_from": {
|
||||||
|
"transform_attestation_ref": "sha256:2222000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"rate_query_attestation_ref": "sha256:3333000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
"rate_id": "PDR-42"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PER_DIEM_DEPARTURE",
|
||||||
|
"amount": "14.00",
|
||||||
|
"incurred_on": "2026-10-03",
|
||||||
|
"computed_from": {
|
||||||
|
"transform_attestation_ref": "sha256:2222000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"rate_query_attestation_ref": "sha256:3333000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
"rate_id": "PDR-42"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "TRANSPORT_RAIL",
|
||||||
|
"amount": "100.00",
|
||||||
|
"incurred_on": "2026-10-01",
|
||||||
|
"description": "ICE Hin- und Rückfahrt",
|
||||||
|
"receipt_ref": "pii://receipts/WF-2026-0044/01HFABC.pdf",
|
||||||
|
"receipt_content_hash": "sha256:b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3",
|
||||||
|
"receipt_attestation_ref": "sha256:cafefeeddeadbeefcafefeeddeadbeefcafefeeddeadbeefcafefeeddeadbeef"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
147
schemas/expense/canonical/v1.2.0.json
Normal file
147
schemas/expense/canonical/v1.2.0.json
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://basicstack.de/schemas/expense/canonical/v1.2.0.json",
|
||||||
|
"title": "Expense (canonical) v1.2.0",
|
||||||
|
"description": "Additive gegenüber v1.1.0: (1) pro Item optional receipt_content_hash + receipt_attestation_ref, damit die Belegbindung in die Signatur-Kette eingeht. (2) Neue Item-Typen PER_DIEM_FULL, PER_DIEM_PARTIAL, PER_DIEM_ARRIVAL, PER_DIEM_DEPARTURE für vom per-diem-calculator erzeugte Pauschalen. (3) Pro Item optional computed_from — Rückbindung an die Transform- und Query-Attestationen, aus denen das Item deterministisch gerechnet wurde.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["schema", "workflow_id", "employee_id", "trip", "currency", "total_amount", "expense_items"],
|
||||||
|
"properties": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "version"],
|
||||||
|
"properties": {
|
||||||
|
"name": { "const": "expense.canonical" },
|
||||||
|
"version": { "const": "1.2.0" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workflow_id": { "type": "string", "pattern": "^WF-[0-9]{4}-[0-9]{4,}$" },
|
||||||
|
"employee_id": { "type": "string", "pattern": "^EMP-[0-9]+$" },
|
||||||
|
"trip": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["start_date", "end_date", "destination"],
|
||||||
|
"properties": {
|
||||||
|
"start_date": { "type": "string", "format": "date" },
|
||||||
|
"end_date": { "type": "string", "format": "date" },
|
||||||
|
"destination": { "type": "string", "minLength": 1, "maxLength": 200 },
|
||||||
|
"purpose": { "type": "string", "maxLength": 500 },
|
||||||
|
"countries_visited": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 20,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["country", "from_date", "to_date"],
|
||||||
|
"properties": {
|
||||||
|
"country": { "type": "string", "pattern": "^[A-Z]{2}$" },
|
||||||
|
"city": { "type": ["string","null"], "maxLength": 100 },
|
||||||
|
"from_date": { "type": "string", "format": "date" },
|
||||||
|
"to_date": { "type": "string", "format": "date" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
|
||||||
|
"total_amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$" },
|
||||||
|
"project_assignment": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["project_code", "cost_center", "source_attestation_ref"],
|
||||||
|
"properties": {
|
||||||
|
"project_code": { "type": "string", "pattern": "^P-[0-9]+$" },
|
||||||
|
"cost_center": { "type": "string", "pattern": "^CC-[0-9]+$" },
|
||||||
|
"source_attestation_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expense_items": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 200,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["type", "amount", "incurred_on"],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"HOTEL", "MEALS", "TRANSPORT_FLIGHT", "TRANSPORT_RAIL", "TRANSPORT_CAR",
|
||||||
|
"PARKING", "COMMUNICATION", "CONFERENCE_FEE", "OTHER",
|
||||||
|
"PER_DIEM_FULL", "PER_DIEM_PARTIAL", "PER_DIEM_ARRIVAL", "PER_DIEM_DEPARTURE"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$" },
|
||||||
|
"incurred_on": { "type": "string", "format": "date" },
|
||||||
|
"description": { "type": "string", "maxLength": 500 },
|
||||||
|
"receipt_ref": { "type": "string", "pattern": "^pii://[A-Za-z0-9/._-]+$" },
|
||||||
|
"receipt_content_hash": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^sha256:[0-9a-f]{64}$",
|
||||||
|
"description": "SHA-256 des hochgeladenen Belegs. Wird vom Receipt-Adapter beim Upload geliefert und ins Item eingetragen; vom Approval mit-signiert."
|
||||||
|
},
|
||||||
|
"receipt_attestation_ref": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^sha256:[0-9a-f]{64}$",
|
||||||
|
"description": "SHA-256 der Receipt Attestation (ingest). Verification kann so prüfen, dass genau dieser Beleg vorlag."
|
||||||
|
},
|
||||||
|
"computed_from": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["transform_attestation_ref"],
|
||||||
|
"description": "Nur gesetzt für deterministisch berechnete Items (z.B. PER_DIEM_*). Bindet an die Transform-Attestation, die dieses Item erzeugt hat, und optional an die Rate-Query-Attestation.",
|
||||||
|
"properties": {
|
||||||
|
"transform_attestation_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
||||||
|
"rate_query_attestation_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
||||||
|
"rate_id": { "type": "string", "pattern": "^PDR-[0-9]+$" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allocations": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 50,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["project_code", "cost_center", "mode", "source_attestation_ref"],
|
||||||
|
"properties": {
|
||||||
|
"project_code": { "type": "string", "pattern": "^P-[0-9]+$" },
|
||||||
|
"cost_center": { "type": "string", "pattern": "^CC-[0-9]+$" },
|
||||||
|
"mode": { "enum": ["percentage", "absolute"] },
|
||||||
|
"percentage": { "type": "string", "pattern": "^(100\\.00|[0-9]{1,2}\\.[0-9]{2})$" },
|
||||||
|
"amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$" },
|
||||||
|
"source_attestation_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": { "properties": { "mode": { "const": "percentage" } }, "required": ["mode"] },
|
||||||
|
"then": { "required": ["percentage"], "not": { "required": ["amount"] } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": { "properties": { "mode": { "const": "absolute" } }, "required": ["mode"] },
|
||||||
|
"then": { "required": ["amount"], "not": { "required": ["percentage"] } }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"enum": ["PER_DIEM_FULL", "PER_DIEM_PARTIAL", "PER_DIEM_ARRIVAL", "PER_DIEM_DEPARTURE"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type"]
|
||||||
|
},
|
||||||
|
"then": { "required": ["computed_from"] }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
{
|
||||||
|
"schema": { "name": "sink.db.expense", "version": "1.2.0" },
|
||||||
|
"workflow_id": "WF-2026-0044",
|
||||||
|
"approval_id": "APR-2026-0044",
|
||||||
|
"decision_id": "DEC-2026-0044",
|
||||||
|
"verification_ref": "sha256:4444000000000000000000000000000000000000000000000000000000000004",
|
||||||
|
"canonical_payload_hash": "sha256:5555000000000000000000000000000000000000000000000000000000000005",
|
||||||
|
"expense": {
|
||||||
|
"employee_id": "EMP-12345",
|
||||||
|
"currency": "EUR",
|
||||||
|
"total_amount": "484.50",
|
||||||
|
"trip": {
|
||||||
|
"start_date": "2026-10-01",
|
||||||
|
"end_date": "2026-10-03",
|
||||||
|
"destination": "Berlin",
|
||||||
|
"purpose": "Kundentermin + Workshop"
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "HOTEL",
|
||||||
|
"amount": "328.50",
|
||||||
|
"incurred_on": "2026-10-01",
|
||||||
|
"description": "Hotel Berlin Mitte, 2 Nächte",
|
||||||
|
"receipt_ref": "pii://receipts/WF-2026-0044/01HFXYZ.pdf",
|
||||||
|
"receipt_content_hash": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
|
||||||
|
"receipt_attestation_ref": "sha256:deadbeefcafefeeddeadbeefcafefeeddeadbeefcafefeeddeadbeefcafefeed",
|
||||||
|
"allocations": [
|
||||||
|
{
|
||||||
|
"project_code": "P-42", "cost_center": "CC-100", "gl_account": "640100",
|
||||||
|
"amount": "328.50",
|
||||||
|
"source_attestation_ref": "sha256:1111000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PER_DIEM_ARRIVAL",
|
||||||
|
"amount": "14.00",
|
||||||
|
"incurred_on": "2026-10-01",
|
||||||
|
"computed_from": {
|
||||||
|
"transform_attestation_ref": "sha256:2222000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"rate_query_attestation_ref": "sha256:3333000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
"rate_id": "PDR-42"
|
||||||
|
},
|
||||||
|
"allocations": [
|
||||||
|
{
|
||||||
|
"project_code": "P-42", "cost_center": "CC-100", "gl_account": "646100",
|
||||||
|
"amount": "14.00",
|
||||||
|
"source_attestation_ref": "sha256:1111000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PER_DIEM_FULL",
|
||||||
|
"amount": "28.00",
|
||||||
|
"incurred_on": "2026-10-02",
|
||||||
|
"computed_from": {
|
||||||
|
"transform_attestation_ref": "sha256:2222000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"rate_query_attestation_ref": "sha256:3333000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
"rate_id": "PDR-42"
|
||||||
|
},
|
||||||
|
"allocations": [
|
||||||
|
{
|
||||||
|
"project_code": "P-42", "cost_center": "CC-100", "gl_account": "646100",
|
||||||
|
"amount": "28.00",
|
||||||
|
"source_attestation_ref": "sha256:1111000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PER_DIEM_DEPARTURE",
|
||||||
|
"amount": "14.00",
|
||||||
|
"incurred_on": "2026-10-03",
|
||||||
|
"computed_from": {
|
||||||
|
"transform_attestation_ref": "sha256:2222000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
"rate_query_attestation_ref": "sha256:3333000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
"rate_id": "PDR-42"
|
||||||
|
},
|
||||||
|
"allocations": [
|
||||||
|
{
|
||||||
|
"project_code": "P-42", "cost_center": "CC-100", "gl_account": "646100",
|
||||||
|
"amount": "14.00",
|
||||||
|
"source_attestation_ref": "sha256:1111000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "TRANSPORT_RAIL",
|
||||||
|
"amount": "100.00",
|
||||||
|
"incurred_on": "2026-10-01",
|
||||||
|
"description": "ICE Hin- und Rückfahrt",
|
||||||
|
"receipt_ref": "pii://receipts/WF-2026-0044/01HFABC.pdf",
|
||||||
|
"receipt_content_hash": "sha256:b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3",
|
||||||
|
"receipt_attestation_ref": "sha256:cafefeeddeadbeefcafefeeddeadbeefcafefeeddeadbeefcafefeeddeadbeef",
|
||||||
|
"allocations": [
|
||||||
|
{
|
||||||
|
"project_code": "P-42", "cost_center": "CC-100", "gl_account": "640200",
|
||||||
|
"amount": "100.00",
|
||||||
|
"source_attestation_ref": "sha256:1111000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
100
schemas/sink/db/expense/v1.2.0.json
Normal file
100
schemas/sink/db/expense/v1.2.0.json
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://basicstack.de/schemas/sink/db/expense/v1.2.0.json",
|
||||||
|
"title": "Sink Payload: sink:expense-db-app, action expense.post v1.2.0",
|
||||||
|
"description": "Additive gegenüber v1.1.0: items[] können PER_DIEM_* Typen tragen, items[] können receipt_content_hash + receipt_attestation_ref + computed_from mitführen. allocations[]-Vertrag unverändert (absolut, Summenerhalt pro Item).",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["schema", "workflow_id", "approval_id", "decision_id", "verification_ref", "canonical_payload_hash", "expense"],
|
||||||
|
"properties": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "version"],
|
||||||
|
"properties": {
|
||||||
|
"name": { "const": "sink.db.expense" },
|
||||||
|
"version": { "const": "1.2.0" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workflow_id": { "type": "string", "pattern": "^WF-[0-9]{4}-[0-9]{4,}$" },
|
||||||
|
"approval_id": { "type": "string", "pattern": "^APR-[0-9]{4}-[0-9]{4,}$" },
|
||||||
|
"decision_id": { "type": "string", "pattern": "^DEC-[0-9]{4}-[0-9]{4,}$" },
|
||||||
|
"verification_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
||||||
|
"canonical_payload_hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
||||||
|
"expense": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["employee_id", "currency", "total_amount", "trip", "items"],
|
||||||
|
"properties": {
|
||||||
|
"employee_id": { "type": "string", "pattern": "^EMP-[0-9]+$" },
|
||||||
|
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
|
||||||
|
"total_amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$" },
|
||||||
|
"trip": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["start_date", "end_date", "destination"],
|
||||||
|
"properties": {
|
||||||
|
"start_date": { "type": "string", "format": "date" },
|
||||||
|
"end_date": { "type": "string", "format": "date" },
|
||||||
|
"destination": { "type": "string", "maxLength": 200 },
|
||||||
|
"purpose": { "type": "string", "maxLength": 500 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 200,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["type", "amount", "incurred_on", "allocations"],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"HOTEL", "MEALS", "TRANSPORT_FLIGHT", "TRANSPORT_RAIL", "TRANSPORT_CAR",
|
||||||
|
"PARKING", "COMMUNICATION", "CONFERENCE_FEE", "OTHER",
|
||||||
|
"PER_DIEM_FULL", "PER_DIEM_PARTIAL", "PER_DIEM_ARRIVAL", "PER_DIEM_DEPARTURE"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$" },
|
||||||
|
"incurred_on": { "type": "string", "format": "date" },
|
||||||
|
"description": { "type": "string", "maxLength": 500 },
|
||||||
|
"receipt_ref": { "type": "string", "pattern": "^pii://[A-Za-z0-9/._-]+$" },
|
||||||
|
"receipt_content_hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
||||||
|
"receipt_attestation_ref":{ "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
||||||
|
"computed_from": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["transform_attestation_ref"],
|
||||||
|
"properties": {
|
||||||
|
"transform_attestation_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
||||||
|
"rate_query_attestation_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
||||||
|
"rate_id": { "type": "string", "pattern": "^PDR-[0-9]+$" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allocations": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 50,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["project_code", "cost_center", "gl_account", "amount", "source_attestation_ref"],
|
||||||
|
"properties": {
|
||||||
|
"project_code": { "type": "string", "pattern": "^P-[0-9]+$" },
|
||||||
|
"cost_center": { "type": "string", "pattern": "^CC-[0-9]+$" },
|
||||||
|
"gl_account": { "type": "string", "pattern": "^[0-9]{4,6}$" },
|
||||||
|
"amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$" },
|
||||||
|
"percentage": { "type": "string", "pattern": "^(100\\.00|[0-9]{1,2}\\.[0-9]{2})$" },
|
||||||
|
"source_attestation_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue