bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
175 lines
5.0 KiB
Rust
175 lines
5.0 KiB
Rust
use sqlx::{PgPool, Postgres, Transaction};
|
|
use uuid::Uuid;
|
|
|
|
use crate::models::*;
|
|
|
|
pub async fn create_account(pool: &PgPool, account: &CreateAccount) -> anyhow::Result<Account> {
|
|
let account = sqlx::query_as::<_, Account>(
|
|
r#"
|
|
INSERT INTO accounts (id, code, name, account_type, created_at)
|
|
VALUES ($1, $2, $3, $4, NOW())
|
|
RETURNING id, code, name, account_type, created_at
|
|
"#,
|
|
)
|
|
.bind(Uuid::new_v4())
|
|
.bind(&account.code)
|
|
.bind(&account.name)
|
|
.bind(&account.account_type)
|
|
.fetch_one(pool)
|
|
.await?;
|
|
|
|
Ok(account)
|
|
}
|
|
|
|
pub async fn get_accounts(pool: &PgPool) -> anyhow::Result<Vec<Account>> {
|
|
let accounts = sqlx::query_as::<_, Account>(
|
|
r#"SELECT id, code, name, account_type, created_at FROM accounts ORDER BY code"#,
|
|
)
|
|
.fetch_all(pool)
|
|
.await?;
|
|
|
|
Ok(accounts)
|
|
}
|
|
|
|
pub async fn get_account_by_id(pool: &PgPool, id: Uuid) -> anyhow::Result<Option<Account>> {
|
|
let account = sqlx::query_as::<_, Account>(
|
|
r#"SELECT id, code, name, account_type, created_at FROM accounts WHERE id = $1"#,
|
|
)
|
|
.bind(id)
|
|
.fetch_optional(pool)
|
|
.await?;
|
|
|
|
Ok(account)
|
|
}
|
|
|
|
pub async fn create_journal_entry(
|
|
pool: &PgPool,
|
|
entry: &CreateJournalEntry,
|
|
created_by: &str,
|
|
) -> anyhow::Result<JournalEntryWithLines> {
|
|
let mut tx: Transaction<'_, Postgres> = pool.begin().await?;
|
|
|
|
let entry_id = Uuid::new_v4();
|
|
let journal_entry = sqlx::query_as::<_, JournalEntry>(
|
|
r#"
|
|
INSERT INTO journal_entries (id, entry_number, description, entry_date, created_at, created_by)
|
|
VALUES ($1, $2, $3, $4, NOW(), $5)
|
|
RETURNING id, entry_number, description, entry_date, created_at, created_by
|
|
"#,
|
|
)
|
|
.bind(entry_id)
|
|
.bind(&entry.entry_number)
|
|
.bind(&entry.description)
|
|
.bind(entry.entry_date)
|
|
.bind(created_by)
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
|
|
let mut lines = Vec::new();
|
|
for line in &entry.lines {
|
|
let line_id = Uuid::new_v4();
|
|
let journal_line = sqlx::query_as::<_, JournalLineWithAccount>(
|
|
r#"
|
|
INSERT INTO journal_lines (id, journal_entry_id, account_id, description, debit, credit)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
RETURNING
|
|
journal_lines.id,
|
|
journal_lines.journal_entry_id,
|
|
journal_lines.account_id,
|
|
(SELECT code FROM accounts WHERE id = $3) as account_code,
|
|
(SELECT name FROM accounts WHERE id = $3) as account_name,
|
|
journal_lines.description,
|
|
journal_lines.debit,
|
|
journal_lines.credit
|
|
"#,
|
|
)
|
|
.bind(line_id)
|
|
.bind(entry_id)
|
|
.bind(line.account_id)
|
|
.bind(&line.description)
|
|
.bind(&line.debit)
|
|
.bind(&line.credit)
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
|
|
lines.push(journal_line);
|
|
}
|
|
|
|
tx.commit().await?;
|
|
|
|
Ok(JournalEntryWithLines {
|
|
entry: journal_entry,
|
|
lines,
|
|
})
|
|
}
|
|
|
|
pub async fn get_journal_entries(pool: &PgPool) -> anyhow::Result<Vec<JournalEntry>> {
|
|
let entries = sqlx::query_as::<_, JournalEntry>(
|
|
r#"SELECT id, entry_number, description, entry_date, created_at, created_by FROM journal_entries ORDER BY entry_date DESC"#,
|
|
)
|
|
.fetch_all(pool)
|
|
.await?;
|
|
|
|
Ok(entries)
|
|
}
|
|
|
|
pub async fn get_journal_entry_by_id(
|
|
pool: &PgPool,
|
|
id: Uuid,
|
|
) -> anyhow::Result<Option<JournalEntryWithLines>> {
|
|
let entry = sqlx::query_as::<_, JournalEntry>(
|
|
r#"SELECT id, entry_number, description, entry_date, created_at, created_by FROM journal_entries WHERE id = $1"#,
|
|
)
|
|
.bind(id)
|
|
.fetch_optional(pool)
|
|
.await?;
|
|
|
|
let Some(entry) = entry else {
|
|
return Ok(None);
|
|
};
|
|
|
|
let lines = sqlx::query_as::<_, JournalLineWithAccount>(
|
|
r#"
|
|
SELECT
|
|
jl.id,
|
|
jl.journal_entry_id,
|
|
jl.account_id,
|
|
a.code as account_code,
|
|
a.name as account_name,
|
|
jl.description,
|
|
jl.debit,
|
|
jl.credit
|
|
FROM journal_lines jl
|
|
JOIN accounts a ON jl.account_id = a.id
|
|
WHERE jl.journal_entry_id = $1
|
|
"#,
|
|
)
|
|
.bind(id)
|
|
.fetch_all(pool)
|
|
.await?;
|
|
|
|
Ok(Some(JournalEntryWithLines { entry, lines }))
|
|
}
|
|
|
|
pub async fn get_trial_balance(pool: &PgPool) -> anyhow::Result<Vec<TrialBalanceRow>> {
|
|
let rows = sqlx::query_as::<_, TrialBalanceRow>(
|
|
r#"
|
|
SELECT
|
|
a.id as account_id,
|
|
a.code as account_code,
|
|
a.name as account_name,
|
|
COALESCE(SUM(jl.debit), 0)::FLOAT8 as total_debit,
|
|
COALESCE(SUM(jl.credit), 0)::FLOAT8 as total_credit,
|
|
(COALESCE(SUM(jl.debit), 0) - COALESCE(SUM(jl.credit), 0))::FLOAT8 as balance
|
|
FROM accounts a
|
|
LEFT JOIN journal_lines jl ON a.id = jl.account_id
|
|
GROUP BY a.id, a.code, a.name
|
|
ORDER BY a.code
|
|
"#,
|
|
)
|
|
.fetch_all(pool)
|
|
.await?;
|
|
|
|
Ok(rows)
|
|
}
|