Backend-Arbeitsstand: ERP-Sync, Lieferlebenszyklus, Reports + config.toml
Bringt das Backend vom initialen Skeleton auf den aktuellen Arbeitsstand (Clean Architecture: domain → application → infrastructure → api). Wesentliche Bereiche: - ERP-Anbindung (MSSQL-Pull der Touren, Import-Scheduler, Rückschreiben) - Lieferlebenszyklus: Scan/Hold/Cancel/Complete, Gutschriften, Notizen, Bild-Anhänge, Unterschriften, PDF-Lieferreport → DOCUframe - Stammdaten: Kunden, Artikel, Lager, Zahlungsarten, Services - Keycloak-JWT-Gate + Fahrer-Provisionierung via Admin-API - Admin-API-Key-Gate (X-Admin-Api-Key) für Maschinen-Endpunkte Jüngste Änderungen dieser Session: - Belegspezifische Kontaktdaten: alle ERP-Adressen (Beleg-/Liefer-/ Rechnungsadresse, Ansprechpartner, Kundenstamm) mit Telefon/Mobil/ E-Mail werden gesynct (Migration 0029, MSSQL-Query, TourDetails) - Konfiguration von .env (envy/dotenvy) auf config.toml (toml/serde) umgestellt; Vorlage config.example.toml, Pfad via HOLZLEITNER_CONFIG Nicht im Repo (per .gitignore): config.toml (Secrets), data/ (Laufzeit-/ Kundendaten), demo.mp4, .claude/, variocontrol-ai/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
107
crates/infrastructure/src/persistence/attachment_repository.rs
Normal file
107
crates/infrastructure/src/persistence/attachment_repository.rs
Normal file
@ -0,0 +1,107 @@
|
||||
//! 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 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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user