Skip to main content

tmf677_usage/
db.rs

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