diff --git a/crates/api/src/config.rs b/crates/api/src/config.rs index f8ee9a6..327bbd5 100644 --- a/crates/api/src/config.rs +++ b/crates/api/src/config.rs @@ -383,6 +383,8 @@ pub struct ServerConfig { /// Zeitstempel werden durchgängig als UTC gespeichert (`TIMESTAMPTZ`, /// `DateTime`). Diese Zone gilt für alles, was ein **Kalenderdatum** /// oder eine **Wanduhrzeit** braucht: „heute" für Touren/Import-Cron, + /// die **Auslösezeiten aller Cron-Jobs** (Import, Report-Retry, + /// Signatur-Cleanup — `"0 0 17 * * *"` = 17:00 in dieser Zone, nicht UTC), /// Datumsangaben im Lieferbericht und in der Belegansicht sowie die /// ERP-Rückschreibung (`delivered_at`). Bewusst konfiguriert statt der /// OS-Zeitzone des Servers (`chrono::Local`), damit das Verhalten nicht diff --git a/crates/api/src/main.rs b/crates/api/src/main.rs index bcca54f..d7ddad5 100644 --- a/crates/api/src/main.rs +++ b/crates/api/src/main.rs @@ -561,7 +561,10 @@ pub(crate) async fn run_app( let import = import_erp_tours.clone(); let offset = cfg.import.date_offset_days; let tz = cfg.server.timezone; - let job = Job::new_async(cfg.import.cron.as_str(), move |_uuid, _lock| { + // `new_async_tz`: Cron-Ausdruck in der konfigurierten Zone auswerten. + // `new_async` würde ihn in UTC interpretieren — "0 0 17 * * *" feuerte + // dann erst um 19:00 deutscher Zeit. + let job = Job::new_async_tz(cfg.import.cron.as_str(), tz, move |_uuid, _lock| { let import = import.clone(); Box::pin(async move { // Kalendertag in der konfigurierten Zone (nicht OS-Local). @@ -648,7 +651,10 @@ pub(crate) async fn run_app( .context("Report-JobScheduler konnte nicht erstellt werden")?; let process = process_delivery_report.clone(); let jobs = report_job_repository.clone(); - let job = Job::new_async(cfg.report.retry_cron.as_str(), move |_uuid, _lock| { + let job = Job::new_async_tz( + cfg.report.retry_cron.as_str(), + cfg.server.timezone, + move |_uuid, _lock| { let process = process.clone(); let jobs = jobs.clone(); Box::pin(async move { @@ -687,7 +693,10 @@ pub(crate) async fn run_app( .context("Signatur-Cleanup-JobScheduler konnte nicht erstellt werden")?; let signatures = signature_storage.clone(); let max_age = Duration::from_secs(cfg.signature.retention_days as u64 * 86_400); - let job = Job::new_async(cfg.signature.cleanup_cron.as_str(), move |_uuid, _lock| { + let job = Job::new_async_tz( + cfg.signature.cleanup_cron.as_str(), + cfg.server.timezone, + move |_uuid, _lock| { let signatures = signatures.clone(); Box::pin(async move { match signatures.delete_older_than(max_age).await {