backbone-maintenance 0.7.21

Minimal Backbone Framework module skeleton
Documentation
//! Repository for MaintenanceSchedule entities
//!
//! Originally generated by metaphor-schema; now **user-owned** — this exact path is declared under
//! `user_owned` in `metaphor.codegen.yaml`, so the generator skips it wholesale. The custom methods
//! below hold the hand-written MaintenanceSchedule SQL (4-layer rule: services orchestrate, repos
//! hold SQL).
//!
//! Thin newtype over `backbone_orm::GenericCrudRepository<MaintenanceSchedule, backbone_orm::SoftDelete>`.
//! All standard CRUD methods are available via `Deref`.

use anyhow::Result;
use chrono::NaiveDate;
use sqlx::PgPool;
use uuid::Uuid;

use backbone_orm::org_scope;

use crate::domain::entity::MaintenanceSchedule;

/// Table name for MaintenanceSchedule entities
pub const TABLE_NAME: &str = "maintenance.maintenance_schedules";

/// Repository for MaintenanceSchedule entities.
///
/// All standard CRUD, soft-delete, pagination, and bulk methods are
/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
pub struct MaintenanceScheduleRepository(
    backbone_orm::GenericCrudRepository<MaintenanceSchedule, backbone_orm::SoftDelete>,
);

impl std::ops::Deref for MaintenanceScheduleRepository {
    type Target = backbone_orm::GenericCrudRepository<MaintenanceSchedule, backbone_orm::SoftDelete>;
    fn deref(&self) -> &Self::Target { &self.0 }
}

impl MaintenanceScheduleRepository {
    /// Create a new repository instance.
    pub fn new(pool: PgPool) -> Self {
        Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
    }
}

/// The exact row a preventive-schedule definition writes.
///
/// Mirrors the raw column shape rather than the `MaintenanceSchedule` entity.
pub struct NewScheduleRow<'a> {
    pub id: Uuid,
    pub asset_id: Uuid,
    pub name: &'a str,
    pub interval_days: i32,
    pub next_due_date: NaiveDate,
}

/// Hand-written MaintenanceSchedule SQL. Lives here (not in the write service) per the module's
/// 4-layer rule: services orchestrate and own the unit of work, repositories hold the SQL.
impl MaintenanceScheduleRepository {
    /// Define a preventive maintenance schedule for an asset.
    ///
    /// Runs outside a transaction on the pool via `org_scope::execute_scoped`, which binds the
    /// ambient org scope (the composing service sets it per request) so the INSERT passes the
    /// decorator's WITH CHECK fence; the decorator's fill trigger stamps the acting unit
    /// (ADR-0029). Undecorated (module tests) the insert runs plain.
    pub async fn insert_schedule(
        &self,
        pool: &PgPool,
        s: &NewScheduleRow<'_>,
    ) -> Result<(), sqlx::Error> {
        org_scope::execute_scoped(
            pool,
            sqlx::query(
                r#"INSERT INTO maintenance.maintenance_schedules
                     (id, asset_id, name, interval_days, next_due_date, status)
                   VALUES ($1,$2,$3,$4,$5,'active')"#,
            )
            .bind(s.id).bind(s.asset_id).bind(s.name)
            .bind(s.interval_days).bind(s.next_due_date),
        )
        .await?;
        Ok(())
    }

    /// Advance a preventive schedule so the next visit becomes due at `performed_date + interval_days`
    /// — the recurrence that makes a preventive plan preventive.
    ///
    /// Takes the CALLER'S connection so it commits as one unit with the visit completion that gates it
    /// (at most once, via that completion's CAS). The caller has already relayed the ambient org
    /// scope onto it — don't re-bind here.
    pub async fn advance_next_due(
        &self,
        conn: &mut sqlx::PgConnection,
        schedule_id: Uuid,
        performed_date: NaiveDate,
    ) -> Result<(), sqlx::Error> {
        sqlx::query(
            r#"UPDATE maintenance.maintenance_schedules
               SET next_due_date = ($2::date + make_interval(days => interval_days))::date
               WHERE id=$1 AND status='active' AND (metadata->>'deleted_at') IS NULL"#,
        )
        .bind(schedule_id).bind(performed_date)
        .execute(conn)
        .await?;
        Ok(())
    }
}

backbone_core::impl_crud_repository!(MaintenanceScheduleRepository, MaintenanceSchedule, soft_delete);