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);