tmf635_usage/
db.rs

1//! Database operations for TMF635 Usage Management
2
3use crate::models::{CreateUsageRequest, Usage, UsageState};
4use chrono::{DateTime, Utc};
5use sqlx::{Pool, Postgres, Row};
6use tmf_apis_core::{TmfError, TmfResult};
7use uuid::Uuid;
8
9// Helper to convert sqlx::Error to TmfError
10fn map_sqlx_error(err: sqlx::Error) -> TmfError {
11    TmfError::Database(err.to_string())
12}
13
14/// Parse usage state from database string
15fn parse_usage_state(s: &str) -> UsageState {
16    match s.to_uppercase().as_str() {
17        "CAPTURED" => UsageState::Captured,
18        "RATED" => UsageState::Rated,
19        "BILLED" => UsageState::Billed,
20        "REJECTED" => UsageState::Rejected,
21        _ => UsageState::Captured,
22    }
23}
24
25/// Convert usage state to database string
26fn usage_state_to_string(state: &UsageState) -> String {
27    match state {
28        UsageState::Captured => "CAPTURED".to_string(),
29        UsageState::Rated => "RATED".to_string(),
30        UsageState::Billed => "BILLED".to_string(),
31        UsageState::Rejected => "REJECTED".to_string(),
32    }
33}
34
35/// Get all usage records
36pub async fn get_usages(pool: &Pool<Postgres>) -> TmfResult<Vec<Usage>> {
37    let rows = sqlx::query(
38        "SELECT id, name, description, version, state, usage_type, usage_date, start_date, end_date, 
39         amount, unit, href, last_update
40         FROM usages ORDER BY usage_date DESC",
41    )
42    .fetch_all(pool)
43    .await
44    .map_err(map_sqlx_error)?;
45
46    let mut usages = Vec::new();
47    for row in rows {
48        usages.push(Usage {
49            base: tmf_apis_core::BaseEntity {
50                id: row.get::<Uuid, _>("id"),
51                href: row.get::<Option<String>, _>("href"),
52                name: row.get::<String, _>("name"),
53                description: row.get::<Option<String>, _>("description"),
54                version: row.get::<Option<String>, _>("version"),
55                lifecycle_status: tmf_apis_core::LifecycleStatus::Active,
56                last_update: row.get::<Option<DateTime<Utc>>, _>("last_update"),
57                valid_for: None,
58            },
59            state: parse_usage_state(&row.get::<String, _>("state")),
60            usage_type: row.get::<Option<String>, _>("usage_type"),
61            usage_date: row.get::<Option<DateTime<Utc>>, _>("usage_date"),
62            start_date: row.get::<Option<DateTime<Utc>>, _>("start_date"),
63            end_date: row.get::<Option<DateTime<Utc>>, _>("end_date"),
64            amount: row.get::<Option<f64>, _>("amount"),
65            unit: row.get::<Option<String>, _>("unit"),
66            product_offering: None, // Load separately if needed
67            related_party: None,    // Load separately if needed
68            rating: None,           // Load separately if needed
69        });
70    }
71
72    Ok(usages)
73}
74
75/// Get usage by ID
76pub async fn get_usage_by_id(pool: &Pool<Postgres>, id: Uuid) -> TmfResult<Usage> {
77    let row = sqlx::query(
78        "SELECT id, name, description, version, state, usage_type, usage_date, start_date, end_date, 
79         amount, unit, href, last_update
80         FROM usages WHERE id = $1",
81    )
82    .bind(id)
83    .fetch_optional(pool)
84    .await
85    .map_err(map_sqlx_error)?
86    .ok_or_else(|| TmfError::NotFound(format!("Usage with id {} not found", id)))?;
87
88    Ok(Usage {
89        base: tmf_apis_core::BaseEntity {
90            id: row.get::<Uuid, _>("id"),
91            href: row.get::<Option<String>, _>("href"),
92            name: row.get::<String, _>("name"),
93            description: row.get::<Option<String>, _>("description"),
94            version: row.get::<Option<String>, _>("version"),
95            lifecycle_status: tmf_apis_core::LifecycleStatus::Active,
96            last_update: row.get::<Option<DateTime<Utc>>, _>("last_update"),
97            valid_for: None,
98        },
99        state: parse_usage_state(&row.get::<String, _>("state")),
100        usage_type: row.get::<Option<String>, _>("usage_type"),
101        usage_date: row.get::<Option<DateTime<Utc>>, _>("usage_date"),
102        start_date: row.get::<Option<DateTime<Utc>>, _>("start_date"),
103        end_date: row.get::<Option<DateTime<Utc>>, _>("end_date"),
104        amount: row.get::<Option<f64>, _>("amount"),
105        unit: row.get::<Option<String>, _>("unit"),
106        product_offering: None,
107        related_party: None,
108        rating: None,
109    })
110}
111
112/// Create a new usage record
113pub async fn create_usage(pool: &Pool<Postgres>, request: CreateUsageRequest) -> TmfResult<Usage> {
114    let id = Uuid::new_v4();
115    let state = usage_state_to_string(&UsageState::Captured);
116
117    sqlx::query(
118        "INSERT INTO usages (id, name, description, version, state, usage_type, usage_date, 
119         start_date, end_date, amount, unit, product_offering_id, rating_id)
120         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
121    )
122    .bind(id)
123    .bind(&request.name)
124    .bind(&request.description)
125    .bind(&request.version)
126    .bind(&state)
127    .bind(&request.usage_type)
128    .bind(request.usage_date)
129    .bind(request.start_date)
130    .bind(request.end_date)
131    .bind(request.amount)
132    .bind(&request.unit)
133    .bind(request.product_offering_id)
134    .bind(request.rating_id)
135    .execute(pool)
136    .await
137    .map_err(map_sqlx_error)?;
138
139    // Create related parties if provided
140    if let Some(parties) = request.related_party {
141        for party in parties {
142            let party_id = Uuid::new_v4();
143            sqlx::query(
144                "INSERT INTO usage_related_parties (id, usage_id, name, role)
145                 VALUES ($1, $2, $3, $4)",
146            )
147            .bind(party_id)
148            .bind(id)
149            .bind(&party.name)
150            .bind(&party.role)
151            .execute(pool)
152            .await
153            .map_err(map_sqlx_error)?;
154        }
155    }
156
157    // Fetch the created usage
158    get_usage_by_id(pool, id).await
159}