cal-core 0.2.158

Callable core lib
Documentation
// File: cal-core/src/rest/account.rs

use crate::account::*;
use crate::accounting::Address;
use crate::device::device::DeviceStruct;
use crate::rest::common::{
    ApiError, ListRequest,
};
use crate::RecordReference;
use serde::{Deserialize, Serialize};

#[cfg(feature = "openapi")]
use utoipa::{IntoParams, ToSchema};

/// Query parameters for account-specific operations
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema, IntoParams))]
#[cfg_attr(feature = "openapi", schema(
    title ="Query parameters containing account ID"
))]
pub struct AccountIdQuery {
    /// Account ID for the operation
    #[cfg_attr(feature = "openapi", schema(example = "507f1f77bcf86cd799439011"))]
    pub account_id: String,
}

/// Request to create a new account
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "openapi", schema(
    title ="Request payload for creating a new account",
    example = json!({
        "name": "Acme Corporation",
        "mbn": "12345",
        "domain": "acme.com",
        "organisation": {
            "id": "507f1f77bcf86cd799439011",
            "name": "Acme Org"
        },
        "clusterSettings": {
            "region": "us-east-1",
            "zone": "a"
        }
    })
))]
#[serde(rename_all = "camelCase")]
pub struct CreateAccountRequest {
    /// Account display name
    #[cfg_attr(feature = "openapi", schema(example = "Acme Corporation", min_length = 1))]
    pub name: String,

    /// Main Business Number (MBN) - must be unique
    #[cfg_attr(feature = "openapi", schema(example = "12345", min_length = 1))]
    pub mbn: String,

    /// Domain name for the account - must be unique
    #[cfg_attr(feature = "openapi", schema(example = "acme.com", min_length = 1))]
    pub domain: String,

    /// Organisation this account belongs to
    pub organisation: RecordReference,

    /// Physical address of the account
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<Address>,

    /// Microsoft Teams integration settings
    #[serde(skip_serializing_if = "Option::is_none")]
    pub teams: Option<TeamsAccount>,

    /// Environment configuration (e.g., production, staging)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub environment: Option<Environment>,

    /// Spending limits configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub spend_cap: Option<SpendCap>,

    /// Concurrent call limits
    #[serde(skip_serializing_if = "Option::is_none")]
    pub concurrency: Option<Concurrency>,

    /// Service tier configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub class_of_service: Option<ClassOfService>,

    /// Cluster deployment settings
    pub cluster_settings: Cluster,
}

/// Request to update an existing account
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "openapi", schema(
    title ="Request payload for updating an account. All fields are optional."
))]
#[serde(rename_all = "camelCase")]
pub struct UpdateAccountRequest {
    /// New account display name
    #[serde(skip_serializing_if = "Option::is_none")]
    #[cfg_attr(feature = "openapi", schema(example = "Acme Corp Updated"))]
    pub name: Option<String>,

    /// New MBN (must be unique if provided)
    #[serde(skip_serializing_if = "Option::is_none")]
    #[cfg_attr(feature = "openapi", schema(example = "54321"))]
    pub mbn: Option<String>,

    /// New domain (must be unique if provided)
    #[serde(skip_serializing_if = "Option::is_none")]
    #[cfg_attr(feature = "openapi", schema(example = "new-acme.com"))]
    pub domain: Option<String>,

    /// Update organisation reference
    #[serde(skip_serializing_if = "Option::is_none")]
    pub organisation: Option<RecordReference>,

    /// Update physical address
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<Address>,

    /// Update Teams settings
    #[serde(skip_serializing_if = "Option::is_none")]
    pub teams: Option<TeamsAccount>,

    /// Update environment
    #[serde(skip_serializing_if = "Option::is_none")]
    pub environment: Option<Environment>,

    /// Update spending limits
    #[serde(skip_serializing_if = "Option::is_none")]
    pub spend_cap: Option<SpendCap>,

    /// Update concurrency limits
    #[serde(skip_serializing_if = "Option::is_none")]
    pub concurrency: Option<Concurrency>,

    /// Update service tier
    #[serde(skip_serializing_if = "Option::is_none")]
    pub class_of_service: Option<ClassOfService>,

    /// Update cluster settings
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cluster_settings: Option<Cluster>,
}

