Files
Holzleitner---Backend--aktu…/crates/application/src/usecases/create_delivery_note.rs
Dennis Nemec 6a9b5872e1 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>
2026-06-01 17:52:58 +02:00

76 lines
2.0 KiB
Rust

use std::sync::Arc;
use uuid::Uuid;
use holzleitner_domain::DeliveryNote;
use crate::dto::CreateDeliveryNoteRequest;
use crate::error::ApplicationError;
use crate::ports::{CarRepository, DeliveryNoteRepository};
/// Legt eine neue Notiz an einer Lieferung an.
///
/// Validierung:
/// * mindestens eines von `text` (nicht-leer nach trim) und
/// `image_attachment` (nicht-leer nach trim) muss gesetzt sein.
/// * `author_car_id` muss — falls gesetzt — zum angemeldeten Account gehören.
pub struct CreateDeliveryNoteUseCase {
repository: Arc<dyn DeliveryNoteRepository>,
cars: Arc<dyn CarRepository>,
}
impl CreateDeliveryNoteUseCase {
pub fn new(
repository: Arc<dyn DeliveryNoteRepository>,
cars: Arc<dyn CarRepository>,
) -> Self {
Self { repository, cars }
}
pub async fn execute(
&self,
delivery_id: Uuid,
author_personalnummer: i64,
request: CreateDeliveryNoteRequest,
) -> Result<DeliveryNote, ApplicationError> {
let text = clean(request.text);
let image = clean(request.image_attachment);
if text.is_none() && image.is_none() {
return Err(ApplicationError::Validation(
"notiz braucht text oder image_attachment".into(),
));
}
if let Some(car_id) = request.author_car_id {
self.cars
.assert_owned_by_account(&[car_id], author_personalnummer)
.await?;
}
self.repository
.create(
delivery_id,
author_personalnummer,
request.author_car_id,
text,
image,
request.credit_delivery_item_id,
request.is_amount_credit_note,
)
.await
}
}
/// Trim + leerer-String → None.
fn clean(input: Option<String>) -> Option<String> {
input.and_then(|s| {
let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_owned())
}
})
}