From 54ed6193968b8f6bed836af6212bca055b85bee9 Mon Sep 17 00:00:00 2001 From: Dennis Nemec Date: Wed, 9 Sep 2026 00:44:26 +0200 Subject: [PATCH] feat(report): Kundennummer an DOCUframe-Makro _SV_assignDeliveryReport uebergeben Das Makro ordnet den Lieferbericht dem Vorgang des Kunden zu und braucht dafuer jetzt zusaetzlich die im ERP hinterlegte Kundennummer: { objectId, belegnummer, kundennummer } - AssignReportRequest + Gateway-Port um kundennummer (String) erweitert - AttachmentRepository.delivery_customer_number(): liest customers.erp_customer_id (= ERP Kunden.Kundennummer) ueber deliveries.customer_id - ProcessDeliveryReportUseCase liest die Nummer aus der DB (nicht aus dem Render-Ergebnis), damit sie auch im Retry-Pfad ohne erneutes Rendern vorliegt Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/ports/attachment_repository.rs | 9 +++++++++ .../src/ports/docuframe_report_gateway.rs | 9 ++++++--- .../src/usecases/process_delivery_report.rs | 16 ++++++++++++++-- crates/infrastructure/src/gsd/dto.rs | 6 ++++++ crates/infrastructure/src/gsd/service.rs | 2 ++ .../src/persistence/attachment_repository.rs | 17 +++++++++++++++++ 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/crates/application/src/ports/attachment_repository.rs b/crates/application/src/ports/attachment_repository.rs index f0b9729..783abd3 100644 --- a/crates/application/src/ports/attachment_repository.rs +++ b/crates/application/src/ports/attachment_repository.rs @@ -58,6 +58,15 @@ pub trait AttachmentRepository: Send + Sync { delivery_id: Uuid, ) -> Result, ApplicationError>; + /// Liefert die ERP-Kundennummer (`customers.erp_customer_id`) des Kunden + /// der Lieferung. Wird u. a. dem DOCUframe-Makro + /// `_SV_assignDeliveryReport` als `kundennummer` mitgegeben. `None`, wenn + /// die Lieferung nicht (mehr) existiert. + async fn delivery_customer_number( + &self, + delivery_id: Uuid, + ) -> Result, ApplicationError>; + /// Listet alle noch nicht gelöschten Attachments einer Lieferung /// (`deleted_at IS NULL`) mit ihrer lokalen Referenz — fürs Aufräumen. async fn list_active_for_delivery( diff --git a/crates/application/src/ports/docuframe_report_gateway.rs b/crates/application/src/ports/docuframe_report_gateway.rs index 5041d39..7a29a41 100644 --- a/crates/application/src/ports/docuframe_report_gateway.rs +++ b/crates/application/src/ports/docuframe_report_gateway.rs @@ -22,12 +22,15 @@ pub trait DocuframeReportGateway: Send + Sync { pdf: Vec, ) -> Result; - /// Ruft das Makro `_SV_assignDeliveryReport` mit `{objectId, belegnummer}` - /// auf und ordnet den hochgeladenen Report dem Beleg zu. Fehler, wenn das - /// Makro `succeeded != true` liefert oder DOCUframe nicht erreichbar ist. + /// Ruft das Makro `_SV_assignDeliveryReport` mit + /// `{objectId, belegnummer, kundennummer}` auf und ordnet den hochgeladenen + /// Report dem Beleg bzw. dem Vorgang des Kunden zu. `kundennummer` ist die + /// im ERP hinterlegte Kundennummer (als String). Fehler, wenn das Makro + /// `succeeded != true` liefert oder DOCUframe nicht erreichbar ist. async fn assign_report( &self, object_id: &str, belegnummer: &str, + kundennummer: &str, ) -> Result<(), ApplicationError>; } diff --git a/crates/application/src/usecases/process_delivery_report.rs b/crates/application/src/usecases/process_delivery_report.rs index 63472ba..eb0e6ff 100644 --- a/crates/application/src/usecases/process_delivery_report.rs +++ b/crates/application/src/usecases/process_delivery_report.rs @@ -92,8 +92,20 @@ impl ProcessDeliveryReportUseCase { } }; - // Schritt 3: Makro-Zuordnung (muss succeeded == true liefern). - self.gateway.assign_report(&object_id, &belegnummer).await?; + // Schritt 3: Makro-Zuordnung (muss succeeded == true liefern). Das + // Makro braucht zusätzlich die ERP-Kundennummer, um den Report dem + // Vorgang des Kunden zuzuordnen. Bewusst aus der DB gelesen (nicht aus + // dem Render-Ergebnis), damit sie auch im Retry-Pfad vorliegt, in dem + // das Rendern übersprungen wird. + let kundennummer = self + .attachment_repo + .delivery_customer_number(delivery_id) + .await? + .ok_or(ApplicationError::NotFound)? + .to_string(); + self.gateway + .assign_report(&object_id, &belegnummer, &kundennummer) + .await?; // Schritt 4: erst aufräumen, dann als erledigt markieren. self.cleanup_local(delivery_id, &belegnummer).await; diff --git a/crates/infrastructure/src/gsd/dto.rs b/crates/infrastructure/src/gsd/dto.rs index 6db65e9..1aac216 100644 --- a/crates/infrastructure/src/gsd/dto.rs +++ b/crates/infrastructure/src/gsd/dto.rs @@ -92,9 +92,15 @@ pub struct CommitData { // ─── Makro-Aufruf (`/v1/execute/`) ──────────────────────────────────── /// Request-Body für das Makro `_SV_assignDeliveryReport`. +/// +/// Beispiel: `{ "objectId": "1EBGJM", "belegnummer": "V-30690285", +/// "kundennummer": "123456" }`. #[derive(Debug, Serialize)] pub struct AssignReportRequest<'a> { #[serde(rename = "objectId")] pub object_id: &'a str, pub belegnummer: &'a str, + /// Im ERP hinterlegte Kundennummer des Kunden (String, wie vom Makro + /// erwartet) — damit DOCUframe den Report dem Vorgang des Kunden zuordnet. + pub kundennummer: &'a str, } diff --git a/crates/infrastructure/src/gsd/service.rs b/crates/infrastructure/src/gsd/service.rs index fc39be3..7eee3e4 100644 --- a/crates/infrastructure/src/gsd/service.rs +++ b/crates/infrastructure/src/gsd/service.rs @@ -497,10 +497,12 @@ impl DocuframeReportGateway for GsdService { &self, object_id: &str, belegnummer: &str, + kundennummer: &str, ) -> Result<(), ApplicationError> { let body = serde_json::to_value(super::dto::AssignReportRequest { object_id, belegnummer, + kundennummer, }) .map_err(Self::ext)?; let val = self diff --git a/crates/infrastructure/src/persistence/attachment_repository.rs b/crates/infrastructure/src/persistence/attachment_repository.rs index b2dda55..82b0ba7 100644 --- a/crates/infrastructure/src/persistence/attachment_repository.rs +++ b/crates/infrastructure/src/persistence/attachment_repository.rs @@ -78,6 +78,23 @@ impl AttachmentRepository for PgAttachmentRepository { Ok(belegnummer) } + async fn delivery_customer_number( + &self, + delivery_id: Uuid, + ) -> Result, ApplicationError> { + let customer_number: Option = sqlx::query_scalar( + "SELECT c.erp_customer_id \ + FROM deliveries d \ + JOIN customers c ON c.id = d.customer_id \ + WHERE d.id = $1", + ) + .bind(delivery_id) + .fetch_optional(&self.pool) + .await + .map_err(db)?; + Ok(customer_number) + } + async fn list_active_for_delivery( &self, delivery_id: Uuid,