feat(admin): GET /admin/belege/{belegnummer} - volle Lieferdetails

Neuer Admin-Endpunkt, der zu einer ERP-Belegnummer das komplette Detail-Paket
einer Lieferung liefert (Kopf, Positionen mit Scan-Ständen, Kunde +
Ansprechpartner, referenzierte Artikel/Lager, Notizen, Geld-Gutschrift,
Dienstleistungswerte, Kontakte). Jede Lieferung, unabhaengig vom Status;
404 bei unbekannter Belegnummer.

Umsetzung: TourRepository::find_tour_id_by_belegnummer loest die Belegnummer
auf ihre tour_id auf; der Use Case laedt das bestehende Tour-Aggregat und
destilliert daraus in-memory die eine Lieferung samt referenzierter Stammdaten
(neues DTO DeliveryDetails). Kein neuer grosser Query.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-07-09 23:42:57 +02:00
parent 7c6d883d44
commit af034250e7
10 changed files with 234 additions and 2 deletions

View File

@ -38,7 +38,8 @@ use holzleitner_application::usecases::{
DeleteDeliveryNoteUseCase, DeleteDeliveryServiceUseCase, DeletePaymentMethodUseCase,
DeleteServiceUseCase, DevResetDeliveryUseCase, DevResyncToursUseCase,
GenerateDeliveryReportUseCase, GetAccountUseCase,
GetAttachmentPreviewUseCase, GetPositionsModifiedUseCase, GetTourUseCase,
GetAttachmentPreviewUseCase, GetDeliveryDetailsUseCase, GetPositionsModifiedUseCase,
GetTourUseCase,
ImportErpToursUseCase, ListCompletedDeliveriesUseCase, ListDeliveredBelegnummernUseCase,
ListMyCarsUseCase,
ListMyToursTodayUseCase, ListPaymentMethodsUseCase,
@ -294,6 +295,9 @@ pub(crate) async fn run_app(
// --- Use Cases -----------------------------------------------------
let get_account = Arc::new(GetAccountUseCase::new(account_repository));
let get_tour = Arc::new(GetTourUseCase::new(tour_repository.clone()));
// Admin: volles Detail-Paket zu einer Belegnummer (nutzt das Tour-Aggregat).
let get_delivery_details =
Arc::new(GetDeliveryDetailsUseCase::new(tour_repository.clone()));
let list_my_tours_today = Arc::new(ListMyToursTodayUseCase::new(
tour_repository.clone(),
cfg.dev.today_override,
@ -473,6 +477,7 @@ pub(crate) async fn run_app(
list_delivered_belegnummern,
list_completed_deliveries,
get_positions_modified,
get_delivery_details,
mark_mail_sent,
list_pending_reviews,
resolve_review,

View File

@ -56,6 +56,7 @@ use utoipa::openapi::security::{
crate::routes::admin::delivered_belegnummern,
crate::routes::admin::completed_deliveries,
crate::routes::admin::positions_modified,
crate::routes::admin::belege_details,
crate::routes::admin::mark_mail_sent,
crate::routes::admin::list_reviews,
crate::routes::admin::resolve_review,
@ -87,6 +88,7 @@ use utoipa::openapi::security::{
holzleitner_domain::DeliveryServiceValue,
holzleitner_domain::Warehouse,
holzleitner_application::dto::TourDetails,
holzleitner_application::dto::DeliveryDetails,
holzleitner_application::dto::DeliveryWithItems,
holzleitner_application::dto::TourSummary,
holzleitner_application::dto::SyncTourRequest,

View File

@ -15,6 +15,7 @@ use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
use holzleitner_application::dto::DeliveryDetails;
use holzleitner_application::error::ApplicationError;
use holzleitner_application::usecases::ImportSummary;
@ -37,6 +38,7 @@ pub fn router() -> Router<AppState> {
"/admin/belege/{belegnummer}/positions-modified",
get(positions_modified),
)
.route("/admin/belege/{belegnummer}", get(belege_details))
.route("/admin/mark-mail-sent", post(mark_mail_sent))
.route("/admin/reviews", get(list_reviews))
.route("/admin/reviews/{delivery_id}/resolve", post(resolve_review))
@ -348,6 +350,34 @@ pub async fn positions_modified(
}))
}
/// Liefert das **volle Detail-Paket** zu einer ERP-Belegnummer: die Lieferung
/// mit Positionen (inkl. Scan-Ständen), Kunde + Ansprechpartner, referenzierte
/// Artikel/Lager, Notizen, Geld-Gutschrift, Dienstleistungswerte und Kontakte —
/// destilliert aus dem Tour-Aggregat. Jede Lieferung, unabhängig vom Status.
/// `404`, wenn die Belegnummer unbekannt ist.
#[utoipa::path(
get,
path = "/admin/belege/{belegnummer}",
tag = "admin",
params(
("belegnummer" = String, Path, description = "ERP-Belegnummer, z. B. V-30690291")
),
responses(
(status = 200, description = "Volle Lieferdetails", body = DeliveryDetails),
(status = 401, description = "Admin-API-Key fehlt/ungültig"),
(status = 404, description = "Belegnummer unbekannt")
),
security(("admin_api_key" = []))
)]
pub async fn belege_details(
State(state): State<AppState>,
Path(belegnummer): Path<String>,
) -> Result<Json<DeliveryDetails>, ApiError> {
tracing::info!(%belegnummer, "admin.belege_details");
let details = state.get_delivery_details.execute(&belegnummer).await?;
Ok(Json(details))
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct MarkMailSentRequest {
/// Belegnummern, deren Liefermail erfolgreich versendet wurde und die als

View File

@ -8,7 +8,8 @@ use holzleitner_application::usecases::{
DeleteDeliveryNoteUseCase, DeleteDeliveryServiceUseCase, DeletePaymentMethodUseCase,
DeleteServiceUseCase, DevResetDeliveryUseCase, DevResyncToursUseCase,
GenerateDeliveryReportUseCase, GetAccountUseCase,
GetAttachmentPreviewUseCase, GetPositionsModifiedUseCase, GetTourUseCase,
GetAttachmentPreviewUseCase, GetDeliveryDetailsUseCase, GetPositionsModifiedUseCase,
GetTourUseCase,
ImportErpToursUseCase, ListCompletedDeliveriesUseCase, ListDeliveredBelegnummernUseCase,
ListMyCarsUseCase,
ListMyToursTodayUseCase, ListPaymentMethodsUseCase,
@ -56,6 +57,8 @@ pub struct AppState {
pub list_completed_deliveries: Arc<ListCompletedDeliveriesUseCase>,
/// Admin: Positions-Änderungs-Flag zu einer einzelnen Belegnummer.
pub get_positions_modified: Arc<GetPositionsModifiedUseCase>,
/// Admin: volles Detail-Paket zu einer Belegnummer.
pub get_delivery_details: Arc<GetDeliveryDetailsUseCase>,
/// Admin: Liefermails von Belegnummern als versendet markieren (Dedup).
pub mark_mail_sent: Arc<MarkMailSentUseCase>,
pub list_pending_reviews: Arc<ListPendingReviewsUseCase>,