Compare commits
8 Commits
30170d85ac
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c74e49e11b | |||
| a099d28113 | |||
| a2471befe2 | |||
| e70a9176ca | |||
| 6418e14be3 | |||
| ca7b26509a | |||
| a74b220ed7 | |||
| 99f492d5eb |
@@ -12,7 +12,12 @@
|
||||
"WebFetch(domain:medium.com)",
|
||||
"WebFetch(domain:quarkus.io)",
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:walidhajeri.hashnode.dev)"
|
||||
"WebFetch(domain:walidhajeri.hashnode.dev)",
|
||||
"Bash(find \"C:\\\\\\\\src\\\\\\\\Galabau\\\\\\\\glb-spielwiese\\\\\\\\automaton\" -name \"FileProcessingPipeline.java\")",
|
||||
"PowerShell(Get-ChildItem -Path \"C:\\\\src\\\\Galabau\\\\glb-spielwiese\\\\quarkus-automaton\\\\src\\\\main\\\\java\" -Recurse | Where-Object { !$_.PSIsContainer } | Select-Object FullName)",
|
||||
"PowerShell(cmd /c \"dir /s /b C:\\\\src\\\\Galabau\\\\glb-spielwiese\\\\quarkus-automaton\\\\src\\\\main\\\\java\")",
|
||||
"Bash(Get-ChildItem -Path \"C:\\\\src\\\\Galabau\\\\glb-spielwiese\" -Directory)",
|
||||
"Bash(Select-Object Name)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ create or replace package body pck_auto_import as
|
||||
PROCEDURE p_create_wv_autonomous(i_swv_bemerkung inkasso.sy_wiedervorlage.swv_bemerkung%TYPE DEFAULT NULL
|
||||
)
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Erstellt eine Wiedervorlage innerhalb eine autonomous transaction, damit rollback/commits in Fehlerfällen, diese Wiedervorlage nicht beeinflussen.
|
||||
-- Beschreibung: Erstellt eine Wiedervorlage innerhalb eine autonomous transaction, damit rollback/commits in Fehlerfällen, diese Wiedervorlage nicht beeinflussen.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: —
|
||||
-- Parameter: —
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-27 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -17,7 +17,7 @@ create or replace package body pck_auto_import as
|
||||
BEGIN
|
||||
pck_wiedervorlage.p_wiedervorlage_anlegen (i_swv_stp_id_art => pck_stammdaten.f_get_stp_id_by_programmid('WV_IMPORT_BA_KORRES')
|
||||
,i_swv_wdatum => sysdate
|
||||
,i_swv_bemerkung => i_swv_bemerkung --'Bitte manuell Prüfen: Beim automatischen Import der BA-Datei "' || l_filename || '" ist folgende Fehler aufgetreten: "' || SQLERRM || '" (Siehe "' || i_object_key || '").'
|
||||
,i_swv_bemerkung => i_swv_bemerkung --'Bitte manuell Prüfen: Beim automatischen Import der BA-Datei "' || l_filename || '" ist folgende Fehler aufgetreten: "' || SQLERRM || '" (Siehe "' || i_object_key || '").'
|
||||
,i_swv_mit_id_wsachbearbeiter => pck_system.f_get_par_wert_by_programmid('BA_IMPORT_SB_MIT_ID')
|
||||
);
|
||||
COMMIT; -- Nur diese autonome Transaktion
|
||||
@@ -25,16 +25,16 @@ create or replace package body pck_auto_import as
|
||||
|
||||
procedure p_run_ba_korrespondenz_dateieingang_automation
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Einstiegspunkt für die APEX Automation (stündlich).
|
||||
-- Schritt 1: p_process_incoming_ba_data aufrufen — verarbeitet Batches, die bereits
|
||||
-- in OCI liegen (Fallback für den Fall, dass der ORDS-Aufruf im letzten
|
||||
-- Beschreibung: Einstiegspunkt für die APEX Automation (stündlich).
|
||||
-- Schritt 1: p_process_incoming_ba_data aufrufen — verarbeitet Batches, die bereits
|
||||
-- in OCI liegen (Fallback für den Fall, dass der ORDS-Aufruf im letzten
|
||||
-- Quarkus-Lauf fehlgeschlagen ist).
|
||||
-- Schritt 2: Quarkus Dateieingang Service via HTTP POST anstoßen (fire & forget).
|
||||
-- Schlägt Schritt 2 fehl, läuft Schritt 1 beim nächsten Stundenlauf erneut.
|
||||
-- Schritt 2: Quarkus Dateieingang Service via HTTP POST anstoßen (fire & forget).
|
||||
-- Schlägt Schritt 2 fehl, läuft Schritt 1 beim nächsten Stundenlauf erneut.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: —
|
||||
-- Parameter: —
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-21 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -55,7 +55,7 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Verarbeitung offener OCI-Batches gestartet'
|
||||
,i_message => 'Verarbeitung offener BA-Korrespondenz-Dateien aus OCI gestartet'
|
||||
);
|
||||
|
||||
p_process_incoming_ba_data;
|
||||
@@ -63,14 +63,14 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Verarbeitung offener OCI-Batches abgeschlossen'
|
||||
,i_message => 'Verarbeitung offener BA-Korrespondenz-Dateien aus OCI abgeschlossen'
|
||||
);
|
||||
|
||||
-- Quarkus anstoßen — Fehler werden geloggt, nicht eskaliert
|
||||
-- Quarkus anstoßen — Fehler werden geloggt, nicht eskaliert
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Quarkus Dateieingang Service wird angestoßen'
|
||||
,i_message => 'Quarkus Dateieingang Service wird angestoßen'
|
||||
);
|
||||
begin
|
||||
l_service_url := pck_system.f_get_par_wert_by_programmid('AUTOMATON_BASE_URL') || l_automaton_endpoint;
|
||||
@@ -94,15 +94,15 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'BA Dateieingang Service angestoßen (202 Accepted)'
|
||||
,i_message => 'BA Dateieingang Service angestoßen (202 Accepted)'
|
||||
);
|
||||
when 409
|
||||
then
|
||||
-- Service läuft bereits — kein Fehler, kein zweiter Lauf nötig
|
||||
-- Service läuft bereits — kein Fehler, kein zweiter Lauf nötig
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'BA Dateieingang Service läuft bereits (409 Conflict) — kein neuer Lauf gestartet'
|
||||
,i_message => 'BA Dateieingang Service läuft bereits (409 Conflict) — kein neuer Lauf gestartet'
|
||||
);
|
||||
else
|
||||
pck_log.p_error(
|
||||
@@ -116,12 +116,12 @@ create or replace package body pck_auto_import as
|
||||
when others
|
||||
then
|
||||
-- Quarkus-Aufruf fehlgeschlagen: loggen, nicht eskalieren.
|
||||
-- Nächster Stundenlauf führt BA-Import-Schritt erneut aus.
|
||||
-- Nächster Stundenlauf führt BA-Import-Schritt erneut aus.
|
||||
pck_log.p_error(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Aufruf des BA Dateieingang Service fehlgeschlagen: ' || sqlerrm
|
||||
,i_detail => to_clob(dbms_utility.format_error_backtrace)
|
||||
,i_detail => to_clob(dbms_utility.format_error_stack) || ' -- Backtrace: -- ' || to_clob(dbms_utility.format_error_backtrace)
|
||||
);
|
||||
end;
|
||||
end p_run_ba_korrespondenz_dateieingang_automation;
|
||||
@@ -134,16 +134,16 @@ create or replace package body pck_auto_import as
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Importiert eine einzelne Datei aus dem OCI Eingangsordner in die Datenbank.
|
||||
-- Ruft inkasso.pck_import.f_import_ba_dokument auf.
|
||||
-- Bei Rückgabe 1 (Erfolg): Datei in den Zielordner verschieben.
|
||||
-- Bei Rückgabe != 1 (z.B. ungültiger Dateiname): Warnung loggen und Exception werfen
|
||||
-- — Datei bleibt liegen, Commit/Rollback liegt beim Aufrufer.
|
||||
-- Kein Commit hier — wird von p_process_incoming_ba_data übernommen.
|
||||
-- Bei Rückgabe 1 (Erfolg): Datei in den Zielordner verschieben.
|
||||
-- Bei Rückgabe != 1 (z.B. ungültiger Dateiname): Warnung loggen und Exception werfen
|
||||
-- — Datei bleibt liegen, Commit/Rollback liegt beim Aufrufer.
|
||||
-- Kein Commit hier — wird von p_process_incoming_ba_data übernommen.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Vollständiger OCI-Objektkey der zu verarbeitenden Datei
|
||||
-- Parameter: i_object_key Vollständiger OCI-Objektkey der zu verarbeitenden Datei
|
||||
-- i_content Dateiinhalt als BLOB
|
||||
-- i_target_folder Zielordner-Prefix für erfolgreich verarbeitete Dateien
|
||||
-- i_target_folder Zielordner-Prefix für erfolgreich verarbeitete Dateien
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Stub erstellt
|
||||
-- SCK 2026-04-09 Implementierung: Aufruf f_import_ba_dokument, Wiedervorlage bei Fehler
|
||||
-- SCK 2026-04-09 Move nach erfolgreichem Import in p_import_ba_korrespondenz verschoben
|
||||
@@ -177,12 +177,12 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_warn(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Aufruf von pck_import.f_import_ba_dokument für Datei "' || l_filename || '" hat einen Fehler geworfen (' || SQLERRM || '). Erstelle wiedervorlage...'
|
||||
,i_message => 'Aufruf von pck_import.f_import_ba_dokument für Datei "' || l_filename || '" hat einen Fehler geworfen (' || SQLERRM || '). Erstelle wiedervorlage...'
|
||||
,i_object_ref => i_object_key
|
||||
);
|
||||
|
||||
-- Bei einem Import Fehler: Wiedervorlage für Sachbearbeiter erstellen & Fehlermeldung mit in die Wiedervorlage schreiben
|
||||
p_create_wv_autonomous(i_swv_bemerkung => 'Bitte manuell Prüfen: Beim automatischen Import der BA-Datei "' || l_filename
|
||||
-- Bei einem Import Fehler: Wiedervorlage für Sachbearbeiter erstellen & Fehlermeldung mit in die Wiedervorlage schreiben
|
||||
p_create_wv_autonomous(i_swv_bemerkung => 'Bitte manuell Prüfen: Beim automatischen Import der BA-Datei "' || l_filename
|
||||
|| '" ist folgender Fehler aufgetreten: "' || SQLERRM || '" (Siehe "' || i_object_key || '").'
|
||||
);
|
||||
raise;
|
||||
@@ -193,12 +193,12 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_warn(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Import für Datei "' || l_filename || '" fehlgeschlagen (Rückgabe: ' || l_return || ') — Wiedervorlage erforderlich'
|
||||
,i_message => 'Import für Datei "' || l_filename || '" fehlgeschlagen (Rückgabe: ' || l_return || ') — Wiedervorlage erforderlich'
|
||||
,i_object_ref => i_object_key
|
||||
);
|
||||
|
||||
-- Wiedervorlage für Sachbearbeiter erstellen
|
||||
p_create_wv_autonomous(i_swv_bemerkung => 'Bitte manuell Prüfen: Die BA-Datei "' || l_filename || '" konnte nicht automatisch importiert werden (Siehe "' || i_object_key || '").'
|
||||
-- Wiedervorlage für Sachbearbeiter erstellen
|
||||
p_create_wv_autonomous(i_swv_bemerkung => 'Bitte manuell Prüfen: Die BA-Datei "' || l_filename || '" konnte nicht automatisch importiert werden (Siehe "' || i_object_key || '").'
|
||||
);
|
||||
|
||||
pck_log.p_info(
|
||||
@@ -208,7 +208,7 @@ create or replace package body pck_auto_import as
|
||||
,i_object_ref => i_object_key
|
||||
);
|
||||
|
||||
raise_application_error(-20000, 'Import fehlgeschlagen: "' || l_filename || '" (Rückgabe: ' || l_return || ')');
|
||||
raise_application_error(-20000, 'Import fehlgeschlagen: "' || l_filename || '" (Rückgabe: ' || l_return || ')');
|
||||
end if;
|
||||
|
||||
pck_log.p_info(
|
||||
@@ -236,13 +236,13 @@ create or replace package body pck_auto_import as
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Verarbeitet alle fertigen Eingangs-Batches aus dem OCI Eingangsordner (Netzlaufwerk).
|
||||
-- Wird von ORDS-Endpunkt (von Quarkus Automaton) und aus Apex Automation aufgerufen.
|
||||
-- Pro Datei: Import + Move → Commit; bei Exception: Rollback, Datei bleibt liegen,
|
||||
-- nächste Datei wird trotzdem verarbeitet.
|
||||
-- Nach dem Datei-Loop: DB-Marker immer löschen (verhindert erneuten Durchlauf).
|
||||
-- Pro Datei: Import + Move -> Commit; bei Exception: Rollback, Datei bleibt liegen,
|
||||
-- nächste Datei wird trotzdem verarbeitet.
|
||||
-- Nach dem Datei-Loop: DB-Marker immer löschen (verhindert erneuten Durchlauf).
|
||||
-- Wenn danach noch Dateien im Ordner liegen: Sachbearbeiter(SB)-Marker anlegen damit Sachbearbeiter
|
||||
-- die übriggebliebenen Dateien manuell prüfen können.
|
||||
-- die übriggebliebenen Dateien manuell prüfen können.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
-- SCK 2026-04-09 Fehlerbehandlung: Datei bleibt liegen, Fehler-Marker, kein erneuter Durchlauf
|
||||
-- SCK 2026-04-09 SB-Marker statt l_had_errors-Flag; Move in p_import_ba_korrespondenz verschoben
|
||||
@@ -264,14 +264,14 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Verarbeitung von eingehen BA Korrespondenzen gestartet'
|
||||
,i_message => 'BA-KORRESPONDENZEN IMPORT-START: Verarbeitung von eingehen BA Korrespondenzen gestartet'
|
||||
);
|
||||
|
||||
-- Zielordner Name zusammenstellen
|
||||
l_target_prefix := pck_system.f_get_par_wert_by_programmid('NETSTORE_TENANT_ID') || pck_system.f_get_par_wert_by_programmid('NETSTORE_BA_PREFIX') || pck_system.f_get_par_wert_by_programmid('NETSTORE_BA_KOR_ARC') || ' ' || to_char(sysdate, 'YYYY') || '/';
|
||||
l_eingang_prefix := pck_system.f_get_par_wert_by_programmid('NETSTORE_TENANT_ID') || pck_system.f_get_par_wert_by_programmid('NETSTORE_BA_PREFIX') || pck_system.f_get_par_wert_by_programmid('NETSTORE_BA_KOR_IM');
|
||||
|
||||
-- Unterordner in eingangs-ordner auflisten (es gibt einen Ordner für jeden entpackte ZIP-Datei)
|
||||
-- Unterordner in eingangs-ordner auflisten (es gibt einen Ordner für jeden entpackte ZIP-Datei)
|
||||
l_folders := pck_net_storage.f_list_objects(
|
||||
i_parent_folder => l_eingang_prefix
|
||||
,i_include_subfolders => 'N'
|
||||
@@ -293,17 +293,17 @@ create or replace package body pck_auto_import as
|
||||
);
|
||||
|
||||
-- Der Marker ist eine Datei mit speziellem Namen, welche vom quarkus automaton in einen entpackten zip-ordner gelegt wird um zu signalisieren, dass alle Dateien des ZIPs erfolgreich in den ordner gelegt wurden.
|
||||
-- Das verhindert die verarbeitung von unvollständig entpackten zips
|
||||
-- Das verhindert die verarbeitung von unvollständig entpackten zips
|
||||
l_db_processing_marker_key := rec_folder.object_key || pck_system.f_get_par_wert_by_programmid('NETSTORE_MARKER_DB');
|
||||
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Marker für Ordner wird geprüft. Marker: ' || l_db_processing_marker_key || ' in Ordner: ' || rec_folder.object_key
|
||||
,i_message => 'Marker für Ordner wird geprüft. Marker: ' || l_db_processing_marker_key || ' in Ordner: ' || rec_folder.object_key
|
||||
,i_object_ref => rec_folder.object_key
|
||||
);
|
||||
|
||||
-- Marker prüfen: -20001 = nicht vorhanden → Upload noch nicht abgeschlossen
|
||||
-- Marker prüfen: -20001 = nicht vorhanden -> Upload noch nicht abgeschlossen
|
||||
begin
|
||||
l_meta := pck_net_storage.f_get_object_metadata(l_db_processing_marker_key);
|
||||
exception
|
||||
@@ -314,7 +314,7 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Kein DB-Verarbeitungsmarker vorhanden — Batch wird übersprungen (Upload noch nicht abgeschlossen)'
|
||||
,i_message => 'Kein DB-Verarbeitungsmarker vorhanden — Entpackter ZIP-Ordner wird übersprungen (Upload noch nicht abgeschlossen)'
|
||||
,i_object_ref => rec_folder.object_key
|
||||
);
|
||||
continue;
|
||||
@@ -322,7 +322,7 @@ create or replace package body pck_auto_import as
|
||||
raise;
|
||||
end;
|
||||
|
||||
-- Zip-Namen aus Ordnerpfad ableiten: eingang/<zip-name>/ → <zip-name>
|
||||
-- Zip-Namen aus Ordnerpfad ableiten: eingang/<zip-name>/ -> <zip-name>
|
||||
l_zip_name := substr(
|
||||
rec_folder.object_key
|
||||
,length(l_eingang_prefix) + 1
|
||||
@@ -333,7 +333,7 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Batch-Verarbeitung gestartet — Zielordner: "' || l_target_folder || '"'
|
||||
,i_message => 'ZIP-START: Verarbeitung von entpacktem ZIP-Ordner gestartet — Zielordner: "' || l_target_folder || '"'
|
||||
,i_object_ref => rec_folder.object_key
|
||||
);
|
||||
|
||||
@@ -345,7 +345,7 @@ create or replace package body pck_auto_import as
|
||||
|
||||
for rec_file in (select object_key, is_folder from table(l_files))
|
||||
loop
|
||||
-- Marker und Pseudo-Ordner überspringen
|
||||
-- Marker und Pseudo-Ordner überspringen
|
||||
if rec_file.object_key = l_db_processing_marker_key or rec_file.is_folder = 'Y'
|
||||
then
|
||||
continue;
|
||||
@@ -365,8 +365,8 @@ create or replace package body pck_auto_import as
|
||||
-- 2. Fachliche Verarbeitung + Move bei Erfolg (innerhalb p_import_ba_korrespondenz)
|
||||
p_import_ba_korrespondenz(rec_file.object_key, l_file_content, l_target_folder);
|
||||
|
||||
-- Commit pro Datei: OCI-Move ist nicht transaktional, daher DB-Änderungen sofort sichern
|
||||
-- sonst würde ein Fehler bei einer späteren Datei den DB-Import bereits verschobener Dateien zurückrollen
|
||||
-- Commit pro Datei: OCI-Move ist nicht transaktional, daher DB-Änderungen sofort sichern
|
||||
-- sonst würde ein Fehler bei einer späteren Datei den DB-Import bereits verschobener Dateien zurückrollen
|
||||
commit;
|
||||
|
||||
exception
|
||||
@@ -383,17 +383,17 @@ create or replace package body pck_auto_import as
|
||||
end;
|
||||
end loop;
|
||||
|
||||
-- DB-Marker immer entfernen — verhindert erneute Verarbeitung beim nächsten Lauf
|
||||
-- DB-Marker immer entfernen — verhindert erneute Verarbeitung beim nächsten Lauf
|
||||
pck_net_storage.p_delete_object(l_db_processing_marker_key);
|
||||
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'DB-Verarbeitungsmarker gelöscht'
|
||||
,i_message => 'DB-Verarbeitungsmarker gelöscht'
|
||||
,i_object_ref => l_db_processing_marker_key
|
||||
);
|
||||
|
||||
-- Prüfen ob noch Dateien im Unterordner liegen (nicht erfolgreich importierte Dateien)
|
||||
-- Prüfen ob noch Dateien im Unterordner liegen (nicht erfolgreich importierte Dateien)
|
||||
l_files := pck_net_storage.f_list_objects(
|
||||
i_parent_folder => rec_folder.object_key
|
||||
,i_include_subfolders => 'Y'
|
||||
@@ -415,11 +415,11 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_warn(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Batch mit Fehlern abgeschlossen — mind. eine Datei konnte nicht importiert werden, SB-Marker wird hochgeladen...'
|
||||
,i_message => 'ZIP-ENDE: Entpackter ZIP-Ordner mit Fehlern abgeschlossen - mind. eine Datei konnte nicht importiert werden, SB-Marker wird hochgeladen...'
|
||||
,i_object_ref => rec_folder.object_key
|
||||
);
|
||||
|
||||
-- Sachbearbeiter (SB)-Marker anlegen: signalisiert Sachbearbeitern, dass Dateien manuell geprüft werden müssen
|
||||
-- Sachbearbeiter (SB)-Marker anlegen: signalisiert Sachbearbeitern, dass Dateien manuell geprüft werden müssen
|
||||
l_sb_marker_key := rec_folder.object_key || pck_system.f_get_par_wert_by_programmid('NETSTORE_MARKER_SB');
|
||||
pck_net_storage.p_upload_object(
|
||||
i_object_key => l_sb_marker_key
|
||||
@@ -431,11 +431,19 @@ create or replace package body pck_auto_import as
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'Batch abgeschlossen, alle Dateien erfolgreich importiert'
|
||||
,i_message => 'ZIP-ENDE: Entpackter ZIP-Ordner abgeschlossen, alle Dateien erfolgreich importiert'
|
||||
,i_object_ref => rec_folder.object_key
|
||||
);
|
||||
end if;
|
||||
end loop;
|
||||
|
||||
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => l_log_action
|
||||
,i_message => 'BA-KORRESPONDENZEN IMPORT-ENDE: Verarbeitung von eingehen BA Korrespondenzen abgeschlossen'
|
||||
);
|
||||
|
||||
end p_process_incoming_ba_data;
|
||||
|
||||
end pck_auto_import;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
create or replace package pck_auto_import as
|
||||
|
||||
-- Von APEX Automation aufgerufen: verarbeitet offene OCI-Batches, stößt dann Quarkus an
|
||||
-- Von APEX Automation aufgerufen: verarbeitet offene OCI-Batches, stoert dann Quarkus an
|
||||
procedure p_run_ba_korrespondenz_dateieingang_automation;
|
||||
|
||||
-- Von ORDS-Endpunkt aufgerufen (net_storage/process_incoming_ba_data): importiert Dateien aus OCI
|
||||
|
||||
@@ -9,18 +9,18 @@ create or replace package body pck_log as
|
||||
,i_object_ref in varchar2 default null
|
||||
)
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Interne Hilfsprozedur — schreibt einen Log-Eintrag in lg_app_log.
|
||||
-- Verwendet autonomous_transaction, damit der Commit unabhängig vom Aufrufer erfolgt.
|
||||
-- Wird ausschließlich von p_info, p_warn und p_error aufgerufen.
|
||||
-- Beschreibung: Interne Hilfsprozedur — schreibt einen Log-Eintrag in lg_app_log.
|
||||
-- Verwendet autonomous_transaction, damit der Commit unabhängig vom Aufrufer erfolgt.
|
||||
-- Wird ausschließlich von p_info, p_warn und p_error aufgerufen.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_level Log-Level (INFO, WARN, ERROR)
|
||||
-- i_module Aufgerufenes Modul / Package
|
||||
-- i_action Aktion innerhalb des Moduls
|
||||
-- i_message Kurze Meldung
|
||||
-- i_detail Optionaler Langtext (Stack Trace, JSON, etc.)
|
||||
-- i_object_ref Optionaler Objektbezug (z.B. Dateiname, Primärschlüssel)
|
||||
-- i_object_ref Optionaler Objektbezug (z.B. Dateiname, Primärschlüssel)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -73,9 +73,9 @@ create or replace package body pck_log as
|
||||
-- Parameter: i_module Aufgerufenes Modul / Package
|
||||
-- i_action Aktion innerhalb des Moduls
|
||||
-- i_message Kurze Meldung
|
||||
-- i_object_ref Optionaler Objektbezug (z.B. Dateiname, Primärschlüssel)
|
||||
-- i_object_ref Optionaler Objektbezug (z.B. Dateiname, Primärschlüssel)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -101,9 +101,9 @@ create or replace package body pck_log as
|
||||
-- Parameter: i_module Aufgerufenes Modul / Package
|
||||
-- i_action Aktion innerhalb des Moduls
|
||||
-- i_message Kurze Meldung
|
||||
-- i_object_ref Optionaler Objektbezug (z.B. Dateiname, Primärschlüssel)
|
||||
-- i_object_ref Optionaler Objektbezug (z.B. Dateiname, Primärschlüssel)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -131,9 +131,9 @@ create or replace package body pck_log as
|
||||
-- i_action Aktion innerhalb des Moduls
|
||||
-- i_message Kurze Fehlerbeschreibung
|
||||
-- i_detail Optionaler Langtext (Stack Trace, JSON, etc.)
|
||||
-- i_object_ref Optionaler Objektbezug (z.B. Dateiname, Primärschlüssel)
|
||||
-- i_object_ref Optionaler Objektbezug (z.B. Dateiname, Primärschlüssel)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
|
||||
@@ -9,15 +9,15 @@ create or replace package body pck_net_storage as
|
||||
,i_action in varchar2 default null
|
||||
) return varchar2
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Baut die vollständige OCI Object Storage URL aus den Konfigurationsparametern.
|
||||
-- Entweder für eine Bucket-Action, ein einzelnes Objekt oder den Bucket-Root.
|
||||
-- Beschreibung: Baut die vollständige OCI Object Storage URL aus den Konfigurationsparametern.
|
||||
-- Entweder für eine Bucket-Action, ein einzelnes Objekt oder den Bucket-Root.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Objektschlüssel (Pfad im Bucket); null für Bucket-Root oder Action-URL
|
||||
-- i_action OCI Bucket-Action (z.B. renameObject); null für Objekt-URL
|
||||
-- Parameter: i_object_key Objektschlüssel (Pfad im Bucket); null für Bucket-Root oder Action-URL
|
||||
-- i_action OCI Bucket-Action (z.B. renameObject); null für Objekt-URL
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Rückgabe: Vollständige URL als VARCHAR2
|
||||
-- Rückgabe: Vollständige URL als VARCHAR2
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Funktion erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -35,7 +35,7 @@ create or replace package body pck_net_storage as
|
||||
return l_base || '/actions/' || i_action;
|
||||
elsif i_object_key is not null
|
||||
then
|
||||
-- Sonderzeichen kodieren, Schrägstriche im Key unverändert lassen
|
||||
-- Sonderzeichen kodieren, Schrägstriche im Key unverändert lassen
|
||||
return l_base || '/o/' || utl_url.escape(i_object_key, false);
|
||||
else
|
||||
return l_base || '/o';
|
||||
@@ -57,15 +57,15 @@ create or replace package body pck_net_storage as
|
||||
|
||||
procedure p_assert_allowed (i_object_key in varchar2)
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Prüft den Objektschlüssel auf Gültigkeit, Path-Traversal-Angriffe und Tenant-Scope.
|
||||
-- Beschreibung: Prüft den Objektschlüssel auf Gültigkeit, Path-Traversal-Angriffe und Tenant-Scope.
|
||||
-- Wirft Application Error -20008 bei null-Key, -20004 bei Path Traversal,
|
||||
-- -20005 bei Scope-Verletzung.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Zu prüfender Objektschlüssel
|
||||
-- Parameter: i_object_key Zu prüfender Objektschlüssel
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
-- SCK 2026-04-10 Null-Prüfung und führender-Slash-Check ergänzt
|
||||
-- SCK 2026-04-10 Null-Prüfung und führender-Slash-Check ergänzt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
l_tenant_prefix varchar2(256);
|
||||
@@ -109,21 +109,21 @@ create or replace package body pck_net_storage as
|
||||
,i_content_type in varchar2 default null
|
||||
) return clob
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Führt einen HTTP-Request gegen die OCI Object Storage API aus.
|
||||
-- Wertet den HTTP-Statuscode aus und löst bei Fehler einen Application Error aus.
|
||||
-- Authentifizierung erfolgt über APEX Web Credential (NETSTORE_CRED_ID).
|
||||
-- Beschreibung: Führt einen HTTP-Request gegen die OCI Object Storage API aus.
|
||||
-- Wertet den HTTP-Statuscode aus und löst bei Fehler einen Application Error aus.
|
||||
-- Authentifizierung erfolgt über APEX Web Credential (NETSTORE_CRED_ID).
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_method HTTP-Methode (GET, PUT, DELETE, POST, HEAD)
|
||||
-- i_url Vollständige Ziel-URL
|
||||
-- i_url Vollständige Ziel-URL
|
||||
-- i_body_clob Optionaler Request-Body als CLOB (z.B. JSON)
|
||||
-- i_body_blob Optionaler Request-Body als BLOB (Binärinhalt)
|
||||
-- i_body_blob Optionaler Request-Body als BLOB (Binärinhalt)
|
||||
-- i_content_type Optionaler Content-Type Header
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Rückgabe: Response-Body als CLOB (bei HEAD-Requests leer)
|
||||
-- Rückgabe: Response-Body als CLOB (bei HEAD-Requests leer)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Funktion erstellt
|
||||
-- SCK 2026-04-16 empty_blob()/empty_clob() als Default entfernt — APEX OCI-Signing braucht null für nicht genutzte Body-Parameter
|
||||
-- SCK 2026-04-16 empty_blob()/empty_clob() als Default entfernt — APEX OCI-Signing braucht null für nicht genutzte Body-Parameter
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
l_response clob;
|
||||
@@ -131,7 +131,7 @@ create or replace package body pck_net_storage as
|
||||
l_header_index number := 1;
|
||||
l_content_length number;
|
||||
begin
|
||||
-- headers zurücksetzen - nur zur Sicherheit, damit keine alten Header übertragen werden.
|
||||
-- headers zurücksetzen - nur zur Sicherheit, damit keine alten Header übertragen werden.
|
||||
apex_web_service.g_request_headers.delete;
|
||||
|
||||
if i_content_type is not null
|
||||
@@ -161,9 +161,9 @@ create or replace package body pck_net_storage as
|
||||
l_header_index := l_header_index + 1;
|
||||
*/
|
||||
|
||||
-- nur für leere BLOBs (z.B. leerer Ordner) Content-Length setzen
|
||||
-- bei nicht leeren blob setzt apex_web_service.make_rest_request den content-length header automatisch, doppeltes setzen führt aber zu einem HTTP-400 API Fehler
|
||||
-- bei leeren blobs (empty_blob()) wird er aber nicht automatisch gesetzt, daher müssen wir ihn manuell setzen
|
||||
-- nur für leere BLOBs (z.B. leerer Ordner) Content-Length setzen
|
||||
-- bei nicht leeren blob setzt apex_web_service.make_rest_request den content-length header automatisch, doppeltes setzen führt aber zu einem HTTP-400 API Fehler
|
||||
-- bei leeren blobs (empty_blob()) wird er aber nicht automatisch gesetzt, daher müssen wir ihn manuell setzen
|
||||
if i_body_blob is not null
|
||||
and dbms_lob.getlength(i_body_blob) = 0
|
||||
then
|
||||
@@ -217,7 +217,7 @@ create or replace package body pck_net_storage as
|
||||
return l_response;
|
||||
end f_make_request;
|
||||
|
||||
-- Interne Implementierung ohne Rechteprüfung — wird von f_list_objects und p_delete_folder (Leerprüfung) genutzt
|
||||
-- Interne Implementierung ohne Rechteprüfung — wird von f_list_objects und p_delete_folder (Leerprüfung) genutzt
|
||||
function f_list_objects_internal (
|
||||
i_parent_folder in varchar2
|
||||
,i_include_subfolders in varchar2
|
||||
@@ -225,18 +225,18 @@ create or replace package body pck_net_storage as
|
||||
,i_limit in number
|
||||
) return t_net_storage_tab
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Listet Objekte und Unterordner im Bucket ohne Rechte- oder Scope-Prüfung.
|
||||
-- Paginiert automatisch über nextStartWith bis alle Ergebnisse geladen sind.
|
||||
-- Wird von f_list_objects (öffentlich) und p_delete_folder (Leerprüfung) intern genutzt.
|
||||
-- Beschreibung: Listet Objekte und Unterordner im Bucket ohne Rechte- oder Scope-Prüfung.
|
||||
-- Paginiert automatisch über nextStartWith bis alle Ergebnisse geladen sind.
|
||||
-- Wird von f_list_objects (öffentlich) und p_delete_folder (Leerprüfung) intern genutzt.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_parent_folder Ordnerpfad im Bucket (z.B. eingang/)
|
||||
-- i_include_subfolders 'Y' = alle Dateien rekursiv, 'N' = nur direkte Kinder des Ordners
|
||||
-- i_start_with Optionaler Startpunkt für Paginierung
|
||||
-- i_start_with Optionaler Startpunkt für Paginierung
|
||||
-- i_limit Maximale Anzahl Ergebnisse (0 = unbegrenzt)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Rückgabe: Collection t_net_storage_tab mit allen gefundenen Objekten
|
||||
-- Rückgabe: Collection t_net_storage_tab mit allen gefundenen Objekten
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Funktion erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -288,7 +288,7 @@ create or replace package body pck_net_storage as
|
||||
,l_obj_path.path
|
||||
,l_obj_path.filename
|
||||
-- Explizit angelegte Ordner sind Zero-Byte-Objekte mit trailing /;
|
||||
-- size, last_modified und etag sind für Ordner nicht relevant
|
||||
-- size, last_modified und etag sind für Ordner nicht relevant
|
||||
,(case when rec.object_name like '%/' then null else rec.object_size end)
|
||||
,(case when rec.object_name like '%/' then null else to_date(substr(rec.last_modified, 1, 19), 'YYYY-MM-DD"T"HH24:MI:SS') end)
|
||||
,(case when rec.object_name like '%/' then 'Y' else 'N' end)
|
||||
@@ -335,7 +335,7 @@ create or replace package body pck_net_storage as
|
||||
end loop;
|
||||
end if;
|
||||
|
||||
-- Nächste Seite prüfen
|
||||
-- Nächste Seite prüfen
|
||||
if not l_done
|
||||
then
|
||||
l_next_start := json_value(l_response, '$.nextStartWith');
|
||||
@@ -351,9 +351,9 @@ create or replace package body pck_net_storage as
|
||||
|
||||
-- Implizite Ordner aus Object-Keys ableiten.
|
||||
-- Die OCI-API liefert virtuelle Ordner (nie als Zero-Byte-Objekt angelegt) nur
|
||||
-- über $.prefixes, und auch nur wenn delimiter gesetzt ist. Bei rekursivem Abruf
|
||||
-- über $.prefixes, und auch nur wenn delimiter gesetzt ist. Bei rekursivem Abruf
|
||||
-- fehlen sie daher komplett. Wir leiten alle Zwischenpfade aus den Object-Keys ab
|
||||
-- und ergänzen fehlende Ordner-Einträge.
|
||||
-- und ergänzen fehlende Ordner-Einträge.
|
||||
declare
|
||||
l_new_folders apex_t_varchar2;
|
||||
begin
|
||||
@@ -364,16 +364,16 @@ create or replace package body pck_net_storage as
|
||||
--
|
||||
-- connect by level iteriert von 1 bis zur Anzahl der Slashes im Key.
|
||||
-- instr(..., '/', 1, level) liefert die Position des n-ten Slashes.
|
||||
-- substr(..., 1, <position>) schneidet den Key bis einschließlich
|
||||
-- dieses Slashes ab — das Ergebnis ist der Ordnerpfad auf Ebene n.
|
||||
-- substr(..., 1, <position>) schneidet den Key bis einschließlich
|
||||
-- dieses Slashes ab — das Ergebnis ist der Ordnerpfad auf Ebene n.
|
||||
--
|
||||
-- Beispiel für 'mandant/Eingang/batch-001/datei.pdf' (3 Slashes):
|
||||
-- level 1 → 'mandant/'
|
||||
-- level 2 → 'mandant/Eingang/'
|
||||
-- level 3 → 'mandant/Eingang/batch-001/'
|
||||
-- Beispiel für 'mandant/Eingang/batch-001/datei.pdf' (3 Slashes):
|
||||
-- level 1 -> 'mandant/'
|
||||
-- level 2 -> 'mandant/Eingang/'
|
||||
-- level 3 -> 'mandant/Eingang/batch-001/'
|
||||
--
|
||||
-- prior object_key = object_key : bindet jede Zeile an sich selbst,
|
||||
-- damit connect by die Levels pro Zeile unabhängig hochzählt.
|
||||
-- damit connect by die Levels pro Zeile unabhängig hochzählt.
|
||||
-- prior sys_guid() is not null : verhindert Cycle-Detection-Fehler,
|
||||
-- da keine echte Eltern-Kind-Beziehung vorliegt.
|
||||
select substr(r.object_key, 1, instr(r.object_key, '/', 1, level)) as folder_path
|
||||
@@ -384,14 +384,14 @@ create or replace package body pck_net_storage as
|
||||
and prior sys_guid() is not null
|
||||
)
|
||||
-- Nur Pfade unterhalb des Parent-Folders behalten:
|
||||
-- like-Bedingung schließt Vorfahren-Pfade aus (z.B. 'mandant/', 'mandant/Eingang/'
|
||||
-- like-Bedingung schließt Vorfahren-Pfade aus (z.B. 'mandant/', 'mandant/Eingang/'
|
||||
-- wenn der Parent-Folder 'mandant/Eingang/batch/' ist).
|
||||
-- != schließt den Parent-Folder selbst aus.
|
||||
-- != schließt den Parent-Folder selbst aus.
|
||||
-- Bei null-Parent-Folder (Bucket-Root): like '%' = immer wahr, chr(0) passt
|
||||
-- auf keinen gültigen Key → beide Bedingungen greifen nicht.
|
||||
-- auf keinen gültigen Key -> beide Bedingungen greifen nicht.
|
||||
where folder_path like nvl(l_parent_folder, '') || '%'
|
||||
and folder_path != nvl(l_parent_folder, chr(0))
|
||||
-- Bereits vorhandene Ordner-Einträge ausschließen (explizit angelegte
|
||||
-- Bereits vorhandene Ordner-Einträge ausschließen (explizit angelegte
|
||||
-- Zero-Byte-Objekte oder via $.prefixes gelieferte virtuelle Ordner).
|
||||
minus
|
||||
select object_key
|
||||
@@ -417,18 +417,18 @@ create or replace package body pck_net_storage as
|
||||
return l_result;
|
||||
end f_list_objects_internal;
|
||||
|
||||
-- ==================== Öffentliche Funktionen ====================
|
||||
-- ==================== Öffentliche Funktionen ====================
|
||||
|
||||
function f_split_object_key (i_object_key in varchar2) return t_object_path
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Extrahiert Pfad und Dateiname aus einem OCI-Objektschlüssel.
|
||||
-- Bei Ordner-Keys (trailing Slash) wird der Ordnername als Dateiname zurückgegeben.
|
||||
-- Beschreibung: Extrahiert Pfad und Dateiname aus einem OCI-Objektschlüssel.
|
||||
-- Bei Ordner-Keys (trailing Slash) wird der Ordnername als Dateiname zurückgegeben.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel (z.B. mandant/Eingang/Import/datei.pdf)
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel (z.B. mandant/Eingang/Import/datei.pdf)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Rückgabe: t_object_path Record mit path (inkl. trailing Slash) und filename
|
||||
-- Rückgabe: t_object_path Record mit path (inkl. trailing Slash) und filename
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-09 Funktion erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -460,16 +460,16 @@ create or replace package body pck_net_storage as
|
||||
,i_limit in number default 0
|
||||
) return t_net_storage_tab
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Listet Objekte und Unterordner im Bucket mit Rechteprüfung und Scope-Validierung.
|
||||
-- Beschreibung: Listet Objekte und Unterordner im Bucket mit Rechteprüfung und Scope-Validierung.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_parent_folder Ordnerpfad im Bucket (z.B. eingang/)
|
||||
-- i_include_subfolders 'Y' = alle Dateien rekursiv inkl. Unterordner, 'N' = nur direkte Dateien im Ordner (Standard)
|
||||
-- i_start_with Optionaler Startpunkt für Paginierung
|
||||
-- i_start_with Optionaler Startpunkt für Paginierung
|
||||
-- i_limit Maximale Anzahl Ergebnisse (0 = unbegrenzt)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Rückgabe: Collection t_net_storage_tab mit allen gefundenen Objekten
|
||||
-- Rückgabe: Collection t_net_storage_tab mit allen gefundenen Objekten
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Funktion erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -485,13 +485,13 @@ create or replace package body pck_net_storage as
|
||||
|
||||
function f_download_object (i_object_key in varchar2) return blob
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Lädt ein einzelnes Objekt aus dem OCI Bucket als BLOB herunter.
|
||||
-- Beschreibung: Lädt ein einzelnes Objekt aus dem OCI Bucket als BLOB herunter.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Rückgabe: Dateiinhalt als BLOB
|
||||
-- Rückgabe: Dateiinhalt als BLOB
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Funktion erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -501,12 +501,15 @@ create or replace package body pck_net_storage as
|
||||
pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES');
|
||||
p_assert_allowed(i_object_key);
|
||||
|
||||
-- Wir nutzen hier direkt apex_web_service.make_rest_request_b, statt der internen f_make_request funktion, da wir nur hier einen blob statt clob return wert brauchen und eine extra
|
||||
l_response := apex_web_service.make_rest_request_b(
|
||||
p_url => f_build_url(i_object_key)
|
||||
,p_http_method => 'GET'
|
||||
,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NETSTORE_CRED_ID')
|
||||
,p_wallet_path => pck_system.f_get_par_wert_by_programmid('NETSTORE_WALLET_PATH')
|
||||
);
|
||||
|
||||
|
||||
l_status := apex_web_service.g_status_code;
|
||||
|
||||
if l_status = 404
|
||||
@@ -529,13 +532,13 @@ create or replace package body pck_net_storage as
|
||||
,i_content_type in varchar2
|
||||
)
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Lädt ein Objekt in den OCI Bucket hoch (PUT). Überschreibt vorhandene Objekte.
|
||||
-- Beschreibung: Lädt ein Objekt in den OCI Bucket hoch (PUT). Überschreibt vorhandene Objekte.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Zielpfad im Bucket
|
||||
-- i_content Dateiinhalt als BLOB
|
||||
-- i_content_type MIME-Type des Inhalts (z.B. application/octet-stream)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -547,7 +550,7 @@ create or replace package body pck_net_storage as
|
||||
|
||||
if substr(i_object_key, -1) = '/'
|
||||
then
|
||||
raise_application_error(-20012, 'Object Key darf nicht mit / enden — zum Anlegen von Ordnern p_create_folder verwenden');
|
||||
raise_application_error(-20012, 'Object Key darf nicht mit / enden — zum Anlegen von Ordnern p_create_folder verwenden');
|
||||
end if;
|
||||
|
||||
l_response := f_make_request(
|
||||
@@ -568,11 +571,11 @@ create or replace package body pck_net_storage as
|
||||
|
||||
procedure p_delete_object (i_object_key in varchar2)
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Löscht ein einzelnes Objekt aus dem OCI Bucket (DELETE).
|
||||
-- Beschreibung: Löscht ein einzelnes Objekt aus dem OCI Bucket (DELETE).
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -591,21 +594,21 @@ create or replace package body pck_net_storage as
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => 'DELETE'
|
||||
,i_message => 'Datei "' || l_obj_path.filename || '" gelöscht | Ordner: ' || l_obj_path.path
|
||||
,i_message => 'Datei "' || l_obj_path.filename || '" gelöscht | Ordner: ' || l_obj_path.path
|
||||
,i_object_ref => i_object_key
|
||||
);
|
||||
end p_delete_object;
|
||||
|
||||
procedure p_delete_folder (i_folder_key in varchar2)
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Löscht einen leeren Ordner im OCI Bucket.
|
||||
-- Schlägt fehl, wenn noch Objekte oder Unterordner vorhanden sind.
|
||||
-- Beschreibung: Löscht einen leeren Ordner im OCI Bucket.
|
||||
-- Schlägt fehl, wenn noch Objekte oder Unterordner vorhanden sind.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_folder_key Kompletter Ordner name inkl. Pfad (z.B. eingang/batch-001/)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
-- SCK 2026-04-10 Rekursives Löschen entfernt — Ordner muss leer sein
|
||||
-- SCK 2026-04-10 Rekursives Löschen entfernt — Ordner muss leer sein
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
l_objects t_net_storage_tab;
|
||||
@@ -617,7 +620,7 @@ create or replace package body pck_net_storage as
|
||||
pck_mitarbeiterrecht.p_hat_recht('ADMIN');
|
||||
p_assert_allowed(l_prefix);
|
||||
|
||||
-- Direkte Kinder prüfen (Dateien und Unterordner)
|
||||
-- Direkte Kinder prüfen (Dateien und Unterordner)
|
||||
l_objects := f_list_objects_internal(
|
||||
i_parent_folder => l_prefix
|
||||
,i_include_subfolders => 'N'
|
||||
@@ -626,14 +629,14 @@ create or replace package body pck_net_storage as
|
||||
);
|
||||
|
||||
/*
|
||||
apex_debug.info('p_delete_folder: prefix=%s, Anzahl gefundene Einträge=%s', l_prefix, l_objects.count);
|
||||
apex_debug.info('p_delete_folder: prefix=%s, Anzahl gefundene Einträge=%s', l_prefix, l_objects.count);
|
||||
for i in 1 .. l_objects.count
|
||||
loop
|
||||
apex_debug.info(' [%s] key=%s | is_folder=%s', i, l_objects(i).object_key, l_objects(i).is_folder);
|
||||
end loop;
|
||||
*/
|
||||
|
||||
-- Den Ordner selbst (object_key = l_prefix) aus der Zählung ausschließen
|
||||
-- Den Ordner selbst (object_key = l_prefix) aus der Zählung ausschließen
|
||||
select count(*)
|
||||
into l_count
|
||||
from table(l_objects)
|
||||
@@ -641,10 +644,10 @@ create or replace package body pck_net_storage as
|
||||
|
||||
if l_count > 0
|
||||
then
|
||||
raise_application_error(-20017, 'Ordner ist nicht leer und kann nicht gelöscht werden');
|
||||
raise_application_error(-20017, 'Ordner ist nicht leer und kann nicht gelöscht werden');
|
||||
end if;
|
||||
|
||||
-- Ordner-Objekt selbst löschen
|
||||
-- Ordner-Objekt selbst löschen
|
||||
l_response := f_make_request(
|
||||
i_method => 'DELETE'
|
||||
,i_url => f_build_url(l_prefix)
|
||||
@@ -654,7 +657,7 @@ create or replace package body pck_net_storage as
|
||||
pck_log.p_info(
|
||||
i_module => c_log_module
|
||||
,i_action => 'DELETE_FOLDER'
|
||||
,i_message => 'Ordner "' || l_obj_path.filename || '" gelöscht | Pfad: ' || l_obj_path.path
|
||||
,i_message => 'Ordner "' || l_obj_path.filename || '" gelöscht | Pfad: ' || l_obj_path.path
|
||||
,i_object_ref => l_prefix
|
||||
);
|
||||
end p_delete_folder;
|
||||
@@ -667,10 +670,10 @@ create or replace package body pck_net_storage as
|
||||
-- Beschreibung: Benennt ein Objekt innerhalb desselben Verzeichnisses um.
|
||||
-- Verwendet die OCI renameObject-Action (kein physisches Kopieren).
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel des Quelldatei
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel des Quelldatei
|
||||
-- i_new_name Neuer Dateiname (ohne Pfad)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -689,7 +692,7 @@ create or replace package body pck_net_storage as
|
||||
|
||||
if instr(i_new_name, '/') > 0
|
||||
then
|
||||
raise_application_error(-20014, 'Dateiname darf keinen Schrägstrich enthalten — zum Verschieben explizite Verschieben-Funktion verwenden');
|
||||
raise_application_error(-20014, 'Dateiname darf keinen Schrägstrich enthalten — zum Verschieben explizite Verschieben-Funktion verwenden');
|
||||
end if;
|
||||
|
||||
l_obj_path := f_split_object_key(i_object_key);
|
||||
@@ -698,7 +701,7 @@ create or replace package body pck_net_storage as
|
||||
|
||||
if l_new_key = i_object_key
|
||||
then
|
||||
raise_application_error(-20016, 'Der Dateiname darf beim Umbenennen nicht unverändert bleiben.');
|
||||
raise_application_error(-20016, 'Der Dateiname darf beim Umbenennen nicht unverändert bleiben.');
|
||||
end if;
|
||||
|
||||
select json_object(
|
||||
@@ -730,12 +733,12 @@ create or replace package body pck_net_storage as
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Verschiebt ein Objekt in einen anderen Ordner im selben Bucket.
|
||||
-- Verwendet die OCI renameObject-Action (kein physisches Kopieren).
|
||||
-- Der Dateiname bleibt erhalten; nur der Pfad ändert sich.
|
||||
-- Der Dateiname bleibt erhalten; nur der Pfad ändert sich.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel der Quelldatei
|
||||
-- i_target_prefix Zielpräfix inkl. trailing Slash (z.B. verarbeitet/batch-001/)
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel der Quelldatei
|
||||
-- i_target_prefix Zielpräfix inkl. trailing Slash (z.B. verarbeitet/batch-001/)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -751,7 +754,7 @@ create or replace package body pck_net_storage as
|
||||
|
||||
if i_target_prefix is null
|
||||
then
|
||||
raise_application_error(-20015, 'Zielpräfix darf nicht null sein');
|
||||
raise_application_error(-20015, 'Zielpräfix darf nicht null sein');
|
||||
end if;
|
||||
|
||||
l_target_prefix := f_normalize_prefix(i_target_prefix);
|
||||
@@ -802,10 +805,10 @@ create or replace package body pck_net_storage as
|
||||
-- Beschreibung: Legt einen neuen Ordner im OCI Bucket an.
|
||||
-- Ordner werden als leeres Objekt mit trailing Slash simuliert.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_parent_folder Übergeordneter Pfad inkl. trailing Slash (z.B. eingang/)
|
||||
-- Parameter: i_parent_folder Übergeordneter Pfad inkl. trailing Slash (z.B. eingang/)
|
||||
-- i_folder_name Name des neuen Ordners (ohne Slash)
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Prozedur erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
@@ -823,7 +826,7 @@ create or replace package body pck_net_storage as
|
||||
|
||||
if instr(i_folder_name, '/') > 0
|
||||
then
|
||||
raise_application_error(-20011, 'Ordnername darf keinen Schrägstrich enthalten');
|
||||
raise_application_error(-20011, 'Ordnername darf keinen Schrägstrich enthalten');
|
||||
end if;
|
||||
|
||||
if l_prefix is not null
|
||||
@@ -854,13 +857,13 @@ create or replace package body pck_net_storage as
|
||||
function f_get_object_metadata (i_object_key in varchar2) return t_object_meta
|
||||
/*Kopf------------------------------------------------------------------------------------------------
|
||||
-- Beschreibung: Ruft die Metadaten eines Objekts per HEAD-Request ab (kein Download des Inhalts).
|
||||
-- Liest Größe, Content-Type, Last-Modified und ETag aus den Response-Headern.
|
||||
-- Liest Größe, Content-Type, Last-Modified und ETag aus den Response-Headern.
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket
|
||||
-- Parameter: i_object_key Vollständiger Objektschlüssel im Bucket
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- Rückgabe: t_object_meta Record mit object_name, object_size, last_modified, content_type, etag
|
||||
-- Rückgabe: t_object_meta Record mit object_name, object_size, last_modified, content_type, etag
|
||||
------------------------------------------------------------------------------------------------------
|
||||
-- MA Datum Änderung
|
||||
-- MA Datum Änderung
|
||||
-- SCK 2026-04-08 Funktion erstellt
|
||||
------------------------------------------------------------------------------------------------Kopf*/
|
||||
is
|
||||
|
||||
@@ -5,10 +5,10 @@ from lg_app_log
|
||||
--truncate table lg_app_log;
|
||||
|
||||
/*
|
||||
Test Calls für logs
|
||||
Test Calls für logs
|
||||
*/
|
||||
begin
|
||||
-- Neuen Batch-Ordner für heutigen Import anlegen
|
||||
-- Neuen Batch-Ordner für heutigen Import anlegen
|
||||
pck_net_storage.p_create_folder(
|
||||
i_prefix => 'testmandant-42/Eingang/Verarbeitet 2026/'
|
||||
,i_folder_name => 'Batch-2026-04-09'
|
||||
@@ -41,7 +41,7 @@ begin
|
||||
,i_new_name => 'angebot_huber_2026-04.pdf'
|
||||
);
|
||||
|
||||
-- scan_003 ist ein Duplikat, wird gelöscht
|
||||
-- scan_003 ist ein Duplikat, wird gelöscht
|
||||
pck_net_storage.p_delete_object(
|
||||
i_object_key => 'testmandant-42/Eingang/Import/scan_003.pdf'
|
||||
);
|
||||
|
||||
34
database/tests/certificate-tests.sql
Normal file
34
database/tests/certificate-tests.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
BEGIN
|
||||
null;
|
||||
--pck_auto_import.p_run_ba_korrespondenz_dateieingang_automation;
|
||||
/*
|
||||
if pck_system.f_get_par_wert_by_programmid('AUTOMATON_API_KEY') = 'sRYh5-)j+~VY7x9A4Q6#Sz8qu7_osjTHlw94KSüJWPpsTäkrwWl5'
|
||||
then dbms_output.put_line('correct');
|
||||
else dbms_output.put_line('error');
|
||||
end if;
|
||||
*/
|
||||
END;
|
||||
/
|
||||
|
||||
select *
|
||||
from LG_APP_LOG
|
||||
order by log_id desc
|
||||
;
|
||||
|
||||
|
||||
DECLARE
|
||||
l_response CLOB;
|
||||
l_url VARCHAR2(500) := 'https://test.grafana.inkasso.ewgala.galabau.de';
|
||||
BEGIN
|
||||
-- Einfacher GET-Request
|
||||
l_response := APEX_WEB_SERVICE.MAKE_REST_REQUEST(
|
||||
p_url => l_url
|
||||
,p_http_method => 'GET'
|
||||
,p_wallet_path => 'file:/u01/app/oracle/product/19.0.0.0/dbhome_1/wallets/combined_wallet'
|
||||
);
|
||||
|
||||
--DBMS_OUTPUT.PUT_LINE('Response:');
|
||||
--DBMS_OUTPUT.PUT_LINE(l_response);
|
||||
END;
|
||||
/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-- Schema-Level Type für f_list_objects Cursor-Rückgabe.
|
||||
-- Wird benötigt da Oracle TABLE() in SQL nur schema-level Types unterstützt.
|
||||
-- Schema-Level Type für f_list_objects Cursor-Rückgabe.
|
||||
-- Wird benötigt da Oracle TABLE() in SQL nur schema-level Types unterstützt.
|
||||
create or replace type t_net_storage_row as object (
|
||||
object_key varchar2(1024)
|
||||
,object_path varchar2(1024)
|
||||
|
||||
@@ -30,16 +30,18 @@ FileProcessingPipeline [ManagedExecutor — Hintergrund-Thread]
|
||||
├─→ OciUploadService.upload() [OCI SDK]
|
||||
│ └─ Dateien in eingang/<zip-name>/ + Marker
|
||||
│
|
||||
├─→ SftpService.renameRemote() [SSHJ]
|
||||
│ └─ .processed (Erfolg) oder .error (Fehler)
|
||||
│
|
||||
├─→ OrdsNotificationService.notify() [MicroProfile REST Client]
|
||||
│ └─ POST pck_auto_import.p_process_incoming_ba_data
|
||||
├─→ SftpService.deleteRemote() [SSHJ]
|
||||
│ └─ ZIP gelöscht (Erfolg) oder .error (Fehler)
|
||||
│
|
||||
└─→ Cleanup: lokale Dateien löschen [immer, im finally]
|
||||
│
|
||||
│ nach allen ZIPs (einmalig):
|
||||
│
|
||||
└─→ OrdsNotificationService.notify() [MicroProfile REST Client]
|
||||
└─ POST pck_auto_import.p_process_incoming_ba_data
|
||||
│
|
||||
▼
|
||||
Oracle DB (pck_auto_import verarbeitet eingang/<zip-name>/)
|
||||
Oracle DB (pck_auto_import verarbeitet alle eingang/-Unterordner)
|
||||
```
|
||||
|
||||
## Pipeline-Steps
|
||||
|
||||
@@ -16,16 +16,22 @@ public interface OciConfig {
|
||||
String bucket();
|
||||
|
||||
/**
|
||||
* Root-Prefix für alle Objekte im Bucket, z.B. {@code mandant_42/}.
|
||||
* Root-Prefix für alle Objekte im Bucket, z.B. {@code testmandant-42/}.
|
||||
* Muss mit {@code /} enden.
|
||||
*/
|
||||
String tenantPrefix();
|
||||
|
||||
/**
|
||||
* Prefix für eingehende Dateien unterhalb von {@code tenantPrefix},
|
||||
* z.B. {@code eingang/}. Muss mit {@code /} enden.
|
||||
* Gemeinsamer Basis-Prefix für alle BA-Eingangs-Pfade unterhalb von {@code tenantPrefix},
|
||||
* z.B. {@code BA/Eingang/}. Muss mit {@code /} enden.
|
||||
*/
|
||||
String incomingKorrespondenzenPrefix();
|
||||
String baBasePrefix();
|
||||
|
||||
/** Konfiguration für die BA-Korrespondenzen-Pipeline. */
|
||||
Korrespondenzen korrespondenzen();
|
||||
|
||||
/** Konfiguration für die BA-Aufrechnungen-Pipeline. */
|
||||
Aufrechnungen aufrechnungen();
|
||||
|
||||
/** OCI Tenancy OCID. Aus Env-Var {@code OCI_TENANCY_ID}. */
|
||||
String tenancyId();
|
||||
@@ -49,4 +55,32 @@ public interface OciConfig {
|
||||
* Muss mit der APEX Automation und dem ORDS-Package abgestimmt sein.
|
||||
*/
|
||||
String markerFilenameDbProcessing();
|
||||
|
||||
interface Korrespondenzen {
|
||||
|
||||
/**
|
||||
* Prefix für eingehende Korrespondenz-Dateien relativ zu {@code baBasePrefix},
|
||||
* z.B. {@code Import/BA-Korrespondenzen/}. Muss mit {@code /} enden.
|
||||
* Vollständiger Pfad: {@code tenantPrefix + baBasePrefix + incomingPrefix}.
|
||||
*/
|
||||
String incomingPrefix();
|
||||
|
||||
/**
|
||||
* Prefix für archivierte ZIP-Originaldateien relativ zu {@code baBasePrefix},
|
||||
* z.B. {@code BA-Korrespondenzen ZIP-Dateien}. Kein abschließendes {@code /} —
|
||||
* das aktuelle Jahr wird zur Laufzeit angehängt: {@code <prefix> <yyyy>/}.
|
||||
* Vollständiger Pfad: {@code tenantPrefix + baBasePrefix + archivePrefix + " 2026/"}.
|
||||
*/
|
||||
String archivePrefix();
|
||||
}
|
||||
|
||||
interface Aufrechnungen {
|
||||
|
||||
/**
|
||||
* Prefix für eingehende Aufrechnungs-Dateien relativ zu {@code baBasePrefix},
|
||||
* z.B. {@code Import/Aufrechnungen/}. Muss mit {@code /} enden.
|
||||
* Vollständiger Pfad: {@code tenantPrefix + baBasePrefix + incomingPrefix}.
|
||||
*/
|
||||
String incomingPrefix();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import java.util.UUID;
|
||||
*/
|
||||
public class ProcessingContext {
|
||||
|
||||
/** Eindeutige Lauf-ID — wird als MDC-Feld {@code runId} gesetzt. */
|
||||
public final UUID runId;
|
||||
/** Eindeutige Datei-ID — wird als MDC-Feld {@code fileId} gesetzt. */
|
||||
public final UUID fileId;
|
||||
|
||||
/** Originaler ZIP-Dateiname auf dem SFTP-Server, z.B. {@code export_2026-04-08.zip}. */
|
||||
public final String zipFilename;
|
||||
@@ -39,8 +39,8 @@ public class ProcessingContext {
|
||||
/** Aktueller Verarbeitungsstatus. */
|
||||
public ProcessingStatus status = ProcessingStatus.PENDING;
|
||||
|
||||
public ProcessingContext(UUID runId, String zipFilename) {
|
||||
this.runId = runId;
|
||||
public ProcessingContext(UUID fileId, String zipFilename) {
|
||||
this.fileId = fileId;
|
||||
this.zipFilename = zipFilename;
|
||||
this.zipNameWithoutExt = zipFilename.endsWith(".zip")
|
||||
? zipFilename.substring(0, zipFilename.length() - 4)
|
||||
|
||||
@@ -5,6 +5,7 @@ public enum ProcessingStatus {
|
||||
PENDING,
|
||||
PARTIALLY_UPLOADED,
|
||||
MARKER_UPLOADED,
|
||||
// TODO: ORDS_NOTIFIED wird seit dem Refactoring (ORDS-Aufruf einmalig am Ende der Pipeline, nicht mehr pro ZIP) nicht mehr gesetzt — entfernen
|
||||
ORDS_NOTIFIED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Year;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -79,38 +80,56 @@ public class OciUploadService {
|
||||
Log.infof("OCI-Upload: %d Datei(en) für '%s'", files.size(), context.zipNameWithoutExt);
|
||||
|
||||
for (FileEntry entry : files) {
|
||||
String key = buildKey(context.zipNameWithoutExt, entry.relativePath);
|
||||
String key = buildKorrespondenzenKey(context.zipNameWithoutExt, entry.relativePath);
|
||||
entry.ociKey = key;
|
||||
putFile(key, context.localExtractDir.resolve(entry.relativePath), entry.fileSize);
|
||||
Log.infof("Datei hochgeladen: %s (%d Bytes)", key, entry.fileSize);
|
||||
}
|
||||
|
||||
Log.infof("OCI-Upload Dateien abgeschlossen: %d Datei(en) in '%s'",
|
||||
files.size(), buildPrefix(context.zipNameWithoutExt));
|
||||
files.size(), buildKorrespondenzenPrefix(context.zipNameWithoutExt));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Marker in OCI — signalisiert der DB-Verarbeitung, dass der Batch vollständig ist.
|
||||
* Wird erst nach dem SFTP-Rename zu {@code .processed} aufgerufen, damit Marker und
|
||||
* SFTP-Zustand immer konsistent sind: Marker vorhanden ↔ ZIP bereits als verarbeitet markiert.
|
||||
* Wird erst nach dem SFTP-Delete aufgerufen, damit Marker und
|
||||
* SFTP-Zustand immer konsistent sind: Marker vorhanden ↔ ZIP bereits vom SFTP gelöscht.
|
||||
*
|
||||
* @param context enthält den Ziel-Prefix für den Marker-Key
|
||||
* @throws OciException bei Verbindungs- oder Upload-Fehlern
|
||||
*/
|
||||
public void uploadMarker(ProcessingContext context) throws OciException {
|
||||
String markerKey = buildKey(context.zipNameWithoutExt, config.markerFilenameDbProcessing());
|
||||
String markerKey = buildKorrespondenzenKey(context.zipNameWithoutExt, config.markerFilenameDbProcessing());
|
||||
Log.infof("Lade Marker hoch: '%s'", markerKey);
|
||||
putMarker(markerKey);
|
||||
context.markerUploaded = true;
|
||||
Log.infof("Marker hochgeladen: '%s'", markerKey);
|
||||
}
|
||||
|
||||
private String buildPrefix(String zipNameWithoutExt) {
|
||||
return config.tenantPrefix() + config.incomingKorrespondenzenPrefix() + zipNameWithoutExt + "/";
|
||||
/**
|
||||
* Lädt die Original-ZIP-Datei in den Archivordner in OCI hoch.
|
||||
* Ziel-Key: {@code tenantPrefix + baBasePrefix + archivePrefix + " <Jahr>/" + zipFilename}
|
||||
*
|
||||
* @param context enthält den lokalen ZIP-Pfad und den Dateinamen
|
||||
* @throws OciException bei Verbindungs- oder Upload-Fehlern
|
||||
* @throws IOException bei Problemen beim Lesen der lokalen ZIP-Datei
|
||||
*/
|
||||
public void uploadZipFile(ProcessingContext context) throws OciException, IOException {
|
||||
String yearFolder = config.korrespondenzen().archivePrefix() + " " + Year.now().getValue() + "/";
|
||||
String key = config.tenantPrefix() + config.baBasePrefix() + yearFolder + context.zipFilename;
|
||||
long fileSize = Files.size(context.localZipPath);
|
||||
Log.infof("Lade ZIP-Archiv hoch: '%s' (%d Bytes)", key, fileSize);
|
||||
putFile(key, context.localZipPath, fileSize);
|
||||
Log.infof("ZIP-Archiv hochgeladen: '%s'", key);
|
||||
}
|
||||
|
||||
private String buildKey(String zipNameWithoutExt, String relativePath) {
|
||||
return buildPrefix(zipNameWithoutExt) + relativePath;
|
||||
private String buildKorrespondenzenPrefix(String zipNameWithoutExt) {
|
||||
return config.tenantPrefix() + config.baBasePrefix()
|
||||
+ config.korrespondenzen().incomingPrefix() + zipNameWithoutExt + "/";
|
||||
}
|
||||
|
||||
private String buildKorrespondenzenKey(String zipNameWithoutExt, String relativePath) {
|
||||
return buildKorrespondenzenPrefix(zipNameWithoutExt) + relativePath;
|
||||
}
|
||||
|
||||
private void putFile(String key, Path localFile, long fileSize) throws OciException {
|
||||
|
||||
@@ -2,7 +2,6 @@ package de.galabau.dateieingang.ords;
|
||||
|
||||
import de.galabau.dateieingang.config.OrdsConfig;
|
||||
import de.galabau.dateieingang.exception.OrdsException;
|
||||
import de.galabau.dateieingang.model.ProcessingContext;
|
||||
import io.quarkus.logging.Log;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -35,28 +34,25 @@ public class OrdsNotificationService {
|
||||
* Wird bei transienten Fehlern bis zu 3-mal wiederholt (1s Backoff, 10s Timeout je Versuch).
|
||||
* Maximale Wartezeit: ca. 33 Sekunden (3 × 10s + 3 × 1s Backoff).
|
||||
*
|
||||
* @param context enthält {@code zipNameWithoutExt} für das Log
|
||||
* @throws OrdsException wenn der ORDS-Aufruf nach allen Retries fehlschlägt
|
||||
*/
|
||||
@Retry(maxRetries = 3, delay = 1000, delayUnit = ChronoUnit.MILLIS,
|
||||
retryOn = OrdsException.class)
|
||||
@Timeout(value = 10, unit = ChronoUnit.SECONDS)
|
||||
public void triggerDbProcessing(ProcessingContext context) throws OrdsException {
|
||||
Log.infof("Rufe ORDS-Endpunkt auf für '%s'", context.zipNameWithoutExt);
|
||||
public void triggerDbProcessing() throws OrdsException {
|
||||
Log.info("Rufe ORDS-Endpunkt auf");
|
||||
Response response;
|
||||
try {
|
||||
response = ordsClient.processIncomingBaData(config.apiKey());
|
||||
} catch (Exception e) {
|
||||
throw new OrdsException("ORDS-Verbindung fehlgeschlagen für '"
|
||||
+ context.zipNameWithoutExt + "'", e);
|
||||
throw new OrdsException("ORDS-Verbindung fehlgeschlagen", e);
|
||||
}
|
||||
|
||||
int status = response.getStatus();
|
||||
if (status >= 400) {
|
||||
throw new OrdsException("ORDS antwortete mit HTTP " + status
|
||||
+ " für '" + context.zipNameWithoutExt + "'");
|
||||
throw new OrdsException("ORDS antwortete mit HTTP " + status);
|
||||
}
|
||||
|
||||
Log.infof("ORDS-Endpunkt aufgerufen, HTTP %d für '%s'", status, context.zipNameWithoutExt);
|
||||
Log.infof("ORDS-Endpunkt aufgerufen, HTTP %d", status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +79,11 @@ public class FileProcessingPipeline {
|
||||
}
|
||||
|
||||
private void processAll() {
|
||||
Log.info("Pipeline-Lauf gestartet");
|
||||
UUID pipelineRunId = UUID.randomUUID();
|
||||
MDC.put("pipelineRunId", pipelineRunId.toString());
|
||||
Log.infof("Pipeline-Lauf gestartet [pipelineRunId=%s]", pipelineRunId);
|
||||
|
||||
try {
|
||||
preProcessingCleanup();
|
||||
|
||||
List<String> zipFiles;
|
||||
@@ -93,24 +96,32 @@ public class FileProcessingPipeline {
|
||||
|
||||
if (zipFiles.isEmpty()) {
|
||||
Log.info("Keine neuen ZIP-Dateien auf dem SFTP-Server gefunden");
|
||||
Log.info("Pipeline-Lauf abgeschlossen");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.infof("%d neue ZIP-Datei(en) auf dem SFTP-Server gefunden", zipFiles.size());
|
||||
|
||||
for (String zipFilename : zipFiles) {
|
||||
Log.infof("Datei gefunden: %s", zipFilename);
|
||||
Log.infof("ZIP-Datei gefunden: %s", zipFilename);
|
||||
processZip(zipFilename);
|
||||
}
|
||||
|
||||
Log.info("Pipeline-Lauf abgeschlossen");
|
||||
MDC.put("step", "ords-notify");
|
||||
try {
|
||||
ordsNotificationService.triggerDbProcessing();
|
||||
} catch (OrdsException e) {
|
||||
Log.errorf(e, "ORDS-Benachrichtigung fehlgeschlagen — DB-Verarbeitung wird beim nächsten Lauf ausgelöst");
|
||||
}
|
||||
} finally {
|
||||
Log.infof("Pipeline-Lauf abgeschlossen [pipelineRunId=%s]", pipelineRunId);
|
||||
MDC.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void processZip(String zipFilename) {
|
||||
ProcessingContext context = new ProcessingContext(UUID.randomUUID(), zipFilename);
|
||||
MDC.put("runId", context.runId.toString());
|
||||
Log.infof("Starte Verarbeitung von '%s' [runId=%s]", zipFilename, context.runId);
|
||||
MDC.put("fileId", context.fileId.toString());
|
||||
Log.infof("Starte Verarbeitung von '%s' [fileId=%s]", zipFilename, context.fileId);
|
||||
|
||||
try {
|
||||
// --- Download ---
|
||||
@@ -119,6 +130,11 @@ public class FileProcessingPipeline {
|
||||
Log.infof("ZIP '%s' heruntergeladen (%d Bytes)", zipFilename,
|
||||
Files.size(context.localZipPath));
|
||||
|
||||
// --- OCI ZIP-Archiv ---
|
||||
MDC.put("step", "oci-zip-archive");
|
||||
Log.info("Starte ZIP-Upload in OCI");
|
||||
ociUploadService.uploadZipFile(context);
|
||||
|
||||
// --- Entpacken ---
|
||||
MDC.put("step", "zip-extract");
|
||||
zipExtractionService.extract(context);
|
||||
@@ -142,19 +158,13 @@ public class FileProcessingPipeline {
|
||||
MDC.put("step", "oci-marker");
|
||||
ociUploadService.uploadMarker(context);
|
||||
context.status = ProcessingStatus.MARKER_UPLOADED;
|
||||
|
||||
// --- ORDS Notify ---
|
||||
MDC.put("step", "ords-notify");
|
||||
ordsNotificationService.triggerDbProcessing(context);
|
||||
|
||||
context.status = ProcessingStatus.ORDS_NOTIFIED;
|
||||
Log.infof("Verarbeitung erfolgreich abgeschlossen: '%s'", zipFilename);
|
||||
|
||||
} catch (ZipException e) {
|
||||
Log.errorf(e, "Ungültige ZIP-Datei '%s' — wird zu .error umbenannt", zipFilename);
|
||||
context.status = ProcessingStatus.FAILED;
|
||||
tryRenameToError(zipFilename);
|
||||
} catch (SftpException | OciException | OrdsException e) {
|
||||
} catch (SftpException | OciException e) {
|
||||
Log.errorf(e, "Verarbeitung von '%s' fehlgeschlagen (Infrastruktur): %s", zipFilename, e.getMessage());
|
||||
context.status = ProcessingStatus.FAILED;
|
||||
} catch (IOException e) {
|
||||
@@ -166,10 +176,11 @@ public class FileProcessingPipeline {
|
||||
} finally {
|
||||
postProcessingCleanup(context);
|
||||
long duration = Duration.between(context.startTime, LocalDateTime.now()).toMillis();
|
||||
Log.infof("Lauf %s für Datei %s abgeschlossen — Status: %s, Dauer: %dms",
|
||||
context.runId, zipFilename, context.status, duration);
|
||||
Log.infof("Datei %s abgeschlossen — Status: %s, Dauer: %dms [fileId=%s]",
|
||||
zipFilename, context.status, duration, context.fileId);
|
||||
Log.info("-----------------------------------------------------------------------------------------------------");
|
||||
MDC.clear();
|
||||
MDC.remove("fileId");
|
||||
MDC.remove("step");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +245,7 @@ public class FileProcessingPipeline {
|
||||
*/
|
||||
private void postProcessingCleanup(ProcessingContext context) {
|
||||
MDC.put("step", "post-cleanup");
|
||||
Log.infof("Cleanup gestartet für Lauf %s", context.runId);
|
||||
Log.infof("Cleanup gestartet für Datei '%s'", context.zipFilename);
|
||||
try {
|
||||
if (context.localZipPath != null) {
|
||||
Files.deleteIfExists(context.localZipPath);
|
||||
@@ -245,8 +256,8 @@ public class FileProcessingPipeline {
|
||||
Log.infof("Lokales Entpack-Verzeichnis gelöscht: %s", context.localExtractDir);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.warnf(e, "Cleanup für Lauf %s fehlgeschlagen — lokale Dateien verbleiben ggf. in %s",
|
||||
context.runId,
|
||||
Log.warnf(e, "Cleanup für '%s' fehlgeschlagen — lokale Dateien verbleiben ggf. in %s",
|
||||
context.zipFilename,
|
||||
context.localZipPath != null ? context.localZipPath.getParent() : "unbekannt");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ galabau.sftp.password=${GALABAU_SFTP_PASSWORD:}
|
||||
# Fingerprint auf host: ssh-keyscan <host> | ssh-keygen -lf -
|
||||
galabau.sftp.host-key-fingerprint=${GALABAU_SFTP_HOST_KEY_FINGERPRINT:SHA256:xyz}
|
||||
# Verzeichnis auf dem SFTP-Server, in dem der Lieferant ZIP-Dateien ablegt
|
||||
galabau.sftp.remote-path=${GALABAU_SFTP_REMOTE_PATH:/bundesagenturfuerarbeit/austausch/dev/galaeingang}
|
||||
galabau.sftp.remote-path=${GALABAU_SFTP_REMOTE_PATH:/bundesagenturfuerarbeit/austausch/sck-dev/galaeingang}
|
||||
# Temporäres lokales Verzeichnis für Download + Entpacken — wird nach jeder ZIP bereinigt
|
||||
galabau.sftp.local-work-dir=/tmp/sftp-work
|
||||
# galabau.sftp.private-key-path=/etc/secrets/sftp-key
|
||||
@@ -27,8 +27,14 @@ galabau.oci.region=${OCI_REGION}
|
||||
galabau.oci.bucket=${OCI_BUCKET}
|
||||
# Root-Prefix im Bucket, muss mit / enden
|
||||
galabau.oci.tenant-prefix=${OCI_TENANT_PREFIX:testmandant-42/}
|
||||
# Eingangs-Prefix unterhalb von tenant-prefix, muss mit / enden
|
||||
galabau.oci.incoming-korrespondenzen-prefix=${OCI_INCOMING_FILES_PATH:BA/Eingang/Import/BA-Korrespondenzen/}
|
||||
# Gemeinsamer Basis-Prefix für alle BA-Eingangs-Pfade, muss mit / enden
|
||||
galabau.oci.ba-base-prefix=${OCI_BA_BASE_PREFIX:BA/Eingang/}
|
||||
# BA-Korrespondenzen: Eingangs-Prefix relativ zu ba-base-prefix, muss mit / enden
|
||||
galabau.oci.korrespondenzen.incoming-prefix=${OCI_KORRESPONDENZEN_INCOMING_PREFIX:Import/BA-Korrespondenzen/}
|
||||
# BA-Korrespondenzen: Archiv-Prefix relativ zu ba-base-prefix — Jahr wird zur Laufzeit angehängt
|
||||
galabau.oci.korrespondenzen.archive-prefix=${OCI_KORRESPONDENZEN_ARCHIVE_PREFIX:BA-Korrespondenzen ZIP-Dateien}
|
||||
# BA-Aufrechnungen: Eingangs-Prefix relativ zu ba-base-prefix, muss mit / enden
|
||||
galabau.oci.aufrechnungen.incoming-prefix=${OCI_AUFRECHNUNGEN_INCOMING_PREFIX:Import/Aufrechnungen/}
|
||||
galabau.oci.tenancy-id=${OCI_TENANCY_ID}
|
||||
galabau.oci.user-id=${OCI_USER_ID}
|
||||
galabau.oci.fingerprint=${OCI_FINGERPRINT}
|
||||
|
||||
@@ -43,14 +43,16 @@ Details zur DB-Verarbeitung: `database/docs/plan_pck_net_storage.md`
|
||||
│ 3c. Alle Dateien in OCI eingang/<zip-name>/ hochladen │
|
||||
│ (Unterordner aus der ZIP werden beibehalten) │
|
||||
│ → Fehler stoppt Verarbeitung dieser ZIP │
|
||||
│ 3d. ZIP auf SFTP umbenennen zu .processed │
|
||||
│ 3d. ZIP auf SFTP löschen │
|
||||
│ → bei ungültiger ZIP: .error (manuelle Prüfung nötig) │
|
||||
│ → bei Infrastrukturfehlern: keine Umbenennung, Retry │
|
||||
│ → bei Infrastrukturfehlern: kein Löschen, Retry │
|
||||
│ 3e. Marker eingang/<zip-name>/_READY_FOR_DB_PROCESSING_ │
|
||||
│ hochladen — ERST NACH dem SFTP-Rename (siehe unten) │
|
||||
│ 3f. ORDS-Endpunkt aufrufen |
|
||||
| (pck_auto_import.p_process_incoming_ba_data) │
|
||||
│ 3g. Lokale Arbeitsdateien löschen │
|
||||
│ hochladen — ERST NACH dem SFTP-Delete (siehe unten) │
|
||||
│ 3f. Lokale Arbeitsdateien löschen │
|
||||
│ │
|
||||
│ Nach allen ZIPs (einmalig): │
|
||||
│ 3g. ORDS-Endpunkt aufrufen │
|
||||
│ (pck_auto_import.p_process_incoming_ba_data) │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
@@ -113,16 +115,16 @@ Daran können die Sachbearbeiter erkennen, dass der Ordner nicht mehr automatisc
|
||||
- OCI: teilweise hochgeladene Dateien bleiben liegen (kein Marker → DB ignoriert den Ordner); beim Retry werden sie überschrieben (OCI PUT ist idempotent)
|
||||
- DB: wird nicht aufgerufen
|
||||
|
||||
**Service: SFTP-Rename zu `.processed` fehlgeschlagen**
|
||||
**Service: SFTP-Delete fehlgeschlagen**
|
||||
- SFTP: ZIP bleibt unverändert, wird beim nächsten Stundenlauf erneut versucht
|
||||
- OCI: Dateien hochgeladen, noch kein Marker (Marker kommt erst nach dem Rename)
|
||||
- OCI: Dateien hochgeladen, noch kein Marker (Marker kommt erst nach dem Delete)
|
||||
- DB: wird nicht aufgerufen
|
||||
- beim nächsten Stundenlauf werden die Dateien aber nicht importiert, da APEX Automation ohne Marker nichts findet
|
||||
- d.h. erst nachdem die ZIP Datei erneut abgearbeitet und komplett in OCI hochgeladen wurde (diesmal mit .processed-Umbennung auf SFTP & Marker in OCI) werden die Dateien abgearbeitet
|
||||
- d.h. erst nachdem die ZIP Datei erneut abgearbeitet und komplett in OCI hochgeladen wurde (diesmal mit erfolgreichem Delete auf SFTP & Marker in OCI) werden die Dateien abgearbeitet
|
||||
|
||||
|
||||
**Service: OCI-Marker-Upload fehlgeschlagen**
|
||||
- SFTP: ZIP ist bereits `.processed` — Quarkus greift sie nie wieder auf
|
||||
- SFTP: ZIP ist bereits gelöscht — Quarkus greift sie nie wieder auf
|
||||
- OCI: Dateien vollständig hochgeladen, Marker fehlt → DB-Verarbeitung wird nicht ausgelöst
|
||||
- DB wird die Dateien wegen dem fehlendem Marker nie automatisiert abarbeiten, aber man sieht das recht einfach über den OCI Dateibrowser in Apex
|
||||
|
||||
@@ -130,9 +132,9 @@ Daran können die Sachbearbeiter erkennen, dass der Ordner nicht mehr automatisc
|
||||
- **Manueller Fix:** Marker-Datei `eingang/<zip-name>/_READY_FOR_DB_PROCESSING_` in OCI von Hand anlegen (leere Datei) — APEX Automation verarbeitet den Batch dann beim nächsten Stundenlauf
|
||||
|
||||
**Service: ORDS-Aufruf fehlgeschlagen**
|
||||
- SFTP: ZIP ist bereits `.processed` — Quarkus greift sie nie wieder auf
|
||||
- SFTP: ZIP ist bereits gelöscht — Quarkus greift sie nie wieder auf
|
||||
- OCI: Dateien + Marker vollständig hochgeladen
|
||||
- DB: APEX Automation findet den Marker beim nächsten Stundenlauf und verarbeitet ihn (Schritt 1) — kein Doppelimport, da Quarkus die `.processed`-Datei nicht erneut verarbeitet
|
||||
- DB: APEX Automation findet den Marker beim nächsten Stundenlauf und verarbeitet ihn (Schritt 1) — kein Doppelimport, da Quarkus die gelöschte ZIP nicht erneut verarbeitet
|
||||
|
||||
**DB: Verarbeitung einer einzelnen Datei schlägt fehl**
|
||||
- OCI `eingang/`: Datei bleibt in `eingang/<zip-name>/` (Rollback)
|
||||
@@ -150,33 +152,33 @@ Daran können die Sachbearbeiter erkennen, dass der Ordner nicht mehr automatisc
|
||||
|
||||
---
|
||||
|
||||
## Design-Entscheidung: Marker wird nach dem SFTP-Rename gesetzt
|
||||
## Design-Entscheidung: Marker wird nach dem SFTP-Delete gesetzt
|
||||
|
||||
Der OCI-Marker `_READY_FOR_DB_PROCESSING_` wird bewusst **nach** dem SFTP-Rename zu `.processed`
|
||||
Der OCI-Marker `_READY_FOR_DB_PROCESSING_` wird bewusst **nach** dem SFTP-Delete
|
||||
hochgeladen — nicht davor. Das erzeugt eine harte Invariante:
|
||||
|
||||
> **Marker in OCI vorhanden ↔ ZIP auf SFTP bereits `.processed`**
|
||||
> **Marker in OCI vorhanden ↔ ZIP auf SFTP bereits gelöscht**
|
||||
|
||||
### Warum ist das wichtig?
|
||||
|
||||
APEX Automation ruft `p_process_incoming_ba_data` in jedem Stundenlauf einmal direkt auf
|
||||
(Schritt 1, Fallback), und Quarkus ruft dieselbe Funktion via ORDS auf (Schritt 3f, schneller Pfad).
|
||||
(Schritt 1, Fallback), und Quarkus ruft dieselbe Funktion via ORDS auf (Schritt 3g, schneller Pfad).
|
||||
Ohne die Invariante könnte folgender Race entstehen:
|
||||
|
||||
1. Quarkus lädt Dateien + Marker hoch, schlägt dann beim SFTP-Rename fehl
|
||||
1. Quarkus lädt Dateien + Marker hoch, schlägt dann beim SFTP-Delete fehl
|
||||
2. APEX Schritt 1 findet den Marker → importiert Daten
|
||||
3. Quarkus wiederholt den Lauf, ruft ORDS auf → zweiter Import derselben Daten
|
||||
|
||||
Mit der Invariante ist dieser Fall ausgeschlossen: APEX Schritt 1 findet nur dann einen Marker,
|
||||
wenn die ZIP auf dem SFTP bereits `.processed` ist. Ist sie das, greift Quarkus sie im Retry
|
||||
wenn die ZIP auf dem SFTP bereits gelöscht ist. Ist sie das, greift Quarkus sie im Retry
|
||||
nicht mehr an — `listZipFiles()` gibt nur `.zip`-Dateien zurück.
|
||||
|
||||
### Einzig verbleibender manueller Fehlerfall
|
||||
|
||||
Schlägt der Marker-Upload fehl (nach erfolgreichem SFTP-Rename), ist der Zustand eindeutig
|
||||
erkennbar: `.processed` auf SFTP, Dateien in OCI ohne Marker. Manueller Fix: Marker-Datei
|
||||
Schlägt der Marker-Upload fehl (nach erfolgreichem SFTP-Delete), ist der Zustand eindeutig
|
||||
erkennbar: ZIP auf SFTP gelöscht, Dateien in OCI ohne Marker. Manueller Fix: Marker-Datei
|
||||
in OCI von Hand anlegen. Dieser Fall erfordert keine DB-seitige Idempotenz, da Quarkus
|
||||
die Datei nicht erneut verarbeitet und ORDS nicht aufruft.
|
||||
die gelöschte ZIP nicht erneut verarbeitet und ORDS nicht aufruft.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user