{ "$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"] } } ] } } } }