1use crate::models::{CreateCustomerUsageRequest, CustomerUsage, UsageState};
4use chrono::{DateTime, Utc};
5use sqlx::{Pool, Postgres, Row};
6use tmf_apis_core::{TmfError, TmfResult};
7use uuid::Uuid;
8
9fn map_sqlx_error(err: sqlx::Error) -> TmfError {
11 TmfError::Database(err.to_string())
12}
13
14fn 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
24fn 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
33pub 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, related_party: None, });
67 }
68
69 Ok(usages)
70}
71
72pub 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
108pub 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 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 get_usage_by_id(pool, id).await
158}