Initial commit: Voiced invoice desktop app
Offline-first invoicing app for Arun P, built by Bongbetic. - Tauri v2 (Rust) + React/TypeScript + IBM Carbon Design System UI - SQLite (rusqlite) storage with a persistent invoice-number series - Guided first-run setup, pre-filled vendor/bank details - Fixed-amount or rate x quantity line items (per sec/min/hour/session/unit) - GST as CGST+SGST or IGST, Indian amount-in-words - Optional signature, with a computer-generated note as fallback - Print-ready PDF export via @react-pdf/renderer - Windows cross-compile via cargo-xwin (NSIS installer) Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
use crate::models::{BankAccount, Settings};
|
||||
use crate::AppState;
|
||||
use rusqlite::{params, Row};
|
||||
use tauri::State;
|
||||
|
||||
const SETTINGS_COLS: &str = "vendor_name, vendor_address, vendor_email, vendor_phone, vendor_pan,
|
||||
vendor_gstin, vendor_state_code, logo_path, signature_path, default_bank_id, default_tax_rate,
|
||||
default_tax_type, payment_terms_days, currency, onboarded, theme";
|
||||
|
||||
fn map_settings(row: &Row) -> rusqlite::Result<Settings> {
|
||||
Ok(Settings {
|
||||
vendor_name: row.get(0)?,
|
||||
vendor_address: row.get(1)?,
|
||||
vendor_email: row.get(2)?,
|
||||
vendor_phone: row.get(3)?,
|
||||
vendor_pan: row.get(4)?,
|
||||
vendor_gstin: row.get(5)?,
|
||||
vendor_state_code: row.get(6)?,
|
||||
logo_path: row.get(7)?,
|
||||
signature_path: row.get(8)?,
|
||||
default_bank_id: row.get(9)?,
|
||||
default_tax_rate: row.get(10)?,
|
||||
default_tax_type: row.get(11)?,
|
||||
payment_terms_days: row.get(12)?,
|
||||
currency: row.get(13)?,
|
||||
onboarded: row.get::<_, i64>(14)? != 0,
|
||||
theme: row.get(15)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_settings(state: State<AppState>) -> Result<Settings, String> {
|
||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||
conn.query_row(
|
||||
&format!("SELECT {} FROM app_settings WHERE id = 1", SETTINGS_COLS),
|
||||
[],
|
||||
map_settings,
|
||||
)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn save_settings(state: State<AppState>, settings: Settings) -> Result<Settings, String> {
|
||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||
conn.execute(
|
||||
r#"UPDATE app_settings SET
|
||||
vendor_name = ?1, vendor_address = ?2, vendor_email = ?3, vendor_phone = ?4,
|
||||
vendor_pan = ?5, vendor_gstin = ?6, vendor_state_code = ?7, logo_path = ?8,
|
||||
signature_path = ?9, default_bank_id = ?10, default_tax_rate = ?11,
|
||||
default_tax_type = ?12, payment_terms_days = ?13, currency = ?14,
|
||||
onboarded = ?15, theme = ?16
|
||||
WHERE id = 1"#,
|
||||
params![
|
||||
settings.vendor_name,
|
||||
settings.vendor_address,
|
||||
settings.vendor_email,
|
||||
settings.vendor_phone,
|
||||
settings.vendor_pan,
|
||||
settings.vendor_gstin,
|
||||
settings.vendor_state_code,
|
||||
settings.logo_path,
|
||||
settings.signature_path,
|
||||
settings.default_bank_id,
|
||||
settings.default_tax_rate,
|
||||
settings.default_tax_type,
|
||||
settings.payment_terms_days,
|
||||
settings.currency,
|
||||
if settings.onboarded { 1 } else { 0 },
|
||||
settings.theme,
|
||||
],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
drop(conn);
|
||||
get_settings(state)
|
||||
}
|
||||
|
||||
fn map_bank(row: &Row) -> rusqlite::Result<BankAccount> {
|
||||
Ok(BankAccount {
|
||||
id: Some(row.get(0)?),
|
||||
label: row.get(1)?,
|
||||
bank_name: row.get(2)?,
|
||||
account_name: row.get(3)?,
|
||||
account_no: row.get(4)?,
|
||||
branch: row.get(5)?,
|
||||
ifsc: row.get(6)?,
|
||||
is_default: row.get::<_, i64>(7)? != 0,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn list_banks(state: State<AppState>) -> Result<Vec<BankAccount>, String> {
|
||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT id, label, bank_name, account_name, account_no, branch, ifsc, is_default
|
||||
FROM bank_accounts ORDER BY is_default DESC, id ASC",
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let rows = stmt
|
||||
.query_map([], map_bank)
|
||||
.map_err(|e| e.to_string())?
|
||||
.collect::<rusqlite::Result<Vec<_>>>()
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn save_bank(state: State<AppState>, bank: BankAccount) -> Result<BankAccount, String> {
|
||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||
let is_default = if bank.is_default { 1 } else { 0 };
|
||||
|
||||
let id = match bank.id {
|
||||
Some(id) => {
|
||||
conn.execute(
|
||||
r#"UPDATE bank_accounts SET label=?1, bank_name=?2, account_name=?3,
|
||||
account_no=?4, branch=?5, ifsc=?6, is_default=?7 WHERE id=?8"#,
|
||||
params![
|
||||
bank.label, bank.bank_name, bank.account_name, bank.account_no,
|
||||
bank.branch, bank.ifsc, is_default, id
|
||||
],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
id
|
||||
}
|
||||
None => {
|
||||
conn.execute(
|
||||
r#"INSERT INTO bank_accounts
|
||||
(label, bank_name, account_name, account_no, branch, ifsc, is_default)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"#,
|
||||
params![
|
||||
bank.label, bank.bank_name, bank.account_name, bank.account_no,
|
||||
bank.branch, bank.ifsc, is_default
|
||||
],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
conn.last_insert_rowid()
|
||||
}
|
||||
};
|
||||
|
||||
if is_default == 1 {
|
||||
conn.execute(
|
||||
"UPDATE bank_accounts SET is_default = 0 WHERE id <> ?1",
|
||||
params![id],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
conn.execute(
|
||||
"UPDATE app_settings SET default_bank_id = ?1 WHERE id = 1",
|
||||
params![id],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
conn.query_row(
|
||||
"SELECT id, label, bank_name, account_name, account_no, branch, ifsc, is_default
|
||||
FROM bank_accounts WHERE id = ?1",
|
||||
params![id],
|
||||
map_bank,
|
||||
)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn delete_bank(state: State<AppState>, id: i64) -> Result<(), String> {
|
||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||
conn.execute("DELETE FROM bank_accounts WHERE id = ?1", params![id])
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user