Handler und Lebenszyklus¶
Wie du Dokumente über die Handler lädst, Stages ausführst und den Zustand am Handler liest.
DocumentHandler — Lebenszyklus-Abstraktion¶
DiWaSS-Dokumente durchlaufen mehrstufige Lebenszyklen mit serverseitiger Versionierung und Validierung. Statt das im Anwendungs-Code abzubilden, kapselt pro Dokumenttyp ein Handler den Zyklus. client.DocumentHandler ist die Factory:
| Factory-Methode | Handler | Ablage in ZEDAL |
|---|---|---|
CreateNotificationHandler() |
NotificationDocumentHandler — Notifizierung aus Unternehmenssicht |
Nachweis-Bereich (Akte) |
CreateMovementHandler() |
MovementDocumentHandler — Begleitformular aus Unternehmenssicht |
Versandpapier-Bereich |
CreateAnnex7Handler() |
Annex7DocumentHandler — Annex-VII-Dokument |
Versandpapier-Bereich |
CreateAuthorityNotificationHandler() |
AuthorityNotificationDocumentHandler — Notifizierung aus Behördensicht |
Nachweis-Bereich (Akte) |
CreateAuthorityMovementHandler() |
AuthorityMovementDocumentHandler — Begleitformular aus Behördensicht |
Nachweis-Bereich (Akte) |
CreateBmuHandler() |
BmuDocumentHandler — BMU-Begleitschein und -Übernahmeschein, kein DiWaSS |
eigener Weg (siehe unten) |
CreateInboxImporter() |
IInboxImporter — Eingänge übernehmen |
— |
Was die Speicherbereiche unterscheidet, steht unter Speicherbereiche.
BmuDocumentHandler ist kein DiWaSS-Handler
Er erbt nicht von DiwassDocumentHandlerBase und hat deshalb weder SendToDiwass() noch WaitForDiwassTransfer() noch FollowupIntents. Sein Weg heißt SubmitTransportDocumentToZedal(...), und geladen wird über LoadTransportDocumentFromZedal(docIdentifier, value). Wer beide Welten bedient, kann die Handler nicht über einen gemeinsamen Basistyp behandeln. Seine Methoden stehen unter BmuDocumentHandler.
Ein bestehendes Dokument laden¶
Hier verwechseln sich zwei Dinge, die gleich klingen und verschiedene Kennungen brauchen:
| Aufruf | Parameter | Wann |
|---|---|---|
LoadFromZedal(DocumentId docId) |
ZEDAL-Dokument-Id (DocumentId) |
Du hast das Dokument in dieser Anwendung schon einmal gespeichert oder geladen und die Id behalten (handler.ZedalDocumentId), oder du hast sie aus einer Inbox-/Query-Antwort. |
LoadFromZedalByNotificationId(string) |
fachliche Notifizierungsnummer | Du kennst nur die Nummer aus dem Vorgang. Nur auf NotificationDocumentHandler. |
LoadFromZedalByMovementId(string) |
fachliche Movement-Kennung | Nur auf MovementDocumentHandler. |
LoadFromZedalByAnnex7DocumentNo(string) |
Annex-VII-Dokumentnummer | Nur auf Annex7DocumentHandler. |
ReloadFromZedal() |
— | Lädt die aktuellste Version derselben Id. Verlangt eine gesetzte ZedalDocumentId, sonst InvalidOperationException. |
DocumentId ist ein generierter Protobuf-Typ, kein String. Ein LoadFromZedal("WSR/DE/…") kompiliert nicht — dafür sind die …By…-Varianten da.
Die …By…-Varianten lösen die fachliche Kennung über die Dokumentlisten der QueryApi auf und delegieren dann an LoadFromZedal. Zwei Verhaltensweisen sind wichtig:
- Kein Treffer →
KeyNotFoundExceptionmit der gesuchten Kennung im Text. Das ist der Normalfall, wenn ein Fremd-Update noch nicht im ZEDAL-Bestand angekommen ist — Fremdänderungen brauchen zwei bis drei Minuten und den Inbox-Import, siehe Aufrufe und Synchronisation. - Mehrere Treffer → geladen wird defensiv das Dokument mit dem jüngsten
last_changed.
Zustand am Handler¶
Die Stage- und Sende-Methoden geben nichts zurück. Alles Weitere liest du an Properties ab:
| Property | Inhalt |
|---|---|
DocXml |
Aktueller Dokumentstand als ZeWaSS-XML (byte[]) |
FollowupIntents |
Mögliche nächste Aktionen im regulatorischen Lebenszyklus. Property, keine Methode |
ValidationErrors / HasValidationErrors |
Validierungsfehler der letzten Mutation |
TransferHistory |
Versand-Historie, neuester Eintrag zuerst |
HasError / LastError |
Bezieht sich auf TransferHistory[0], also den jüngsten Versuch |
ZedalDocumentId |
Id im ZEDAL-Dokumentenspeicher |
TargetFileId |
Akte, in der das Dokument liegt. null bei Ablage im Versandpapier-Bereich |
LastTransferLogId |
Log-Id des letzten Versands; Schlüssel für das Status-Polling |
DiwassNotificationId |
Von DiWaSS zugewiesene Kennung, sobald der Versand synchronisiert ist |
Zustand nie über Listenlängen bestimmen
ValidationErrors wird von Refresh() nicht zurückgesetzt, weil die Get-Endpunkte keine Validierungsfehler liefern — eine gefüllte Liste kann also von einer früheren Mutation stammen. TransferHistory füllt sich nur über Refresh(), nicht über Mutationen. Und die Antwort auf eine Behörden-Nachforderung landet in der requestInformation-Liste, während die Antwortliste leer bleibt.
Werte deshalb FollowupIntents aus, nicht die Länge einzelner Listen. Details unter Lebenszyklus → Zustand bestimmen.
Der Weg durch eine Stage¶
var handler = client.DocumentHandler.CreateNotificationHandler();
await handler.CreateSubmission(submission); // SubmissionDocumentType
await handler.SendToDiwass(
ZedalCallContext.Default.WithTimeout(180)); // budget per operation, see Lifecycle
await handler.WaitForDiwassTransfer(
timeout: TimeSpan.FromMinutes(10),
pollInterval: TimeSpan.FromSeconds(15));
await handler.ReloadFromZedal(); // only then do server fields appear in DocXml
if (handler.HasValidationErrors)
{
foreach (var error in handler.ValidationErrors)
{
Console.WriteLine(error);
}
}
Vier Eigenschaften dieses Ablaufs, die man kennen muss:
SendToDiwass()speichert implizit. Ist dasDocXmlseit dem letzten Abgleich verändert oder lag es noch nie in ZEDAL, ruftSendToDiwass()selbstSaveToZedal()auf. Ein explizitesSaveToZedal()vorher ist erlaubt und wird erkannt — sinnvoll, wenn du dieZedalDocumentIdvor dem Push persistieren willst, etwa zur Wiederaufnahme nach einem Absturz.SaveToZedal()kommuniziert nicht nach außen. Es persistiert nur in ZEDAL. Optional nimmt es eine bestehende Akte (targetFile) — das greift nur beim Erst-Save eines Dokuments, das im Nachweis-Bereich landet. Ohne Angabe entsteht eine neue Akte.WaitForDiwassTransferlädt das Dokument nicht neu. Es pollt nur den Übermittlungsstatus. Wer auf serverseitig ergänzte Felder zugreifen will — etwa die zugewiesene Notifizierungsnummer —, muss danachReloadFromZedal()aufrufen.- Das Poll-Intervall hat eine Untergrenze.
MinTransferPollIntervalist 1 Sekunde; ein kleinerer Wert wirftArgumentOutOfRangeException, bevor gepollt wird. In der Praxis sind 5 bis 15 Sekunden angemessen.
WaitForDiwassTransfer kehrt bei RECEIVED_RECEIPT_POSITIVE und DELIVERED_TO_RECIPIENT_INBOX regulär zurück, wirft DiwassTransferFailedException bei RECEIVED_RECEIPT_NEGATIVE und CANCELLED und TimeoutException, wenn der Status nicht rechtzeitig terminal wird. Die Bedeutung dieser Zustände steht unter Versandverfahren.
Für alles, was kein Übermittlungsstatus ist, gibt es WaitUntil:
await handler.WaitUntil(
condition: h => h.FollowupIntents.Count > 0,
timeout: TimeSpan.FromMinutes(10),
pollInterval: TimeSpan.FromSeconds(15));
WaitUntil lädt das Dokument bei jedem Durchlauf neu (ReloadFromZedal) und prüft dann die Bedingung. Die Deadline aus dem ZedalCallContext gilt pro Reload, nicht für die Gesamtwartezeit — die steuert timeout. Der CancellationToken beendet auch die Wartezeit zwischen zwei Reloads, ein Abbruch hängt also nicht bis zum Ende des Intervalls.
Das Zeitbudget muss zu dem passen, worauf du wartest. Für die Rückmeldung des EU-Systems auf einen eigenen Vorgang sind Minuten realistisch — Richtwerte unter Versandverfahren. Auf eine Behördenentscheidung wartet man dagegen nicht in einer Schleife: Das ist ein Vorgang mit menschlicher Beteiligung und gehört in den periodischen Abgleich, nicht in ein Timeout.
Warten ersetzt kein Abholen
Beide Warteschleifen laufen im SDK, nicht am Dokument. Sie sagen nichts darüber, ob eine fachliche Antwort aus dem EU-System schon da ist — die kommt asynchron als neue Dokumentversion, und für Fremdänderungen ist der Inbox-Import der Zustellweg. Ein Polling ohne Import pollt gegen einen Stand, der sich nie ändert. Siehe Aufrufe und Synchronisation.
Welche Stage-Methode in welcher Reihenfolge zulässig ist, steht in den Anleitungen: Notifizierung (Unternehmen), Begleitformular (Unternehmen), Behördensicht.
Movement-Sequenznummern¶
Die Nummernvergabe liegt unter client.DocumentNumbers:
Die Nummer wird nicht serverseitig reserviert: Zwei Prozesse, die gleichzeitig fragen, bekommen dieselbe. Mehrere aufeinanderfolgende Nummern holt GetNextMovementSequenceNumbers(notificationId, count) in einer Abfrage. Für den Offline-Fall gibt es MovementSequenceNumber.NextFrom(IEnumerable<string>) — dieselbe Rechnung über eine selbst geführte Liste. Formate und Kontingentregel stehen unter Begleitformular (Unternehmen).