Files
Holzleitner---Backend--aktu…/crates/infrastructure/src/persistence/attachment_repository.rs
Dennis Nemec 54ed619396 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) <noreply@anthropic.com>
2026-09-09 00:44:26 +02:00

125 lines
3.6 KiB
Rust

//! Postgres-Implementierung des `AttachmentRepository`-Ports.
use async_trait::async_trait;
use sqlx::PgPool;
use uuid::Uuid;
use holzleitner_application::error::ApplicationError;
use holzleitner_application::ports::{
AttachmentLocalRef, AttachmentRef, AttachmentRepository, NewAttachment,
};
pub struct PgAttachmentRepository {
pool: PgPool,
}
impl PgAttachmentRepository {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
}
fn db<E: std::fmt::Display>(e: E) -> ApplicationError {
ApplicationError::Repository(e.to_string())
}
#[async_trait]
impl AttachmentRepository for PgAttachmentRepository {
async fn create(&self, attachment: NewAttachment) -> Result<Uuid, ApplicationError> {
let id: Uuid = sqlx::query_scalar(
r#"
INSERT INTO attachments (
docuframe_object_id, mime_type, size_bytes, filename,
checksum_sha256, width, height, uploaded_by, delivery_id
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id
"#,
)
.bind(attachment.docuframe_object_id)
.bind(attachment.mime_type)
.bind(attachment.size_bytes)
.bind(attachment.filename)
.bind(attachment.checksum_sha256)
.bind(attachment.width)
.bind(attachment.height)
.bind(attachment.uploaded_by)
.bind(attachment.delivery_id)
.fetch_one(&self.pool)
.await
.map_err(db)?;
Ok(id)
}
async fn get(&self, id: Uuid) -> Result<Option<AttachmentRef>, ApplicationError> {
let row: Option<(String, String)> = sqlx::query_as(
"SELECT docuframe_object_id, mime_type FROM attachments WHERE id = $1",
)
.bind(id)
.fetch_optional(&self.pool)
.await
.map_err(db)?;
Ok(row.map(|(docuframe_object_id, mime_type)| AttachmentRef {
docuframe_object_id,
mime_type,
}))
}
async fn delivery_belegnummer(
&self,
delivery_id: Uuid,
) -> Result<Option<String>, ApplicationError> {
let belegnummer: Option<String> = sqlx::query_scalar(
"SELECT erp_belegnummer FROM deliveries WHERE id = $1",
)
.bind(delivery_id)
.fetch_optional(&self.pool)
.await
.map_err(db)?;
Ok(belegnummer)
}
async fn delivery_customer_number(
&self,
delivery_id: Uuid,
) -> Result<Option<i64>, ApplicationError> {
let customer_number: Option<i64> = 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,
) -> Result<Vec<AttachmentLocalRef>, ApplicationError> {
let rows: Vec<(Uuid, String)> = sqlx::query_as(
"SELECT id, docuframe_object_id FROM attachments \
WHERE delivery_id = $1 AND deleted_at IS NULL",
)
.bind(delivery_id)
.fetch_all(&self.pool)
.await
.map_err(db)?;
Ok(rows
.into_iter()
.map(|(id, reference)| AttachmentLocalRef { id, reference })
.collect())
}
async fn mark_deleted(&self, id: Uuid) -> Result<(), ApplicationError> {
sqlx::query("UPDATE attachments SET deleted_at = now() WHERE id = $1")
.bind(id)
.execute(&self.pool)
.await
.map_err(db)?;
Ok(())
}
}