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}