backbone-maintenance 0.7.21

Minimal Backbone Framework module skeleton
Documentation
//! Repository for MaintenanceVisitPart 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 MaintenanceVisitPart SQL (4-layer rule: services orchestrate, repos
//! hold SQL).
//!
//! Thin newtype over `backbone_orm::GenericCrudRepository<MaintenanceVisitPart, backbone_orm::SoftDelete>`.
//! All standard CRUD methods are available via `Deref`.

use anyhow::Result;
use rust_decimal::Decimal;
use sqlx::{PgPool, Row};
use uuid::Uuid;

use backbone_orm::{company_scope, org_scope};

use crate::domain::entity::MaintenanceVisitPart;

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

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

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

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

/// A part line as the completion path reads it — quantity only; it is valued on completion.
pub struct VisitPartRow {
    pub id: Uuid,
    pub item_id: Uuid,
    pub quantity: Decimal,
}

/// Hand-written MaintenanceVisitPart 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 MaintenanceVisitPartRepository {
    /// Add a part line to a visit (quantity only; `unit_cost`/`amount` are valued on completion).
    ///
    /// Runs 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). The visit-status
    /// check the caller makes first already fences the line to a visit it may see. Undecorated
    /// (module tests) the insert runs plain.
    pub async fn insert_part(
        &self,
        pool: &PgPool,
        id: Uuid,
        visit_id: Uuid,
        item_id: Uuid,
        quantity: Decimal,
    ) -> Result<(), sqlx::Error> {
        org_scope::execute_scoped(
            pool,
            sqlx::query(
                r#"INSERT INTO maintenance.maintenance_visit_parts
                       (id, visit_id, item_id, quantity, unit_cost, amount)
                   VALUES ($1,$2,$3,$4,0,0)"#,
            )
            .bind(id).bind(visit_id).bind(item_id).bind(quantity),
        )
        .await?;
        Ok(())
    }

    /// The visit's frozen part set (caller-scoped — rides the org-bound request connection).
    pub async fn list_for_visit(
        &self,
        pool: &PgPool,
        visit_id: Uuid,
    ) -> Result<Vec<VisitPartRow>, sqlx::Error> {
        let rows = company_scope::fetch_all_rows_scoped(
            pool,
            sqlx::query(
                "SELECT id, item_id, quantity FROM maintenance.maintenance_visit_parts WHERE visit_id=$1",
            )
            .bind(visit_id),
        )
        .await?;
        Ok(rows
            .iter()
            .map(|r| VisitPartRow {
                id: r.get("id"), item_id: r.get("item_id"), quantity: r.get("quantity"),
            })
            .collect())
    }

    /// Write back the valuation inventory returned for one line (moving-average rate and extended
    /// value). Runs via `org_scope::execute_scoped` under the ambient org scope; both values are
    /// expected already money-rounded.
    pub async fn set_valuation(
        &self,
        pool: &PgPool,
        visit_id: Uuid,
        item_id: Uuid,
        unit_cost: Decimal,
        amount: Decimal,
    ) -> Result<(), sqlx::Error> {
        org_scope::execute_scoped(
            pool,
            sqlx::query(
                r#"UPDATE maintenance.maintenance_visit_parts SET unit_cost=$3, amount=$4
                   WHERE visit_id=$1 AND item_id=$2"#,
            )
            .bind(visit_id).bind(item_id).bind(unit_cost).bind(amount),
        )
        .await?;
        Ok(())
    }
}

backbone_core::impl_crud_repository!(MaintenanceVisitPartRepository, MaintenanceVisitPart, soft_delete);