Skip to main content

backbone_support/infrastructure/persistence/
warranty_claim_repository.rs

1//! Repository for WarrantyClaim 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 WarrantyClaim SQL (4-layer rule: services orchestrate, repos hold SQL).
6//!
7//! Tenancy (ADR-0029): the SQL here carries no tenant key. The scoped-execute helpers ride the
8//! request-dedicated connection when the composing service bound one (its fence variables govern
9//! what the RLS layer accepts) and fall back to a plain pool execute otherwise.
10//!
11//! Thin newtype over `backbone_orm::GenericCrudRepository<WarrantyClaim, backbone_orm::SoftDelete>`.
12//! All standard CRUD methods are available via `Deref`.
13
14use anyhow::Result;
15use chrono::{DateTime, Utc};
16use sqlx::PgPool;
17use uuid::Uuid;
18
19use backbone_orm::org_scope;
20
21use crate::domain::entity::WarrantyClaim;
22
23/// Table name for WarrantyClaim entities
24pub const TABLE_NAME: &str = "support.warranty_claims";
25
26/// Repository for WarrantyClaim entities.
27///
28/// All standard CRUD, soft-delete, pagination, and bulk methods are
29/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
30pub struct WarrantyClaimRepository(
31    backbone_orm::GenericCrudRepository<WarrantyClaim, backbone_orm::SoftDelete>,
32);
33
34impl std::ops::Deref for WarrantyClaimRepository {
35    type Target = backbone_orm::GenericCrudRepository<WarrantyClaim, backbone_orm::SoftDelete>;
36    fn deref(&self) -> &Self::Target {
37        &self.0
38    }
39}
40
41impl WarrantyClaimRepository {
42    /// Create a new repository instance.
43    pub fn new(pool: PgPool) -> Self {
44        Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
45    }
46}
47
48/// The exact row a filed warranty claim writes.
49///
50/// Mirrors the raw column shape rather than the `WarrantyClaim` entity. Coverage
51/// (`is_under_warranty`) is computed by the caller at file time and stored, not re-derived here.
52pub struct NewWarrantyClaimRow<'a> {
53    pub id: Uuid,
54    pub customer_id: Option<Uuid>,
55    pub item_id: Uuid,
56    pub serial_no: Option<&'a str>,
57    pub claim_date: DateTime<Utc>,
58    pub warranty_expiry: Option<DateTime<Utc>>,
59    pub is_under_warranty: bool,
60    pub issue_id: Option<Uuid>,
61    pub description: Option<&'a str>,
62}
63
64/// Hand-written WarrantyClaim SQL. Lives here (not in the write service) per the module's 4-layer rule.
65impl WarrantyClaimRepository {
66    /// File a warranty claim.
67    ///
68    /// A write outside any transaction: takes the pool and runs the scoped helper so it rides the
69    /// request-dedicated connection when the composing service bound a scope — under a decorated
70    /// deployment the fence's WITH CHECK governs the row; with no scope bound this is a plain insert.
71    pub async fn insert_claim(
72        &self,
73        pool: &PgPool,
74        c: &NewWarrantyClaimRow<'_>,
75    ) -> Result<(), sqlx::Error> {
76        org_scope::execute_scoped(
77            pool,
78            sqlx::query(
79                r#"INSERT INTO support.warranty_claims
80                     (id, customer_id, item_id, serial_no, claim_date, warranty_expiry,
81                      is_under_warranty, status, issue_id, description)
82                   VALUES ($1,$2,$3,$4,$5,$6,$7,'open'::warranty_status,$8,$9)"#,
83            )
84            .bind(c.id)
85            .bind(c.customer_id)
86            .bind(c.item_id)
87            .bind(c.serial_no)
88            .bind(c.claim_date)
89            .bind(c.warranty_expiry)
90            .bind(c.is_under_warranty)
91            .bind(c.issue_id)
92            .bind(c.description),
93        )
94        .await?;
95        Ok(())
96    }
97
98    /// Adjudicate an open claim. `status` is bound as a free string and cast at the DB
99    /// (`$2::warranty_status`). Returns rows affected (0 = not an open claim).
100    pub async fn adjudicate(
101        &self,
102        pool: &PgPool,
103        claim_id: Uuid,
104        status: &str,
105        resolution: Option<&str>,
106    ) -> Result<u64, sqlx::Error> {
107        let moved = org_scope::execute_scoped(
108            pool,
109            sqlx::query(
110                r#"UPDATE support.warranty_claims SET status=$2::warranty_status, resolution=$3
111                   WHERE id=$1 AND status='open'::warranty_status AND (metadata->>'deleted_at') IS NULL"#,
112            )
113            .bind(claim_id).bind(status).bind(resolution),
114        )
115        .await?;
116        Ok(moved.rows_affected())
117    }
118}
119
120backbone_core::impl_crud_repository!(WarrantyClaimRepository, WarrantyClaim, soft_delete);