use sqlx::{PgPool, Postgres, Transaction}; use uuid::Uuid; use crate::models::*; pub async fn create_account(pool: &PgPool, account: &CreateAccount) -> anyhow::Result { 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> { 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> { 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 { 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> { 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> { 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> { 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) }