cala_server/graphql/
account.rs

1use async_graphql::{dataloader::*, types::connection::*, *};
2
3use cala_ledger::{
4    balance::*,
5    primitives::{AccountId, Currency, JournalId},
6};
7
8pub use cala_ledger::account::AccountsByNameCursor;
9
10use crate::app::CalaApp;
11
12use super::{
13    account_set::*, balance::Balance, convert::ToGlobalId, loader::LedgerDataLoader, primitives::*,
14    schema::DbOp,
15};
16
17#[derive(Clone, SimpleObject)]
18#[graphql(complex)]
19pub struct Account {
20    id: ID,
21    account_id: UUID,
22    version: u32,
23    code: String,
24    name: String,
25    normal_balance_type: DebitOrCredit,
26    status: Status,
27    external_id: Option<String>,
28    description: Option<String>,
29    metadata: Option<JSON>,
30    pub(super) created_at: Timestamp,
31    modified_at: Timestamp,
32}
33
34#[ComplexObject]
35impl Account {
36    async fn balance(
37        &self,
38        ctx: &Context<'_>,
39        journal_id: UUID,
40        currency: CurrencyCode,
41    ) -> async_graphql::Result<Option<Balance>> {
42        let journal_id = JournalId::from(journal_id);
43        let account_id = AccountId::from(self.account_id);
44        let currency = Currency::from(currency);
45
46        let balance: Option<AccountBalance> = match ctx.data_opt::<DbOp>() {
47            Some(op) => {
48                let app = ctx.data_unchecked::<CalaApp>();
49                let mut op = op.try_lock().expect("Lock held concurrently");
50                Some(
51                    app.ledger()
52                        .balances()
53                        .find_in_op(&mut op, journal_id, account_id, currency)
54                        .await?,
55                )
56            }
57            None => {
58                let loader = ctx.data_unchecked::<DataLoader<LedgerDataLoader>>();
59                loader.load_one((journal_id, account_id, currency)).await?
60            }
61        };
62        Ok(balance.map(Balance::from))
63    }
64
65    async fn sets(
66        &self,
67        ctx: &Context<'_>,
68        first: i32,
69        after: Option<String>,
70    ) -> Result<Connection<AccountSetsByNameCursor, AccountSet, EmptyFields, EmptyFields>> {
71        let app = ctx.data_unchecked::<CalaApp>();
72        let account_id = AccountId::from(self.account_id);
73        query(
74            after,
75            None,
76            Some(first),
77            None,
78            |after, _, first, _| async move {
79                let first = first.expect("First always exists");
80                let query_args = cala_ledger::es_entity::PaginatedQueryArgs { first, after };
81
82                let result = match ctx.data_opt::<DbOp>() {
83                    Some(op) => {
84                        let mut op = op.try_lock().expect("Lock held concurrently");
85                        app.ledger()
86                            .account_sets()
87                            .find_where_member_in_op(&mut op, account_id, query_args)
88                            .await?
89                    }
90                    None => {
91                        app.ledger()
92                            .account_sets()
93                            .find_where_member(account_id, query_args)
94                            .await?
95                    }
96                };
97                let mut connection = Connection::new(false, result.has_next_page);
98                connection
99                    .edges
100                    .extend(result.entities.into_iter().map(|entity| {
101                        let cursor = AccountSetsByNameCursor::from(&entity);
102                        Edge::new(cursor, AccountSet::from(entity))
103                    }));
104                Ok::<_, async_graphql::Error>(connection)
105            },
106        )
107        .await
108    }
109}
110
111#[derive(InputObject)]
112pub(super) struct AccountCreateInput {
113    pub account_id: UUID,
114    pub external_id: Option<String>,
115    pub code: String,
116    pub name: String,
117    #[graphql(default)]
118    pub normal_balance_type: DebitOrCredit,
119    pub description: Option<String>,
120    #[graphql(default)]
121    pub status: Status,
122    pub metadata: Option<JSON>,
123    pub account_set_ids: Option<Vec<UUID>>,
124}
125
126#[derive(SimpleObject)]
127pub(super) struct AccountCreatePayload {
128    pub account: Account,
129}
130
131#[derive(InputObject)]
132pub(super) struct AccountUpdateInput {
133    pub external_id: Option<String>,
134    pub code: Option<String>,
135    pub name: Option<String>,
136    pub normal_balance_type: Option<DebitOrCredit>,
137    pub description: Option<String>,
138    pub status: Option<Status>,
139    pub metadata: Option<JSON>,
140}
141
142#[derive(SimpleObject)]
143pub(super) struct AccountUpdatePayload {
144    pub account: Account,
145}
146
147impl ToGlobalId for cala_ledger::AccountId {
148    fn to_global_id(&self) -> async_graphql::types::ID {
149        async_graphql::types::ID::from(format!("account:{}", self))
150    }
151}
152
153impl From<cala_ledger::account::Account> for Account {
154    fn from(account: cala_ledger::account::Account) -> Self {
155        let created_at = account.created_at();
156        let modified_at = account.modified_at();
157        let values = account.into_values();
158        Self {
159            id: values.id.to_global_id(),
160            account_id: UUID::from(values.id),
161            version: values.version,
162            code: values.code,
163            name: values.name,
164            normal_balance_type: values.normal_balance_type,
165            status: values.status,
166            external_id: values.external_id,
167            description: values.description,
168            metadata: values.metadata.map(JSON::from),
169            created_at: created_at.into(),
170            modified_at: modified_at.into(),
171        }
172    }
173}
174
175impl From<cala_ledger::account::Account> for AccountCreatePayload {
176    fn from(value: cala_ledger::account::Account) -> Self {
177        Self {
178            account: Account::from(value),
179        }
180    }
181}
182
183impl From<cala_ledger::account::Account> for AccountUpdatePayload {
184    fn from(value: cala_ledger::account::Account) -> Self {
185        Self {
186            account: Account::from(value),
187        }
188    }
189}