backbone_pos/infrastructure/persistence/pos_discount_repository_custom.rs
1//! Hand-written PosDiscount reads (user-owned, never regenerated) — the custom methods on
2//! [`PosDiscountRepository`]. Sibling to the generated newtype file, per the module's custom-code
3//! convention. Holds the SQL per the 4-layer rule: services orchestrate, repositories hold SQL.
4//!
5//! The order-level discount master is the server-side source of the discount RATE: a ring names a
6//! discount by id, and the percentage applied is ALWAYS the one stored on the caller's scoped
7//! master row — never a rate echoed back by the client (the offline-sync trust posture: client
8//! identity, server money).
9
10use rust_decimal::Decimal;
11use sqlx::Row;
12use uuid::Uuid;
13
14use crate::infrastructure::persistence::PosDiscountRepository;
15
16/// A discount master row as the ticket compute reads it: the percentage is a FRACTION (0.1 = 10%),
17/// stored `decimal(6,4)` exactly like the register's retired flat tax rate was.
18pub struct DiscountRow {
19 pub id: Uuid,
20 pub name: String,
21 pub percentage: Decimal,
22}
23
24impl PosDiscountRepository {
25 /// Read one discount master row by id, on the CALLER'S scope-relayed transaction (the discount
26 /// gate runs inside the ring/sync unit of work). `Ok(None)` = no such discount visible to the
27 /// caller's scope (soft-deleted masters are retired — a ring naming one is refused, not silently
28 /// discounted). The fence is the whole guard (ADR-0029).
29 pub async fn fetch_discount(
30 &self,
31 conn: &mut sqlx::PgConnection,
32 discount_id: Uuid,
33 ) -> Result<Option<DiscountRow>, sqlx::Error> {
34 let row = sqlx::query(
35 r#"SELECT id, name, percentage
36 FROM pos.pos_discounts
37 WHERE id=$1 AND (metadata->>'deleted_at') IS NULL"#,
38 )
39 .bind(discount_id)
40 .fetch_optional(conn)
41 .await?;
42 Ok(row.map(|r| DiscountRow {
43 id: r.get("id"),
44 name: r.get("name"),
45 percentage: r.get("percentage"),
46 }))
47 }
48}