feat(import): ERP-Sync in DB dokumentieren + Startup-Catch-up
Jeder ERPframe-Import (Scheduler, Startup, manuell) wird in der neuen Tabelle erp_sync_runs protokolliert (target_date, trigger, success, Zaehler, Fehler). Beim Serverstart synct das Backend das Zieldatum (heute + offset, i.d.R. morgen) nach, falls dafuer noch kein erfolgreicher Lauf dokumentiert ist — deckt Erststart UND laengere Unterbrechungen ab, bei denen der Cron-Zeitpunkt verpasst wurde. Gated ueber [import] enabled. - Migration 0030_erp_sync_runs - Port SyncRunRepository (+ SyncRunRecord, SyncTrigger) - Adapter PgSyncRunRepository - ImportErpToursUseCase dokumentiert jeden Lauf; neues execute_with(date, trigger) - main.rs: Repo verdrahtet, Scheduler-Trigger, Startup-Catch-up (tokio::spawn) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -17,6 +17,7 @@ pub mod payment_method_repository;
|
||||
pub mod pool;
|
||||
pub mod scan_repository;
|
||||
pub mod service_repository;
|
||||
pub mod sync_run_repository;
|
||||
pub mod tour_repository;
|
||||
|
||||
pub use account_repository::PgAccountRepository;
|
||||
@ -32,4 +33,5 @@ pub use payment_method_repository::PgPaymentMethodRepository;
|
||||
pub use pool::{connect_and_migrate, PoolConfig};
|
||||
pub use scan_repository::PgScanRepository;
|
||||
pub use service_repository::PgServiceRepository;
|
||||
pub use sync_run_repository::PgSyncRunRepository;
|
||||
pub use tour_repository::PgTourRepository;
|
||||
|
||||
60
crates/infrastructure/src/persistence/sync_run_repository.rs
Normal file
60
crates/infrastructure/src/persistence/sync_run_repository.rs
Normal file
@ -0,0 +1,60 @@
|
||||
//! Postgres-Implementierung von `SyncRunRepository` (Tabelle `erp_sync_runs`).
|
||||
//!
|
||||
//! Reines Append-/Audit-Log der ERP-Import-Läufe + die Catch-up-Abfrage
|
||||
//! „gibt es schon einen erfolgreichen Sync für Datum X?".
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::NaiveDate;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use holzleitner_application::error::ApplicationError;
|
||||
use holzleitner_application::ports::{SyncRunRecord, SyncRunRepository};
|
||||
|
||||
pub struct PgSyncRunRepository {
|
||||
pool: PgPool,
|
||||
}
|
||||
|
||||
impl PgSyncRunRepository {
|
||||
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 SyncRunRepository for PgSyncRunRepository {
|
||||
async fn record(&self, run: SyncRunRecord) -> Result<(), ApplicationError> {
|
||||
sqlx::query(
|
||||
"INSERT INTO erp_sync_runs \
|
||||
(target_date, trigger, success, tours_total, tours_ok, tours_failed, \
|
||||
drivers_provisioned, error) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
)
|
||||
.bind(run.target_date)
|
||||
.bind(run.trigger.as_str())
|
||||
.bind(run.success)
|
||||
.bind(run.tours_total)
|
||||
.bind(run.tours_ok)
|
||||
.bind(run.tours_failed)
|
||||
.bind(run.drivers_provisioned)
|
||||
.bind(run.error)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn has_successful_run_for(&self, date: NaiveDate) -> Result<bool, ApplicationError> {
|
||||
let exists: bool = sqlx::query_scalar(
|
||||
"SELECT EXISTS(SELECT 1 FROM erp_sync_runs WHERE target_date = $1 AND success)",
|
||||
)
|
||||
.bind(date)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(db)?;
|
||||
Ok(exists)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user