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>
76 lines
2.0 KiB
Rust
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())
|
|
}
|
|
})
|
|
}
|