backbone_maintenance/infrastructure/persistence/maintenance_schedule_repository.rs
1//! Repository for MaintenanceSchedule entities
2//!
3//! Originally generated by metaphor-schema; now **user-owned** — this exact path is declared under
4//! `user_owned` in `metaphor.codegen.yaml`, so the generator skips it wholesale. The custom methods
5//! below hold the hand-written MaintenanceSchedule SQL (4-layer rule: services orchestrate, repos
6//! hold SQL).
7//!
8//! Thin newtype over `backbone_orm::GenericCrudRepository<MaintenanceSchedule, backbone_orm::SoftDelete>`.
9//! All standard CRUD methods are available via `Deref`.
10
11use anyhow::Result;
12use chrono::NaiveDate;
13use sqlx::PgPool;
14use uuid::Uuid;
15
16use backbone_orm::org_scope;
17
18use crate::domain::entity::MaintenanceSchedule;
19
20/// Table name for MaintenanceSchedule entities
21pub const TABLE_NAME: &str = "maintenance.maintenance_schedules";
22
23/// Repository for MaintenanceSchedule entities.
24///
25/// All standard CRUD, soft-delete, pagination, and bulk methods are
26/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
27pub struct MaintenanceScheduleRepository(
28 backbone_orm::GenericCrudRepository<MaintenanceSchedule, backbone_orm::SoftDelete>,
29);
30
31impl std::ops::Deref for MaintenanceScheduleRepository {
32 type Target = backbone_orm::GenericCrudRepository<MaintenanceSchedule, backbone_orm::SoftDelete>;
33 fn deref(&self) -> &Self::Target { &self.0 }
34}
35
36impl MaintenanceScheduleRepository {
37 /// Create a new repository instance.
38 pub fn new(pool: PgPool) -> Self {
39 Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
40 }
41}
42
43/// The exact row a preventive-schedule definition writes.
44///
45/// Mirrors the raw column shape rather than the `MaintenanceSchedule` entity.
46pub struct NewScheduleRow<'a> {
47 pub id: Uuid,
48 pub asset_id: Uuid,
49 pub name: &'a str,
50 pub interval_days: i32,
51 pub next_due_date: NaiveDate,
52}
53
54/// Hand-written MaintenanceSchedule SQL. Lives here (not in the write service) per the module's
55/// 4-layer rule: services orchestrate and own the unit of work, repositories hold the SQL.
56impl MaintenanceScheduleRepository {
57 /// Define a preventive maintenance schedule for an asset.
58 ///
59 /// Runs outside a transaction on the pool via `org_scope::execute_scoped`, which binds the
60 /// ambient org scope (the composing service sets it per request) so the INSERT passes the
61 /// decorator's WITH CHECK fence; the decorator's fill trigger stamps the acting unit
62 /// (ADR-0029). Undecorated (module tests) the insert runs plain.
63 pub async fn insert_schedule(
64 &self,
65 pool: &PgPool,
66 s: &NewScheduleRow<'_>,
67 ) -> Result<(), sqlx::Error> {
68 org_scope::execute_scoped(
69 pool,
70 sqlx::query(
71 r#"INSERT INTO maintenance.maintenance_schedules
72 (id, asset_id, name, interval_days, next_due_date, status)
73 VALUES ($1,$2,$3,$4,$5,'active')"#,
74 )
75 .bind(s.id).bind(s.asset_id).bind(s.name)
76 .bind(s.interval_days).bind(s.next_due_date),
77 )
78 .await?;
79 Ok(())
80 }
81
82 /// Advance a preventive schedule so the next visit becomes due at `performed_date + interval_days`
83 /// — the recurrence that makes a preventive plan preventive.
84 ///
85 /// Takes the CALLER'S connection so it commits as one unit with the visit completion that gates it
86 /// (at most once, via that completion's CAS). The caller has already relayed the ambient org
87 /// scope onto it — don't re-bind here.
88 pub async fn advance_next_due(
89 &self,
90 conn: &mut sqlx::PgConnection,
91 schedule_id: Uuid,
92 performed_date: NaiveDate,
93 ) -> Result<(), sqlx::Error> {
94 sqlx::query(
95 r#"UPDATE maintenance.maintenance_schedules
96 SET next_due_date = ($2::date + make_interval(days => interval_days))::date
97 WHERE id=$1 AND status='active' AND (metadata->>'deleted_at') IS NULL"#,
98 )
99 .bind(schedule_id).bind(performed_date)
100 .execute(conn)
101 .await?;
102 Ok(())
103 }
104}
105
106backbone_core::impl_crud_repository!(MaintenanceScheduleRepository, MaintenanceSchedule, soft_delete);