{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://basicstack.de/schemas/expense/canonical/v1.0.0.json", "title": "Expense (canonical) v1.0.0", "description": "Kanonische Reisekostenstruktur. Bindet die Benutzerfreigabe. Technische Senken-Mappings dürfen die hier definierten Felder nicht in ihrer geschäftlichen Bedeutung ändern — insbesondere total_amount, currency und expense_items[].amount.", "type": "object", "additionalProperties": false, "required": ["schema", "workflow_id", "employee_id", "trip", "currency", "total_amount", "expense_items", "project_assignment"], "properties": { "schema": { "type": "object", "additionalProperties": false, "required": ["name", "version"], "properties": { "name": { "const": "expense.canonical" }, "version": { "const": "1.0.0" } } }, "workflow_id": { "type": "string", "pattern": "^WF-[0-9]{4}-[0-9]{4,}$", "description": "Prozess-ID, konstant über die gesamte Reisekostenabrechnung." }, "employee_id": { "type": "string", "pattern": "^EMP-[0-9]+$", "description": "Interne Mitarbeiter-ID. Nicht die SAP-Personalnummer — die wird senken-seitig zugeordnet." }, "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 } } }, "currency": { "type": "string", "pattern": "^[A-Z]{3}$", "description": "ISO 4217 Währungscode (z.B. EUR)." }, "total_amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$", "description": "Summe aller expense_items, als Dezimal-String mit zwei Nachkommastellen (JSON Number vermeidet Rundungsfehler)." }, "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"] }, "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/._-]+$", "description": "Referenz in den PII-Store (verschlüsselter S3-Bucket), nicht der Beleg selbst." } } } }, "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}$", "description": "SHA-256 der Query Attestation (ds:expense-db-app / project_assignments), aus der dieses Projekt stammt. Zwingend, damit Verification nachweisen kann, dass das gewählte Projekt tatsächlich in der dem Benutzer vorgelegten Liste war." } } } } }