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>
125 lines
3.6 KiB
Rust
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(())
|
|
}
|
|
}
|