Compare commits

...

6 Commits

15 changed files with 314 additions and 206 deletions

View File

@@ -15,7 +15,9 @@
"WebFetch(domain:walidhajeri.hashnode.dev)", "WebFetch(domain:walidhajeri.hashnode.dev)",
"Bash(find \"C:\\\\\\\\src\\\\\\\\Galabau\\\\\\\\glb-spielwiese\\\\\\\\automaton\" -name \"FileProcessingPipeline.java\")", "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(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\")" "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)"
] ]
} }
} }

View File

@@ -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 PROCEDURE p_create_wv_autonomous(i_swv_bemerkung inkasso.sy_wiedervorlage.swv_bemerkung%TYPE DEFAULT NULL
) )
/*Kopf------------------------------------------------------------------------------------------------ /*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 -- SCK 2026-04-27 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -17,7 +17,7 @@ create or replace package body pck_auto_import as
BEGIN BEGIN
pck_wiedervorlage.p_wiedervorlage_anlegen (i_swv_stp_id_art => pck_stammdaten.f_get_stp_id_by_programmid('WV_IMPORT_BA_KORRES') 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_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') ,i_swv_mit_id_wsachbearbeiter => pck_system.f_get_par_wert_by_programmid('BA_IMPORT_SB_MIT_ID')
); );
COMMIT; -- Nur diese autonome Transaktion 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 procedure p_run_ba_korrespondenz_dateieingang_automation
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Einstiegspunkt für die APEX Automation (stündlich). -- Beschreibung: Einstiegspunkt für die APEX Automation (stündlich).
-- Schritt 1: p_process_incoming_ba_data aufrufen — verarbeitet Batches, die bereits -- 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 -- in OCI liegen (Fallback für den Fall, dass der ORDS-Aufruf im letzten
-- Quarkus-Lauf fehlgeschlagen ist). -- Quarkus-Lauf fehlgeschlagen ist).
-- Schritt 2: Quarkus Dateieingang Service via HTTP POST anstoßen (fire & forget). -- 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. -- 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 -- SCK 2026-04-21 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -66,11 +66,11 @@ create or replace package body pck_auto_import as
,i_message => 'Verarbeitung offener BA-Korrespondenz-Dateien aus OCI 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( pck_log.p_info(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,i_action => l_log_action
,i_message => 'Quarkus Dateieingang Service wird angestoßen' ,i_message => 'Quarkus Dateieingang Service wird angestoßen'
); );
begin begin
l_service_url := pck_system.f_get_par_wert_by_programmid('AUTOMATON_BASE_URL') || l_automaton_endpoint; 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( pck_log.p_info(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,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 when 409
then 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( pck_log.p_info(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,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 else
pck_log.p_error( pck_log.p_error(
@@ -116,12 +116,12 @@ create or replace package body pck_auto_import as
when others when others
then then
-- Quarkus-Aufruf fehlgeschlagen: loggen, nicht eskalieren. -- 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( pck_log.p_error(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,i_action => l_log_action
,i_message => 'Aufruf des BA Dateieingang Service fehlgeschlagen: ' || sqlerrm ,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;
end p_run_ba_korrespondenz_dateieingang_automation; end p_run_ba_korrespondenz_dateieingang_automation;
@@ -134,16 +134,16 @@ create or replace package body pck_auto_import as
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Importiert eine einzelne Datei aus dem OCI Eingangsordner in die Datenbank. -- Beschreibung: Importiert eine einzelne Datei aus dem OCI Eingangsordner in die Datenbank.
-- Ruft inkasso.pck_import.f_import_ba_dokument auf. -- Ruft inkasso.pck_import.f_import_ba_dokument auf.
-- Bei Rückgabe 1 (Erfolg): Datei in den Zielordner verschieben. -- Bei Rückgabe 1 (Erfolg): Datei in den Zielordner verschieben.
-- Bei Rückgabe != 1 (z.B. ungültiger Dateiname): Warnung loggen und Exception werfen -- Bei Rückgabe != 1 (z.B. ungültiger Dateiname): Warnung loggen und Exception werfen
-- — Datei bleibt liegen, Commit/Rollback liegt beim Aufrufer. -- — Datei bleibt liegen, Commit/Rollback liegt beim Aufrufer.
-- Kein Commit hier — wird von p_process_incoming_ba_data übernommen. -- 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_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-08 Stub erstellt
-- SCK 2026-04-09 Implementierung: Aufruf f_import_ba_dokument, Wiedervorlage bei Fehler -- 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 -- 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( pck_log.p_warn(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,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 ,i_object_ref => i_object_key
); );
-- Bei einem Import Fehler: Wiedervorlage für Sachbearbeiter erstellen & Fehlermeldung mit in die Wiedervorlage schreiben -- 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 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 || '").' || '" ist folgender Fehler aufgetreten: "' || SQLERRM || '" (Siehe "' || i_object_key || '").'
); );
raise; raise;
@@ -193,12 +193,12 @@ create or replace package body pck_auto_import as
pck_log.p_warn( pck_log.p_warn(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,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 ,i_object_ref => i_object_key
); );
-- Wiedervorlage für Sachbearbeiter erstellen -- 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 || '").' 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( pck_log.p_info(
@@ -208,7 +208,7 @@ create or replace package body pck_auto_import as
,i_object_ref => i_object_key ,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; end if;
pck_log.p_info( pck_log.p_info(
@@ -236,13 +236,13 @@ create or replace package body pck_auto_import as
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Verarbeitet alle fertigen Eingangs-Batches aus dem OCI Eingangsordner (Netzlaufwerk). -- Beschreibung: Verarbeitet alle fertigen Eingangs-Batches aus dem OCI Eingangsordner (Netzlaufwerk).
-- Wird von ORDS-Endpunkt (von Quarkus Automaton) und aus Apex Automation aufgerufen. -- Wird von ORDS-Endpunkt (von Quarkus Automaton) und aus Apex Automation aufgerufen.
-- Pro Datei: Import + Move Commit; bei Exception: Rollback, Datei bleibt liegen, -- Pro Datei: Import + Move -> Commit; bei Exception: Rollback, Datei bleibt liegen,
-- nächste Datei wird trotzdem verarbeitet. -- nächste Datei wird trotzdem verarbeitet.
-- Nach dem Datei-Loop: DB-Marker immer löschen (verhindert erneuten Durchlauf). -- 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 -- 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-08 Prozedur erstellt
-- SCK 2026-04-09 Fehlerbehandlung: Datei bleibt liegen, Fehler-Marker, kein erneuter Durchlauf -- 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 -- SCK 2026-04-09 SB-Marker statt l_had_errors-Flag; Move in p_import_ba_korrespondenz verschoben
@@ -271,7 +271,7 @@ create or replace package body pck_auto_import as
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_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'); 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( l_folders := pck_net_storage.f_list_objects(
i_parent_folder => l_eingang_prefix i_parent_folder => l_eingang_prefix
,i_include_subfolders => 'N' ,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. -- 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'); l_db_processing_marker_key := rec_folder.object_key || pck_system.f_get_par_wert_by_programmid('NETSTORE_MARKER_DB');
pck_log.p_info( pck_log.p_info(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,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 ,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 begin
l_meta := pck_net_storage.f_get_object_metadata(l_db_processing_marker_key); l_meta := pck_net_storage.f_get_object_metadata(l_db_processing_marker_key);
exception exception
@@ -314,7 +314,7 @@ create or replace package body pck_auto_import as
pck_log.p_info( pck_log.p_info(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,i_action => l_log_action
,i_message => 'Kein DB-Verarbeitungsmarker vorhanden — Entpackter ZIP-Ordner 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 ,i_object_ref => rec_folder.object_key
); );
continue; continue;
@@ -322,7 +322,7 @@ create or replace package body pck_auto_import as
raise; raise;
end; 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( l_zip_name := substr(
rec_folder.object_key rec_folder.object_key
,length(l_eingang_prefix) + 1 ,length(l_eingang_prefix) + 1
@@ -333,7 +333,7 @@ create or replace package body pck_auto_import as
pck_log.p_info( pck_log.p_info(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,i_action => l_log_action
,i_message => 'ZIP-START: Verarbeitung von entpacktem ZIP-Ordner 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 ,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)) for rec_file in (select object_key, is_folder from table(l_files))
loop 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' if rec_file.object_key = l_db_processing_marker_key or rec_file.is_folder = 'Y'
then then
continue; 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) -- 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); 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 -- 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 -- sonst würde ein Fehler bei einer späteren Datei den DB-Import bereits verschobener Dateien zurückrollen
commit; commit;
exception exception
@@ -383,17 +383,17 @@ create or replace package body pck_auto_import as
end; end;
end loop; 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_net_storage.p_delete_object(l_db_processing_marker_key);
pck_log.p_info( pck_log.p_info(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,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 ,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( l_files := pck_net_storage.f_list_objects(
i_parent_folder => rec_folder.object_key i_parent_folder => rec_folder.object_key
,i_include_subfolders => 'Y' ,i_include_subfolders => 'Y'
@@ -415,11 +415,11 @@ create or replace package body pck_auto_import as
pck_log.p_warn( pck_log.p_warn(
i_module => c_log_module i_module => c_log_module
,i_action => l_log_action ,i_action => l_log_action
,i_message => 'ZIP-ENDE: Entpackter ZIP-Ordner 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 ,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'); 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( pck_net_storage.p_upload_object(
i_object_key => l_sb_marker_key i_object_key => l_sb_marker_key

View File

@@ -1,6 +1,6 @@
create or replace package pck_auto_import as 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; procedure p_run_ba_korrespondenz_dateieingang_automation;
-- Von ORDS-Endpunkt aufgerufen (net_storage/process_incoming_ba_data): importiert Dateien aus OCI -- Von ORDS-Endpunkt aufgerufen (net_storage/process_incoming_ba_data): importiert Dateien aus OCI

View File

@@ -9,18 +9,18 @@ create or replace package body pck_log as
,i_object_ref in varchar2 default null ,i_object_ref in varchar2 default null
) )
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Interne Hilfsprozedur — schreibt einen Log-Eintrag in lg_app_log. -- Beschreibung: Interne Hilfsprozedur — schreibt einen Log-Eintrag in lg_app_log.
-- Verwendet autonomous_transaction, damit der Commit unabhängig vom Aufrufer erfolgt. -- Verwendet autonomous_transaction, damit der Commit unabhängig vom Aufrufer erfolgt.
-- Wird ausschließlich von p_info, p_warn und p_error aufgerufen. -- Wird ausschließlich von p_info, p_warn und p_error aufgerufen.
------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------
-- Parameter: i_level Log-Level (INFO, WARN, ERROR) -- Parameter: i_level Log-Level (INFO, WARN, ERROR)
-- i_module Aufgerufenes Modul / Package -- i_module Aufgerufenes Modul / Package
-- i_action Aktion innerhalb des Moduls -- i_action Aktion innerhalb des Moduls
-- i_message Kurze Meldung -- i_message Kurze Meldung
-- i_detail Optionaler Langtext (Stack Trace, JSON, etc.) -- 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 -- SCK 2026-04-08 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -73,9 +73,9 @@ create or replace package body pck_log as
-- Parameter: i_module Aufgerufenes Modul / Package -- Parameter: i_module Aufgerufenes Modul / Package
-- i_action Aktion innerhalb des Moduls -- i_action Aktion innerhalb des Moduls
-- i_message Kurze Meldung -- 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 -- SCK 2026-04-08 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -101,9 +101,9 @@ create or replace package body pck_log as
-- Parameter: i_module Aufgerufenes Modul / Package -- Parameter: i_module Aufgerufenes Modul / Package
-- i_action Aktion innerhalb des Moduls -- i_action Aktion innerhalb des Moduls
-- i_message Kurze Meldung -- 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 -- SCK 2026-04-08 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -131,9 +131,9 @@ create or replace package body pck_log as
-- i_action Aktion innerhalb des Moduls -- i_action Aktion innerhalb des Moduls
-- i_message Kurze Fehlerbeschreibung -- i_message Kurze Fehlerbeschreibung
-- i_detail Optionaler Langtext (Stack Trace, JSON, etc.) -- 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 -- SCK 2026-04-08 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is

View File

@@ -9,15 +9,15 @@ create or replace package body pck_net_storage as
,i_action in varchar2 default null ,i_action in varchar2 default null
) return varchar2 ) return varchar2
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Baut die vollständige OCI Object Storage URL aus den Konfigurationsparametern. -- 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. -- 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 -- 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 -- 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 -- SCK 2026-04-08 Funktion erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -35,7 +35,7 @@ create or replace package body pck_net_storage as
return l_base || '/actions/' || i_action; return l_base || '/actions/' || i_action;
elsif i_object_key is not null elsif i_object_key is not null
then 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); return l_base || '/o/' || utl_url.escape(i_object_key, false);
else else
return l_base || '/o'; 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) procedure p_assert_allowed (i_object_key in varchar2)
/*Kopf------------------------------------------------------------------------------------------------ /*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, -- Wirft Application Error -20008 bei null-Key, -20004 bei Path Traversal,
-- -20005 bei Scope-Verletzung. -- -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-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*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
l_tenant_prefix varchar2(256); 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 ,i_content_type in varchar2 default null
) return clob ) return clob
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Führt einen HTTP-Request gegen die OCI Object Storage API aus. -- 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. -- Wertet den HTTP-Statuscode aus und löst bei Fehler einen Application Error aus.
-- Authentifizierung erfolgt über APEX Web Credential (NETSTORE_CRED_ID). -- Authentifizierung erfolgt über APEX Web Credential (NETSTORE_CRED_ID).
------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------
-- Parameter: i_method HTTP-Methode (GET, PUT, DELETE, POST, HEAD) -- 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_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 -- 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-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*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
l_response clob; l_response clob;
@@ -131,7 +131,7 @@ create or replace package body pck_net_storage as
l_header_index number := 1; l_header_index number := 1;
l_content_length number; l_content_length number;
begin 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; apex_web_service.g_request_headers.delete;
if i_content_type is not null 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; l_header_index := l_header_index + 1;
*/ */
-- nur für leere BLOBs (z.B. leerer Ordner) Content-Length 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 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 -- 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 if i_body_blob is not null
and dbms_lob.getlength(i_body_blob) = 0 and dbms_lob.getlength(i_body_blob) = 0
then then
@@ -217,7 +217,7 @@ create or replace package body pck_net_storage as
return l_response; return l_response;
end f_make_request; 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 ( function f_list_objects_internal (
i_parent_folder in varchar2 i_parent_folder in varchar2
,i_include_subfolders in varchar2 ,i_include_subfolders in varchar2
@@ -225,18 +225,18 @@ create or replace package body pck_net_storage as
,i_limit in number ,i_limit in number
) return t_net_storage_tab ) return t_net_storage_tab
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Listet Objekte und Unterordner im Bucket ohne Rechte- oder Scope-Prüfung. -- Beschreibung: Listet Objekte und Unterordner im Bucket ohne Rechte- oder Scope-Prüfung.
-- Paginiert automatisch über nextStartWith bis alle Ergebnisse geladen sind. -- Paginiert automatisch über nextStartWith bis alle Ergebnisse geladen sind.
-- Wird von f_list_objects (öffentlich) und p_delete_folder (Leerprüfung) intern genutzt. -- Wird von f_list_objects (öffentlich) und p_delete_folder (Leerprüfung) intern genutzt.
------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------
-- Parameter: i_parent_folder Ordnerpfad im Bucket (z.B. eingang/) -- Parameter: i_parent_folder Ordnerpfad im Bucket (z.B. eingang/)
-- i_include_subfolders 'Y' = alle Dateien rekursiv, 'N' = nur direkte Kinder des Ordners -- 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) -- 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 -- SCK 2026-04-08 Funktion erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -288,7 +288,7 @@ create or replace package body pck_net_storage as
,l_obj_path.path ,l_obj_path.path
,l_obj_path.filename ,l_obj_path.filename
-- Explizit angelegte Ordner sind Zero-Byte-Objekte mit trailing /; -- 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 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 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) ,(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 loop;
end if; end if;
-- Nächste Seite prüfen -- Nächste Seite prüfen
if not l_done if not l_done
then then
l_next_start := json_value(l_response, '$.nextStartWith'); 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. -- Implizite Ordner aus Object-Keys ableiten.
-- Die OCI-API liefert virtuelle Ordner (nie als Zero-Byte-Objekt angelegt) nur -- 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 -- 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 declare
l_new_folders apex_t_varchar2; l_new_folders apex_t_varchar2;
begin 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. -- connect by level iteriert von 1 bis zur Anzahl der Slashes im Key.
-- instr(..., '/', 1, level) liefert die Position des n-ten Slashes. -- instr(..., '/', 1, level) liefert die Position des n-ten Slashes.
-- substr(..., 1, <position>) schneidet den Key bis einschließlich -- substr(..., 1, <position>) schneidet den Key bis einschließlich
-- dieses Slashes ab — das Ergebnis ist der Ordnerpfad auf Ebene n. -- dieses Slashes ab — das Ergebnis ist der Ordnerpfad auf Ebene n.
-- --
-- Beispiel für 'mandant/Eingang/batch-001/datei.pdf' (3 Slashes): -- Beispiel für 'mandant/Eingang/batch-001/datei.pdf' (3 Slashes):
-- level 1 'mandant/' -- level 1 -> 'mandant/'
-- level 2 'mandant/Eingang/' -- level 2 -> 'mandant/Eingang/'
-- level 3 'mandant/Eingang/batch-001/' -- level 3 -> 'mandant/Eingang/batch-001/'
-- --
-- prior object_key = object_key : bindet jede Zeile an sich selbst, -- 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, -- prior sys_guid() is not null : verhindert Cycle-Detection-Fehler,
-- da keine echte Eltern-Kind-Beziehung vorliegt. -- da keine echte Eltern-Kind-Beziehung vorliegt.
select substr(r.object_key, 1, instr(r.object_key, '/', 1, level)) as folder_path 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 and prior sys_guid() is not null
) )
-- Nur Pfade unterhalb des Parent-Folders behalten: -- 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). -- 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 -- 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, '') || '%' where folder_path like nvl(l_parent_folder, '') || '%'
and folder_path != nvl(l_parent_folder, chr(0)) 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). -- Zero-Byte-Objekte oder via $.prefixes gelieferte virtuelle Ordner).
minus minus
select object_key select object_key
@@ -417,18 +417,18 @@ create or replace package body pck_net_storage as
return l_result; return l_result;
end f_list_objects_internal; end f_list_objects_internal;
-- ==================== Öffentliche Funktionen ==================== -- ==================== Öffentliche Funktionen ====================
function f_split_object_key (i_object_key in varchar2) return t_object_path function f_split_object_key (i_object_key in varchar2) return t_object_path
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Extrahiert Pfad und Dateiname aus einem OCI-Objektschlüssel. -- Beschreibung: Extrahiert Pfad und Dateiname aus einem OCI-Objektschlüssel.
-- Bei Ordner-Keys (trailing Slash) wird der Ordnername als Dateiname zurückgegeben. -- 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 -- SCK 2026-04-09 Funktion erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -460,16 +460,16 @@ create or replace package body pck_net_storage as
,i_limit in number default 0 ,i_limit in number default 0
) return t_net_storage_tab ) return t_net_storage_tab
/*Kopf------------------------------------------------------------------------------------------------ /*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/) -- 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_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) -- 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 -- SCK 2026-04-08 Funktion erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is 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 function f_download_object (i_object_key in varchar2) return blob
/*Kopf------------------------------------------------------------------------------------------------ /*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 -- SCK 2026-04-08 Funktion erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -501,12 +501,15 @@ create or replace package body pck_net_storage as
pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES'); pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES');
p_assert_allowed(i_object_key); 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( l_response := apex_web_service.make_rest_request_b(
p_url => f_build_url(i_object_key) p_url => f_build_url(i_object_key)
,p_http_method => 'GET' ,p_http_method => 'GET'
,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NETSTORE_CRED_ID') ,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; l_status := apex_web_service.g_status_code;
if l_status = 404 if l_status = 404
@@ -529,13 +532,13 @@ create or replace package body pck_net_storage as
,i_content_type in varchar2 ,i_content_type in varchar2
) )
/*Kopf------------------------------------------------------------------------------------------------ /*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 -- Parameter: i_object_key Zielpfad im Bucket
-- i_content Dateiinhalt als BLOB -- i_content Dateiinhalt als BLOB
-- i_content_type MIME-Type des Inhalts (z.B. application/octet-stream) -- i_content_type MIME-Type des Inhalts (z.B. application/octet-stream)
------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------
-- MA Datum Änderung -- MA Datum Änderung
-- SCK 2026-04-08 Prozedur erstellt -- SCK 2026-04-08 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -547,7 +550,7 @@ create or replace package body pck_net_storage as
if substr(i_object_key, -1) = '/' if substr(i_object_key, -1) = '/'
then 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; end if;
l_response := f_make_request( 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) procedure p_delete_object (i_object_key in varchar2)
/*Kopf------------------------------------------------------------------------------------------------ /*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 -- SCK 2026-04-08 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -591,21 +594,21 @@ create or replace package body pck_net_storage as
pck_log.p_info( pck_log.p_info(
i_module => c_log_module i_module => c_log_module
,i_action => 'DELETE' ,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 ,i_object_ref => i_object_key
); );
end p_delete_object; end p_delete_object;
procedure p_delete_folder (i_folder_key in varchar2) procedure p_delete_folder (i_folder_key in varchar2)
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Löscht einen leeren Ordner im OCI Bucket. -- Beschreibung: Löscht einen leeren Ordner im OCI Bucket.
-- Schlägt fehl, wenn noch Objekte oder Unterordner vorhanden sind. -- Schlägt fehl, wenn noch Objekte oder Unterordner vorhanden sind.
------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------
-- Parameter: i_folder_key Kompletter Ordner name inkl. Pfad (z.B. eingang/batch-001/) -- 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-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*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
l_objects t_net_storage_tab; 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'); pck_mitarbeiterrecht.p_hat_recht('ADMIN');
p_assert_allowed(l_prefix); 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( l_objects := f_list_objects_internal(
i_parent_folder => l_prefix i_parent_folder => l_prefix
,i_include_subfolders => 'N' ,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 for i in 1 .. l_objects.count
loop loop
apex_debug.info(' [%s] key=%s | is_folder=%s', i, l_objects(i).object_key, l_objects(i).is_folder); apex_debug.info(' [%s] key=%s | is_folder=%s', i, l_objects(i).object_key, l_objects(i).is_folder);
end loop; 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(*) select count(*)
into l_count into l_count
from table(l_objects) from table(l_objects)
@@ -641,10 +644,10 @@ create or replace package body pck_net_storage as
if l_count > 0 if l_count > 0
then 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; end if;
-- Ordner-Objekt selbst löschen -- Ordner-Objekt selbst löschen
l_response := f_make_request( l_response := f_make_request(
i_method => 'DELETE' i_method => 'DELETE'
,i_url => f_build_url(l_prefix) ,i_url => f_build_url(l_prefix)
@@ -654,7 +657,7 @@ create or replace package body pck_net_storage as
pck_log.p_info( pck_log.p_info(
i_module => c_log_module i_module => c_log_module
,i_action => 'DELETE_FOLDER' ,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 ,i_object_ref => l_prefix
); );
end p_delete_folder; 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. -- Beschreibung: Benennt ein Objekt innerhalb desselben Verzeichnisses um.
-- Verwendet die OCI renameObject-Action (kein physisches Kopieren). -- 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) -- i_new_name Neuer Dateiname (ohne Pfad)
------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------
-- MA Datum Änderung -- MA Datum Änderung
-- SCK 2026-04-08 Prozedur erstellt -- SCK 2026-04-08 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -689,7 +692,7 @@ create or replace package body pck_net_storage as
if instr(i_new_name, '/') > 0 if instr(i_new_name, '/') > 0
then 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; end if;
l_obj_path := f_split_object_key(i_object_key); 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 if l_new_key = i_object_key
then 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; end if;
select json_object( select json_object(
@@ -730,12 +733,12 @@ create or replace package body pck_net_storage as
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Verschiebt ein Objekt in einen anderen Ordner im selben Bucket. -- Beschreibung: Verschiebt ein Objekt in einen anderen Ordner im selben Bucket.
-- Verwendet die OCI renameObject-Action (kein physisches Kopieren). -- 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 -- Parameter: i_object_key Vollständiger Objektschlüssel der Quelldatei
-- i_target_prefix Zielpräfix inkl. trailing Slash (z.B. verarbeitet/batch-001/) -- 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 -- SCK 2026-04-08 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -751,7 +754,7 @@ create or replace package body pck_net_storage as
if i_target_prefix is null if i_target_prefix is null
then then
raise_application_error(-20015, 'Zielpräfix darf nicht null sein'); raise_application_error(-20015, 'Zielpräfix darf nicht null sein');
end if; end if;
l_target_prefix := f_normalize_prefix(i_target_prefix); 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. -- Beschreibung: Legt einen neuen Ordner im OCI Bucket an.
-- Ordner werden als leeres Objekt mit trailing Slash simuliert. -- 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) -- i_folder_name Name des neuen Ordners (ohne Slash)
------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------
-- MA Datum Änderung -- MA Datum Änderung
-- SCK 2026-04-08 Prozedur erstellt -- SCK 2026-04-08 Prozedur erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is
@@ -823,7 +826,7 @@ create or replace package body pck_net_storage as
if instr(i_folder_name, '/') > 0 if instr(i_folder_name, '/') > 0
then then
raise_application_error(-20011, 'Ordnername darf keinen Schrägstrich enthalten'); raise_application_error(-20011, 'Ordnername darf keinen Schrägstrich enthalten');
end if; end if;
if l_prefix is not null 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 function f_get_object_metadata (i_object_key in varchar2) return t_object_meta
/*Kopf------------------------------------------------------------------------------------------------ /*Kopf------------------------------------------------------------------------------------------------
-- Beschreibung: Ruft die Metadaten eines Objekts per HEAD-Request ab (kein Download des Inhalts). -- 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 -- SCK 2026-04-08 Funktion erstellt
------------------------------------------------------------------------------------------------Kopf*/ ------------------------------------------------------------------------------------------------Kopf*/
is is