/// Request parameters for listing accounts
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "openapi", schema(
    title ="Query parameters for listing accounts with filtering and pagination"
))]
#[serde(rename_all = "camelCase")]
pub struct AccountListRequest {
    /// Common list parameters (pagination, sorting, search)
    #[serde(flatten)]
    pub common: ListRequest,

    /// Filter by organisation ID
    #[serde(skip_serializing_if = "Option::is_none")]
    #[cfg_attr(feature = "openapi", schema(example = "507f1f77bcf86cd799439011"))]
    pub organisation_id: Option<String>,

    /// Filter by account name (partial match)
    #[serde(skip_serializing_if = "Option::is_none")]
    #[cfg_attr(feature = "openapi", schema(example = "Acme"))]
    pub name: Option<String>,

    /// Filter by exact MBN
    #[serde(skip_serializing_if = "Option::is_none")]
    #[cfg_attr(feature = "openapi", schema(example = "12345"))]
    pub mbn: Option<String>,

    /// Filter by exact domain
    #[serde(skip_serializing_if = "Option::is_none")]
    #[cfg_attr(feature = "openapi", schema(example = "acme.com"))]
    pub domain: Option<String>,
}

/// Account-specific error codes
#[derive(Debug, Clone)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub enum AccountErrorCode {
    /// Account not found
    NotFound,
    /// MBN already exists
    DuplicateMbn,
    /// Domain already exists
    DuplicateDomain,
    /// Invalid organisation reference
    InvalidOrganisation,
    /// Invalid cluster configuration
    InvalidCluster,
    /// Required field is missing
    MissingRequiredField,
}

impl AccountErrorCode {
    /// Convert error code to string representation
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::NotFound => "ACCOUNT_NOT_FOUND",
            Self::DuplicateMbn => "DUPLICATE_MBN",
            Self::DuplicateDomain => "DUPLICATE_DOMAIN",
            Self::InvalidOrganisation => "INVALID_ORGANISATION",
            Self::InvalidCluster => "INVALID_CLUSTER",
            Self::MissingRequiredField => "MISSING_REQUIRED_FIELD",
        }
    }
}

// Helper implementations
impl CreateAccountRequest {
    /// Validate the create account request
    pub fn validate(&self) -> Result<(), ApiError> {
        if self.name.trim().is_empty() {
            return Err(ApiError::new(
                AccountErrorCode::MissingRequiredField.as_str(),
                "Account name is required",
            )
                .with_field("name"));
        }

        if self.mbn.trim().is_empty() {
            return Err(ApiError::new(
                AccountErrorCode::MissingRequiredField.as_str(),
                "MBN is required",
            )
                .with_field("mbn"));
        }

        if self.domain.trim().is_empty() {
            return Err(ApiError::new(
                AccountErrorCode::MissingRequiredField.as_str(),
                "Domain is required",
            )
                .with_field("domain"));
        }

        Ok(())
    }
}

impl UpdateAccountRequest {
    /// Check if the update request has any fields to update
    pub fn is_empty(&self) -> bool {
        self.name.is_none()
            && self.mbn.is_none()
            && self.domain.is_none()
            && self.organisation.is_none()
            && self.address.is_none()
            && self.teams.is_none()
            && self.environment.is_none()
            && self.spend_cap.is_none()
            && self.concurrency.is_none()
            && self.class_of_service.is_none()
            && self.cluster_settings.is_none()
    }
}

impl Default for AccountListRequest {
    fn default() -> Self {
        Self {
            common: ListRequest {
                pagination: crate::rest::common::PaginationParams::default(),
                sort: crate::rest::common::SortParams::default(),
                search: None,
                time_range: None,
                filters: None,
            },
            organisation_id: None,
            name: None,
            mbn: None,
            domain: None,
        }
    }
}

impl AccountListRequest {
    /// Create a new account list request with defaults
    pub fn new() -> Self {
        Self::default()
    }

    /// Set pagination parameters
    pub fn with_pagination(mut self, page: u32, page_size: u32) -> Self {
        self.common.pagination = crate::rest::common::PaginationParams::new(page, page_size);
        self
    }

    /// Filter by organisation ID
    pub fn with_organisation_id(mut self, organisation_id: String) -> Self {
        self.organisation_id = Some(organisation_id);
        self
    }
}