Compare commits

..

4 Commits

8 changed files with 121 additions and 18 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

@@ -121,7 +121,7 @@ create or replace package body pck_auto_import as
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;

View File

@@ -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

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

@@ -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

@@ -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}