backbone-support 0.12.0

Customer support: issues tracked against service level agreements, and warranty claims
Documentation
//! Repository for WarrantyClaim 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 WarrantyClaim SQL (4-layer rule: services orchestrate, repos hold SQL).
//!
//! Tenancy (ADR-0029): the SQL here carries no tenant key. The scoped-execute helpers ride the
//! request-dedicated connection when the composing service bound one (its fence variables govern
//! what the RLS layer accepts) and fall back to a plain pool execute otherwise.
//!
//! Thin newtype over `backbone_orm::GenericCrudRepository<WarrantyClaim, backbone_orm::SoftDelete>`.
//! All standard CRUD methods are available via `Deref`.

use anyhow::Result;
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use uuid::Uuid;

use backbone_orm::org_scope;

use crate::domain::entity::WarrantyClaim;

/// Table name for WarrantyClaim entities
pub const TABLE_NAME: &str = "support.warranty_claims";

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

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

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

/// The exact row a filed warranty claim writes.
///
/// Mirrors the raw column shape rather than the `WarrantyClaim` entity. Coverage
/// (`is_under_warranty`) is computed by the caller at file time and stored, not re-derived here.
pub struct NewWarrantyClaimRow<'a> {
    pub id: Uuid,
    pub customer_id: Option<Uuid>,
    pub item_id: Uuid,
    pub serial_no: Option<&'a str>,
    pub claim_date: DateTime<Utc>,
    pub warranty_expiry: Option<DateTime<Utc>>,
    pub is_under_warranty: bool,
    pub issue_id: Option<Uuid>,
    pub description: Option<&'a str>,
}

/// Hand-written WarrantyClaim SQL. Lives here (not in the write service) per the module's 4-layer rule.
impl WarrantyClaimRepository {
    /// File a warranty claim.
    ///
    /// A write outside any transaction: takes the pool and runs the scoped helper so it rides the
    /// request-dedicated connection when the composing service bound a scope — under a decorated
    /// deployment the fence's WITH CHECK governs the row; with no scope bound this is a plain insert.
    pub async fn insert_claim(
        &self,
        pool: &PgPool,
        c: &NewWarrantyClaimRow<'_>,
    ) -> Result<(), sqlx::Error> {
        org_scope::execute_scoped(
            pool,
            sqlx::query(
                r#"INSERT INTO support.warranty_claims
                     (id, customer_id, item_id, serial_no, claim_date, warranty_expiry,
                      is_under_warranty, status, issue_id, description)
                   VALUES ($1,$2,$3,$4,$5,$6,$7,'open'::warranty_status,$8,$9)"#,
            )
            .bind(c.id)
            .bind(c.customer_id)
            .bind(c.item_id)
            .bind(c.serial_no)
            .bind(c.claim_date)
            .bind(c.warranty_expiry)
            .bind(c.is_under_warranty)
            .bind(c.issue_id)
            .bind(c.description),
        )
        .await?;
        Ok(())
    }

    /// Adjudicate an open claim. `status` is bound as a free string and cast at the DB
    /// (`$2::warranty_status`). Returns rows affected (0 = not an open claim).
    pub async fn adjudicate(
        &self,
        pool: &PgPool,
        claim_id: Uuid,
        status: &str,
        resolution: Option<&str>,
    ) -> Result<u64, sqlx::Error> {
        let moved = org_scope::execute_scoped(
            pool,
            sqlx::query(
                r#"UPDATE support.warranty_claims SET status=$2::warranty_status, resolution=$3
                   WHERE id=$1 AND status='open'::warranty_status AND (metadata->>'deleted_at') IS NULL"#,
            )
            .bind(claim_id).bind(status).bind(resolution),
        )
        .await?;
        Ok(moved.rows_affected())
    }
}

backbone_core::impl_crud_repository!(WarrantyClaimRepository, WarrantyClaim, soft_delete);