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:
2026-10-03 23:07:51 +05:30
co-authored by CommandCodeBot
commit 3af9f3f66f
125 changed files with 13660 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
use crate::db::format_number;
use crate::models::InvoiceSeries;
use crate::AppState;
use rusqlite::{params, Row};
use tauri::State;
fn map_series(row: &Row) -> rusqlite::Result<InvoiceSeries> {
let prefix: String = row.get(1)?;
let padding: i64 = row.get(2)?;
let next_number: i64 = row.get(3)?;
Ok(InvoiceSeries {
id: row.get(0)?,
next_invoice_number: format_number(&prefix, padding, next_number),
prefix,
padding,
next_number,
is_active: row.get::<_, i64>(4)? != 0,
created_at: row.get(5)?,
})
}
const SERIES_SELECT: &str =
"SELECT id, prefix, padding, next_number, is_active, created_at FROM invoice_series";
#[tauri::command]
pub fn get_active_series(state: State<AppState>) -> Result<Option<InvoiceSeries>, String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
let mut stmt = conn
.prepare(&format!(
"{} WHERE is_active = 1 ORDER BY id DESC LIMIT 1",
SERIES_SELECT
))
.map_err(|e| e.to_string())?;
let mut rows = stmt.query_map([], map_series).map_err(|e| e.to_string())?;
match rows.next() {
Some(r) => Ok(Some(r.map_err(|e| e.to_string())?)),
None => Ok(None),
}
}
#[tauri::command]
pub fn list_series(state: State<AppState>) -> Result<Vec<InvoiceSeries>, String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
let mut stmt = conn
.prepare(&format!("{} ORDER BY id DESC", SERIES_SELECT))
.map_err(|e| e.to_string())?;
let rows = stmt
.query_map([], map_series)
.map_err(|e| e.to_string())?
.collect::<rusqlite::Result<Vec<_>>>()
.map_err(|e| e.to_string())?;
Ok(rows)
}
/// Deactivates the current series and starts a fresh one, resetting the counter to 1.
#[tauri::command]
pub fn start_new_series(
state: State<AppState>,
prefix: String,
padding: i64,
) -> Result<InvoiceSeries, String> {
let prefix = prefix.trim().to_string();
if prefix.is_empty() {
return Err("Series prefix cannot be empty".into());
}
let padding = padding.clamp(1, 8);
let conn = state.db.lock().map_err(|e| e.to_string())?;
conn.execute("UPDATE invoice_series SET is_active = 0", [])
.map_err(|e| e.to_string())?;
conn.execute(
"INSERT INTO invoice_series (prefix, padding, next_number, is_active, created_at)
VALUES (?1, ?2, 1, 1, ?3)",
params![prefix, padding, chrono::Utc::now().to_rfc3339()],
)
.map_err(|e| e.to_string())?;
let id = conn.last_insert_rowid();
conn.query_row(
&format!("{} WHERE id = ?1", SERIES_SELECT),
params![id],
map_series,
)
.map_err(|e| e.to_string())
}