1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! Repository for PurchaseReceiptItem 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 PurchaseReceiptItem SQL
//! (4-layer rule: services orchestrate, repositories hold the SQL).
//!
//! Thin newtype over `backbone_orm::GenericCrudRepository<PurchaseReceiptItem, 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::org_scope;
use crate::domain::entity::PurchaseReceiptItem;
/// Table name for PurchaseReceiptItem entities
pub const TABLE_NAME: &str = "inventory.purchase_receipt_items";
/// Repository for PurchaseReceiptItem entities.
///
/// All standard CRUD, soft-delete, pagination, and bulk methods are
/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
pub struct PurchaseReceiptItemRepository(
backbone_orm::GenericCrudRepository<PurchaseReceiptItem, backbone_orm::SoftDelete>,
);
impl std::ops::Deref for PurchaseReceiptItemRepository {
type Target = backbone_orm::GenericCrudRepository<PurchaseReceiptItem, backbone_orm::SoftDelete>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl PurchaseReceiptItemRepository {
/// Create a new repository instance.
pub fn new(pool: PgPool) -> Self {
Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
}
}
/// The exact row a receipt line insert writes. `amount` arrives already rounded.
pub struct NewReceiptItemRow {
pub id: Uuid,
pub receipt_id: Uuid,
pub item_id: Uuid,
pub quantity: Decimal,
pub rate: Decimal,
pub amount: Decimal,
/// Landed-cost service line (the seam owned by inventory): the door skips move-minting
/// for flagged lines — they carry cost into a LandedCost document, not stock.
pub is_landed_costs_line: bool,
}
/// A receipt line as the submit path reads it: what arrived, and at what cost.
pub struct ReceiptItemRow {
pub item_id: Uuid,
pub quantity: Decimal,
pub rate: Decimal,
/// Landed-cost service line — the submit/cancel doors skip it (no stock behind it).
pub is_landed_costs_line: bool,
}
/// Hand-written PurchaseReceiptItem SQL. Lives here per the module's 4-layer rule.
impl PurchaseReceiptItemRepository {
/// Insert one receipt line. Takes the CALLER'S connection so it commits with its header; the
/// caller has already relayed the ambient org scope onto it — don't re-bind here.
pub async fn insert_item(
&self,
conn: &mut sqlx::PgConnection,
l: &NewReceiptItemRow,
) -> Result<(), sqlx::Error> {
sqlx::query(
r#"INSERT INTO inventory.purchase_receipt_items
(id, receipt_id, item_id, quantity, rate, amount, is_landed_costs_line)
VALUES ($1,$2,$3,$4,$5,$6,$7)"#,
)
.bind(l.id).bind(l.receipt_id).bind(l.item_id).bind(l.quantity).bind(l.rate).bind(l.amount)
.bind(l.is_landed_costs_line)
.execute(conn)
.await?;
Ok(())
}
/// Read the live lines in `id` order — the order the movement applies them (and therefore the
/// order `sle_no` is assigned in), so it must not change. The scoped read helper makes it ride
/// the ambient org scope's request-dedicated connection, so the composing decorator's fence
/// (ADR-0029) bounds what the read can see.
pub async fn fetch_items(
&self,
pool: &PgPool,
receipt_id: Uuid,
) -> Result<Vec<ReceiptItemRow>, sqlx::Error> {
let rows = org_scope::fetch_all_rows_scoped(
pool,
sqlx::query(
r#"SELECT item_id, quantity, rate, is_landed_costs_line FROM inventory.purchase_receipt_items
WHERE receipt_id=$1 AND (metadata->>'deleted_at') IS NULL ORDER BY id"#,
)
.bind(receipt_id),
)
.await?;
Ok(rows.iter().map(|it| ReceiptItemRow {
item_id: it.get("item_id"), quantity: it.get("quantity"), rate: it.get("rate"),
is_landed_costs_line: it.get("is_landed_costs_line"),
}).collect())
}
}
backbone_core::impl_crud_repository!(PurchaseReceiptItemRepository, PurchaseReceiptItem, soft_delete);