View File

@@ -5,10 +5,10 @@ from lg_app_log
--truncate table lg_app_log; --truncate table lg_app_log;
/* /*
Test Calls für logs Test Calls für logs
*/ */
begin begin
-- Neuen Batch-Ordner für heutigen Import anlegen -- Neuen Batch-Ordner für heutigen Import anlegen
pck_net_storage.p_create_folder( pck_net_storage.p_create_folder(
i_prefix => 'testmandant-42/Eingang/Verarbeitet 2026/' i_prefix => 'testmandant-42/Eingang/Verarbeitet 2026/'
,i_folder_name => 'Batch-2026-04-09' ,i_folder_name => 'Batch-2026-04-09'
@@ -41,7 +41,7 @@ begin
,i_new_name => 'angebot_huber_2026-04.pdf' ,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( pck_net_storage.p_delete_object(
i_object_key => 'testmandant-42/Eingang/Import/scan_003.pdf' i_object_key => 'testmandant-42/Eingang/Import/scan_003.pdf'
); );

View 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;
/

View File

@@ -1,5 +1,5 @@
-- Schema-Level Type für f_list_objects Cursor-Rückgabe. -- 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. -- Wird benötigt da Oracle TABLE() in SQL nur schema-level Types unterstützt.
create or replace type t_net_storage_row as object ( create or replace type t_net_storage_row as object (
object_key varchar2(1024) object_key varchar2(1024)
,object_path varchar2(1024) ,object_path varchar2(1024)

View File

@@ -30,16 +30,18 @@ FileProcessingPipeline [ManagedExecutor — Hintergrund-Thread]
├─→ OciUploadService.upload() [OCI SDK] ├─→ OciUploadService.upload() [OCI SDK]
│ └─ Dateien in eingang/<zip-name>/ + Marker │ └─ Dateien in eingang/<zip-name>/ + Marker
├─→ SftpService.renameRemote() [SSHJ] ├─→ SftpService.deleteRemote() [SSHJ]
│ └─ .processed (Erfolg) oder .error (Fehler) │ └─ ZIP gelöscht (Erfolg) oder .error (Fehler)
├─→ OrdsNotificationService.notify() [MicroProfile REST Client]
│ └─ POST pck_auto_import.p_process_incoming_ba_data
└─→ Cleanup: lokale Dateien löschen [immer, im finally] └─→ 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 ## Pipeline-Steps

View File

@@ -16,16 +16,22 @@ public interface OciConfig {
String bucket(); 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. * Muss mit {@code /} enden.
*/ */
String tenantPrefix(); String tenantPrefix();
/** /**
* Prefix für eingehende Dateien unterhalb von {@code tenantPrefix}, * Gemeinsamer Basis-Prefix für alle BA-Eingangs-Pfade unterhalb von {@code tenantPrefix},
* z.B. {@code eingang/}. Muss mit {@code /} enden. * 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}. */ /** OCI Tenancy OCID. Aus Env-Var {@code OCI_TENANCY_ID}. */
String tenancyId(); String tenancyId();
@@ -49,4 +55,32 @@ public interface OciConfig {
* Muss mit der APEX Automation und dem ORDS-Package abgestimmt sein. * Muss mit der APEX Automation und dem ORDS-Package abgestimmt sein.
*/ */
String markerFilenameDbProcessing(); 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();
}
} }

View File

@@ -5,6 +5,7 @@ public enum ProcessingStatus {
PENDING, PENDING,
PARTIALLY_UPLOADED, PARTIALLY_UPLOADED,
MARKER_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, ORDS_NOTIFIED,
FAILED FAILED
} }

View File

@@ -19,6 +19,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Year;
import java.util.List; 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); Log.infof("OCI-Upload: %d Datei(en) für '%s'", files.size(), context.zipNameWithoutExt);
for (FileEntry entry : files) { for (FileEntry entry : files) {
String key = buildKey(context.zipNameWithoutExt, entry.relativePath); String key = buildKorrespondenzenKey(context.zipNameWithoutExt, entry.relativePath);
entry.ociKey = key; entry.ociKey = key;
putFile(key, context.localExtractDir.resolve(entry.relativePath), entry.fileSize); putFile(key, context.localExtractDir.resolve(entry.relativePath), entry.fileSize);
Log.infof("Datei hochgeladen: %s (%d Bytes)", key, entry.fileSize); Log.infof("Datei hochgeladen: %s (%d Bytes)", key, entry.fileSize);
} }
Log.infof("OCI-Upload Dateien abgeschlossen: %d Datei(en) in '%s'", 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. * 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 * Wird erst nach dem SFTP-Delete aufgerufen, damit Marker und
* SFTP-Zustand immer konsistent sind: Marker vorhanden ↔ ZIP bereits als verarbeitet markiert. * 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 * @param context enthält den Ziel-Prefix für den Marker-Key
* @throws OciException bei Verbindungs- oder Upload-Fehlern * @throws OciException bei Verbindungs- oder Upload-Fehlern
*/ */
public void uploadMarker(ProcessingContext context) throws OciException { 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); Log.infof("Lade Marker hoch: '%s'", markerKey);
putMarker(markerKey); putMarker(markerKey);
context.markerUploaded = true; context.markerUploaded = true;
Log.infof("Marker hochgeladen: '%s'", markerKey); 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) { private String buildKorrespondenzenPrefix(String zipNameWithoutExt) {
return buildPrefix(zipNameWithoutExt) + relativePath; 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 { private void putFile(String key, Path localFile, long fileSize) throws OciException {

View File

@@ -130,6 +130,11 @@ public class FileProcessingPipeline {
Log.infof("ZIP '%s' heruntergeladen (%d Bytes)", zipFilename, Log.infof("ZIP '%s' heruntergeladen (%d Bytes)", zipFilename,
Files.size(context.localZipPath)); Files.size(context.localZipPath));
// --- OCI ZIP-Archiv ---
MDC.put("step", "oci-zip-archive");
Log.info("Starte ZIP-Upload in OCI");
ociUploadService.uploadZipFile(context);
// --- Entpacken --- // --- Entpacken ---
MDC.put("step", "zip-extract"); MDC.put("step", "zip-extract");
zipExtractionService.extract(context); zipExtractionService.extract(context);

View File

@@ -12,7 +12,7 @@ galabau.sftp.password=${GALABAU_SFTP_PASSWORD:}
# Fingerprint auf host: ssh-keyscan <host> | ssh-keygen -lf - # Fingerprint auf host: ssh-keyscan <host> | ssh-keygen -lf -
galabau.sftp.host-key-fingerprint=${GALABAU_SFTP_HOST_KEY_FINGERPRINT:SHA256:xyz} galabau.sftp.host-key-fingerprint=${GALABAU_SFTP_HOST_KEY_FINGERPRINT:SHA256:xyz}
# Verzeichnis auf dem SFTP-Server, in dem der Lieferant ZIP-Dateien ablegt # 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 # Temporäres lokales Verzeichnis für Download + Entpacken — wird nach jeder ZIP bereinigt
galabau.sftp.local-work-dir=/tmp/sftp-work galabau.sftp.local-work-dir=/tmp/sftp-work
# galabau.sftp.private-key-path=/etc/secrets/sftp-key # galabau.sftp.private-key-path=/etc/secrets/sftp-key
@@ -27,8 +27,14 @@ galabau.oci.region=${OCI_REGION}
galabau.oci.bucket=${OCI_BUCKET} galabau.oci.bucket=${OCI_BUCKET}
# Root-Prefix im Bucket, muss mit / enden # Root-Prefix im Bucket, muss mit / enden
galabau.oci.tenant-prefix=${OCI_TENANT_PREFIX:testmandant-42/} galabau.oci.tenant-prefix=${OCI_TENANT_PREFIX:testmandant-42/}
# Eingangs-Prefix unterhalb von tenant-prefix, muss mit / enden # Gemeinsamer Basis-Prefix für alle BA-Eingangs-Pfade, muss mit / enden
galabau.oci.incoming-korrespondenzen-prefix=${OCI_INCOMING_FILES_PATH:BA/Eingang/Import/BA-Korrespondenzen/} 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.tenancy-id=${OCI_TENANCY_ID}
galabau.oci.user-id=${OCI_USER_ID} galabau.oci.user-id=${OCI_USER_ID}
galabau.oci.fingerprint=${OCI_FINGERPRINT} galabau.oci.fingerprint=${OCI_FINGERPRINT}

View File

@@ -43,14 +43,16 @@ Details zur DB-Verarbeitung: `database/docs/plan_pck_net_storage.md`
│ 3c. Alle Dateien in OCI eingang/<zip-name>/ hochladen │ │ 3c. Alle Dateien in OCI eingang/<zip-name>/ hochladen │
│ (Unterordner aus der ZIP werden beibehalten) │ │ (Unterordner aus der ZIP werden beibehalten) │
│ → Fehler stoppt Verarbeitung dieser ZIP │ │ → 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 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_ │ │ 3e. Marker eingang/<zip-name>/_READY_FOR_DB_PROCESSING_ │
│ hochladen — ERST NACH dem SFTP-Rename (siehe unten) │ │ hochladen — ERST NACH dem SFTP-Delete (siehe unten) │
│ 3f. ORDS-Endpunkt aufrufen | │ 3f. Lokale Arbeitsdateien löschen
| (pck_auto_import.p_process_incoming_ba_data)
3g. 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) - 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 - 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 - 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 - DB: wird nicht aufgerufen
- beim nächsten Stundenlauf werden die Dateien aber nicht importiert, da APEX Automation ohne Marker nichts findet - 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** **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 - 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 - 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 - **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** **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 - 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** **DB: Verarbeitung einer einzelnen Datei schlägt fehl**
- OCI `eingang/`: Datei bleibt in `eingang/<zip-name>/` (Rollback) - 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: 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? ### Warum ist das wichtig?
APEX Automation ruft `p_process_incoming_ba_data` in jedem Stundenlauf einmal direkt auf 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: 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 2. APEX Schritt 1 findet den Marker → importiert Daten
3. Quarkus wiederholt den Lauf, ruft ORDS auf → zweiter Import derselben 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, 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. nicht mehr an — `listZipFiles()` gibt nur `.zip`-Dateien zurück.
### Einzig verbleibender manueller Fehlerfall ### Einzig verbleibender manueller Fehlerfall
Schlägt der Marker-Upload fehl (nach erfolgreichem SFTP-Rename), ist der Zustand eindeutig Schlägt der Marker-Upload fehl (nach erfolgreichem SFTP-Delete), ist der Zustand eindeutig
erkennbar: `.processed` auf SFTP, Dateien in OCI ohne Marker. Manueller Fix: Marker-Datei 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 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.
--- ---