create or replace package body pck_auto_import as c_log_module constant lg_app_log.log_module%type := 'AUTOMATISCHER_BA_IMPORT'; 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 -- 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. ------------------------------------------------------------------------------------------------------ -- Parameter: — ------------------------------------------------------------------------------------------------------ -- MA Datum Änderung -- SCK 2026-04-21 Prozedur erstellt ------------------------------------------------------------------------------------------------Kopf*/ is l_service_url varchar2(4000 char); l_api_key varchar2(500 char); l_response clob; l_http_status number; l_log_action constant varchar2(512 char) := 'BA_KORRESPONDENZEN_DATEIEINGANG_AUTOMATION'; begin -- Schritt 1: Offene Batches in OCI verarbeiten p_process_incoming_ba_data; -- Schritt 2: Quarkus anstoßen — Fehler werden geloggt, nicht eskaliert begin l_service_url := pck_system.f_get_par_wert_by_programmid('AUTOMATON_BASE_URL') || '/api/process-incoming'; l_api_key := pck_system.f_get_par_wert_by_programmid('AUTOMATON_API_KEY'); apex_web_service.g_request_headers.delete; apex_web_service.g_request_headers(1).name := 'X-Api-Key'; apex_web_service.g_request_headers(1).value := l_api_key; l_response := apex_web_service.make_rest_request( p_url => l_service_url ,p_http_method => 'POST' ); l_http_status := apex_web_service.g_status_code; case l_http_status when 202 then pck_log.p_info( i_module => c_log_module ,i_action => l_log_action ,i_message => 'Dateieingang Service angestoßen (202 Accepted)' ); when 409 then -- 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 => 'Dateieingang Service läuft bereits (409 Conflict) — kein neuer Lauf gestartet' ); else pck_log.p_warn( i_module => c_log_module ,i_action => l_log_action ,i_message => 'Dateieingang Service: unerwarteter HTTP-Status ' || l_http_status ); end case; exception when others then -- Quarkus-Aufruf fehlgeschlagen: loggen, nicht eskalieren. -- Nächster Stundenlauf führt Schritt 1 erneut aus. pck_log.p_error( i_module => c_log_module ,i_action => l_log_action ,i_message => 'Aufruf des Dateieingang Service fehlgeschlagen: ' || sqlerrm ,i_detail => to_clob(dbms_utility.format_error_backtrace) ); end; end p_run_ba_korrespondenz_dateieingang_automation; procedure p_import_ba_korrespondenz ( i_object_key in varchar2 ,i_content in blob ,i_target_folder in varchar2 ) /*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. ------------------------------------------------------------------------------------------------------ -- 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 ------------------------------------------------------------------------------------------------------ -- 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 ------------------------------------------------------------------------------------------------Kopf*/ is l_filename varchar2(512); l_file_size number; l_return number; l_log_action varchar2(512 char) := 'IMPORT_BA_PDF_FILE'; begin -- Dateiname aus OCI-Objektkey ableiten (letztes Segment nach '/') l_filename := substr(i_object_key, instr(i_object_key, '/', -1) + 1); l_file_size := dbms_lob.getlength(i_content); l_return := inkasso.pck_import.f_import_ba_dokument( i_datei => i_content ,i_dateiname => l_filename ,i_datei_groesse => l_file_size ); if l_return != 1 then 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_object_ref => i_object_key ); -- Wiedervorlage für Sachbearbeiter erstellen pck_wiedervorlage.p_wiedervorlage_anlegen (i_swv_stp_id_art => pck_stammdaten.f_get_stp_id_by_programmid('WV_IMPORT_DATEV') -- TODO: neue WV Art? z.B. WV_IMPORT_BA_DATEN ,i_swv_wdatum => sysdate --TODO: welches Datum? sysdate? ,i_swv_bemerkung => 'Bitte manuell Prüfen: Die BA-Datei "' || l_filename || '" konnte nicht automatisch importiert werden (Siehe "' || i_object_key || '").' ,i_swv_mit_id_wsachbearbeiter => pck_system.f_get_par_wert_by_programmid('BA_IMPORT_SB_MIT_ID') ); raise_application_error(-20000, 'Import fehlgeschlagen: "' || l_filename || '" (Rückgabe: ' || l_return || ')'); end if; -- Datei in Verarbeitet-Ordner verschieben pck_net_storage.p_move_object( i_object_key => i_object_key ,i_target_prefix => i_target_folder ); pck_log.p_info( i_module => c_log_module ,i_action => l_log_action ,i_message => 'Datei "' || l_filename || '" erfolgreich verarbeitet und verschoben' ,i_object_ref => i_object_key ); end p_import_ba_korrespondenz; procedure p_process_incoming_ba_data /*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). -- Wenn danach noch Dateien im Ordner liegen: Sachbearbeiter(SB)-Marker anlegen damit Sachbearbeiter -- die übriggebliebenen Dateien manuell prüfen können. ------------------------------------------------------------------------------------------------------ -- 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 ------------------------------------------------------------------------------------------------Kopf*/ is l_db_processing_marker_key varchar2(1024); l_sb_marker_key varchar2(1024); l_target_folder varchar2(4000 char); l_target_prefix varchar2(4000 char); l_eingang_prefix varchar2(4000 char); l_zip_name varchar2(512); l_file_content blob; l_meta pck_net_storage.t_object_meta; l_folders t_net_storage_tab; l_files t_net_storage_tab; l_has_remaining_files boolean; l_log_action varchar2(512 char) := 'IMPORT_BA_DATA'; begin -- Zielordner Name zusammenstellen l_target_prefix := pck_system.f_get_par_wert_by_programmid('NETSTORE_BA_PREFIX') || 'Verarbeitet ' || to_char(sysdate, 'YYYY') || '/'; l_eingang_prefix := pck_system.f_get_par_wert_by_programmid('NETSTORE_BA_PREFIX') || pck_system.f_get_par_wert_by_programmid('NETSTORE_BA_IMPORT'); -- 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' ); for rec_folder in (select object_key, is_folder from table(l_folders)) loop -- Nur Unterordner verarbeiten if rec_folder.is_folder != 'Y' then continue; end if; -- 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 l_db_processing_marker_key := rec_folder.object_key || pck_system.f_get_par_wert_by_programmid('NETSTORE_MARKER_DB'); -- 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 when others then if sqlcode = -20001 then continue; end if; raise; end; -- Zip-Namen aus Ordnerpfad ableiten: eingang// → l_zip_name := substr( rec_folder.object_key ,length(l_eingang_prefix) + 1 ,length(rec_folder.object_key) - length(l_eingang_prefix) - 1 ); l_target_folder := l_target_prefix || l_zip_name || '/'; -- Alle Dateien im Unterordner auflisten (inkl. Unterordner = alle Tiefen) l_files := pck_net_storage.f_list_objects( i_parent_folder => rec_folder.object_key ,i_include_subfolders => 'Y' ); for rec_file in (select object_key, is_folder from table(l_files)) loop -- Marker und Pseudo-Ordner überspringen if rec_file.object_key = l_db_processing_marker_key or rec_file.is_folder = 'Y' then continue; end if; begin -- 1. Dateiinhalt laden l_file_content := pck_net_storage.f_download_object(rec_file.object_key); -- 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; exception when others then rollback; pck_log.p_error( i_module => c_log_module ,i_action => 'IMPORT_FILE' ,i_message => 'Fehler bei Dateiverarbeitung: ' || sqlerrm ,i_detail => to_clob(dbms_utility.format_error_backtrace) ,i_object_ref => rec_file.object_key ); end; end loop; -- DB-Marker immer entfernen — verhindert erneute Verarbeitung beim nächsten Lauf pck_net_storage.p_delete_object(l_db_processing_marker_key); -- 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' ); l_has_remaining_files := false; for rec_remaining in (select object_key, is_folder from table(l_files)) loop if rec_remaining.is_folder != 'Y' then l_has_remaining_files := true; exit; end if; end loop; if l_has_remaining_files then -- 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 ,i_content => empty_blob() ,i_content_type => 'application/octet-stream' ); 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 gesetzt' ,i_object_ref => rec_folder.object_key ); else pck_log.p_info( i_module => c_log_module ,i_action => l_log_action ,i_message => 'Batch abgeschlossen, alle Dateien erfolgreich importiert' ,i_object_ref => rec_folder.object_key ); end if; end loop; end p_process_incoming_ba_data; end pck_auto_import; /