//! Client (organization) database operations use serde::{Deserialize, Serialize}; use sqlx::PgPool; use uuid::Uuid; /// Client record from database #[derive(Debug, Clone, sqlx::FromRow)] pub struct Client { pub id: Uuid, pub name: String, pub code: Option, pub notes: Option, pub is_active: bool, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, } /// Client response for API #[derive(Debug, Clone, Serialize)] pub struct ClientResponse { pub id: Uuid, pub name: String, pub code: Option, pub notes: Option, pub is_active: bool, pub created_at: chrono::DateTime, pub site_count: Option, pub agent_count: Option, } impl From for ClientResponse { fn from(c: Client) -> Self { ClientResponse { id: c.id, name: c.name, code: c.code, notes: c.notes, is_active: c.is_active, created_at: c.created_at, site_count: None, agent_count: None, } } } /// Data for creating a new client #[derive(Debug, Deserialize)] pub struct CreateClient { pub name: String, pub code: Option, pub notes: Option, } /// Data for updating a client #[derive(Debug, Deserialize)] pub struct UpdateClient { pub name: Option, pub code: Option, pub notes: Option, pub is_active: Option, } /// Create a new client pub async fn create_client(pool: &PgPool, client: CreateClient) -> Result { sqlx::query_as::<_, Client>( r#" INSERT INTO clients (name, code, notes) VALUES ($1, $2, $3) RETURNING * "#, ) .bind(&client.name) .bind(&client.code) .bind(&client.notes) .fetch_one(pool) .await } /// Get a client by ID pub async fn get_client_by_id(pool: &PgPool, id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, Client>("SELECT * FROM clients WHERE id = $1") .bind(id) .fetch_optional(pool) .await } /// Get all clients pub async fn get_all_clients(pool: &PgPool) -> Result, sqlx::Error> { sqlx::query_as::<_, Client>("SELECT * FROM clients ORDER BY name") .fetch_all(pool) .await } /// Get all clients with counts #[derive(Debug, Clone, sqlx::FromRow)] pub struct ClientWithCounts { pub id: Uuid, pub name: String, pub code: Option, pub notes: Option, pub is_active: bool, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub site_count: i64, pub agent_count: i64, } pub async fn get_all_clients_with_counts(pool: &PgPool) -> Result, sqlx::Error> { sqlx::query_as::<_, ClientWithCounts>( r#" SELECT c.*, COALESCE((SELECT COUNT(*) FROM sites WHERE client_id = c.id), 0) as site_count, COALESCE((SELECT COUNT(*) FROM agents a JOIN sites s ON a.site_id = s.id WHERE s.client_id = c.id), 0) as agent_count FROM clients c ORDER BY c.name "#, ) .fetch_all(pool) .await } /// Update a client pub async fn update_client( pool: &PgPool, id: Uuid, update: UpdateClient, ) -> Result, sqlx::Error> { sqlx::query_as::<_, Client>( r#" UPDATE clients SET name = COALESCE($1, name), code = COALESCE($2, code), notes = COALESCE($3, notes), is_active = COALESCE($4, is_active) WHERE id = $5 RETURNING * "#, ) .bind(&update.name) .bind(&update.code) .bind(&update.notes) .bind(&update.is_active) .bind(id) .fetch_optional(pool) .await } /// Delete a client pub async fn delete_client(pool: &PgPool, id: Uuid) -> Result { let result = sqlx::query("DELETE FROM clients WHERE id = $1") .bind(id) .execute(pool) .await?; Ok(result.rows_affected() > 0) }