Initial commit: Voiced invoice desktop app

Offline-first invoicing app for Test Vendor, 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:
2026-10-03 23:07:51 +05:30
co-authored by CommandCodeBot
commit 875a12b2e6
121 changed files with 13660 additions and 0 deletions
+168
View File
@@ -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(())
}