feat: add template_id column to invoices table and update_invoice_template command
This commit is contained in:
@@ -62,6 +62,7 @@ pub struct Invoice {
|
|||||||
pub total: f64,
|
pub total: f64,
|
||||||
pub notes: Option<String>,
|
pub notes: Option<String>,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
|
pub template_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client commands
|
// Client commands
|
||||||
@@ -317,11 +318,11 @@ pub fn get_reports(state: State<AppState>, start_date: String, end_date: String)
|
|||||||
pub fn create_invoice(state: State<AppState>, invoice: Invoice) -> Result<i64, String> {
|
pub fn create_invoice(state: State<AppState>, invoice: Invoice) -> Result<i64, String> {
|
||||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO invoices (client_id, invoice_number, date, due_date, subtotal, tax_rate, tax_amount, discount, total, notes, status)
|
"INSERT INTO invoices (client_id, invoice_number, date, due_date, subtotal, tax_rate, tax_amount, discount, total, notes, status, template_id)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
|
||||||
params![invoice.client_id, invoice.invoice_number, invoice.date, invoice.due_date,
|
params![invoice.client_id, invoice.invoice_number, invoice.date, invoice.due_date,
|
||||||
invoice.subtotal, invoice.tax_rate, invoice.tax_amount, invoice.discount,
|
invoice.subtotal, invoice.tax_rate, invoice.tax_amount, invoice.discount,
|
||||||
invoice.total, invoice.notes, invoice.status],
|
invoice.total, invoice.notes, invoice.status, invoice.template_id],
|
||||||
).map_err(|e| e.to_string())?;
|
).map_err(|e| e.to_string())?;
|
||||||
Ok(conn.last_insert_rowid())
|
Ok(conn.last_insert_rowid())
|
||||||
}
|
}
|
||||||
@@ -330,7 +331,7 @@ pub fn create_invoice(state: State<AppState>, invoice: Invoice) -> Result<i64, S
|
|||||||
pub fn get_invoices(state: State<AppState>) -> Result<Vec<Invoice>, String> {
|
pub fn get_invoices(state: State<AppState>) -> Result<Vec<Invoice>, String> {
|
||||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||||
let mut stmt = conn.prepare(
|
let mut stmt = conn.prepare(
|
||||||
"SELECT id, client_id, invoice_number, date, due_date, subtotal, tax_rate, tax_amount, discount, total, notes, status
|
"SELECT id, client_id, invoice_number, date, due_date, subtotal, tax_rate, tax_amount, discount, total, notes, status, template_id
|
||||||
FROM invoices ORDER BY date DESC"
|
FROM invoices ORDER BY date DESC"
|
||||||
).map_err(|e| e.to_string())?;
|
).map_err(|e| e.to_string())?;
|
||||||
let invoices = stmt.query_map([], |row| {
|
let invoices = stmt.query_map([], |row| {
|
||||||
@@ -347,6 +348,7 @@ pub fn get_invoices(state: State<AppState>) -> Result<Vec<Invoice>, String> {
|
|||||||
total: row.get(9)?,
|
total: row.get(9)?,
|
||||||
notes: row.get(10)?,
|
notes: row.get(10)?,
|
||||||
status: row.get(11)?,
|
status: row.get(11)?,
|
||||||
|
template_id: row.get(12)?,
|
||||||
})
|
})
|
||||||
}).map_err(|e| e.to_string())?;
|
}).map_err(|e| e.to_string())?;
|
||||||
invoices.collect::<Result<Vec<_>, _>>().map_err(|e| e.to_string())
|
invoices.collect::<Result<Vec<_>, _>>().map_err(|e| e.to_string())
|
||||||
@@ -357,11 +359,11 @@ pub fn update_invoice(state: State<AppState>, invoice: Invoice) -> Result<(), St
|
|||||||
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE invoices SET client_id = ?1, invoice_number = ?2, date = ?3, due_date = ?4,
|
"UPDATE invoices SET client_id = ?1, invoice_number = ?2, date = ?3, due_date = ?4,
|
||||||
subtotal = ?5, tax_rate = ?6, tax_amount = ?7, discount = ?8, total = ?9, notes = ?10, status = ?11
|
subtotal = ?5, tax_rate = ?6, tax_amount = ?7, discount = ?8, total = ?9, notes = ?10, status = ?11, template_id = ?12
|
||||||
WHERE id = ?12",
|
WHERE id = ?13",
|
||||||
params![invoice.client_id, invoice.invoice_number, invoice.date, invoice.due_date,
|
params![invoice.client_id, invoice.invoice_number, invoice.date, invoice.due_date,
|
||||||
invoice.subtotal, invoice.tax_rate, invoice.tax_amount, invoice.discount,
|
invoice.subtotal, invoice.tax_rate, invoice.tax_amount, invoice.discount,
|
||||||
invoice.total, invoice.notes, invoice.status, invoice.id],
|
invoice.total, invoice.notes, invoice.status, invoice.template_id, invoice.id],
|
||||||
).map_err(|e| e.to_string())?;
|
).map_err(|e| e.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -373,6 +375,67 @@ pub fn delete_invoice(state: State<AppState>, id: i64) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn update_invoice_template(state: State<AppState>, id: i64, template_id: String) -> Result<(), String> {
|
||||||
|
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE invoices SET template_id = ?1 WHERE id = ?2",
|
||||||
|
params![template_id, id],
|
||||||
|
).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoice items
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct InvoiceItem {
|
||||||
|
pub id: Option<i64>,
|
||||||
|
pub invoice_id: i64,
|
||||||
|
pub description: String,
|
||||||
|
pub quantity: f64,
|
||||||
|
pub rate: f64,
|
||||||
|
pub amount: f64,
|
||||||
|
pub time_entry_id: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_invoice_items(state: State<AppState>, invoice_id: i64) -> Result<Vec<InvoiceItem>, String> {
|
||||||
|
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT id, invoice_id, description, quantity, rate, amount, time_entry_id
|
||||||
|
FROM invoice_items WHERE invoice_id = ?1 ORDER BY id"
|
||||||
|
).map_err(|e| e.to_string())?;
|
||||||
|
let items = stmt.query_map(params![invoice_id], |row| {
|
||||||
|
Ok(InvoiceItem {
|
||||||
|
id: Some(row.get(0)?),
|
||||||
|
invoice_id: row.get(1)?,
|
||||||
|
description: row.get(2)?,
|
||||||
|
quantity: row.get(3)?,
|
||||||
|
rate: row.get(4)?,
|
||||||
|
amount: row.get(5)?,
|
||||||
|
time_entry_id: row.get(6)?,
|
||||||
|
})
|
||||||
|
}).map_err(|e| e.to_string())?;
|
||||||
|
items.collect::<Result<Vec<_>, _>>().map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn create_invoice_item(state: State<AppState>, item: InvoiceItem) -> Result<i64, String> {
|
||||||
|
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO invoice_items (invoice_id, description, quantity, rate, amount, time_entry_id)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||||
|
params![item.invoice_id, item.description, item.quantity, item.rate, item.amount, item.time_entry_id],
|
||||||
|
).map_err(|e| e.to_string())?;
|
||||||
|
Ok(conn.last_insert_rowid())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn delete_invoice_items(state: State<AppState>, invoice_id: i64) -> Result<(), String> {
|
||||||
|
let conn = state.db.lock().map_err(|e| e.to_string())?;
|
||||||
|
conn.execute("DELETE FROM invoice_items WHERE invoice_id = ?1", params![invoice_id]).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// Settings commands
|
// Settings commands
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_settings(state: State<AppState>) -> Result<std::collections::HashMap<String, String>, String> {
|
pub fn get_settings(state: State<AppState>) -> Result<std::collections::HashMap<String, String>, String> {
|
||||||
@@ -1053,6 +1116,12 @@ pub fn import_json_data(state: State<AppState>, data: serde_json::Value) -> Resu
|
|||||||
Ok(counts)
|
Ok(counts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File save command (bypasses fs plugin scope for user-selected paths)
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn save_binary_file(path: String, data: Vec<u8>) -> Result<(), String> {
|
||||||
|
std::fs::write(&path, &data).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
// Mini timer window commands
|
// Mini timer window commands
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_mini_timer(app: tauri::AppHandle) -> Result<(), String> {
|
pub fn open_mini_timer(app: tauri::AppHandle) -> Result<(), String> {
|
||||||
|
|||||||
@@ -111,6 +111,22 @@ pub fn init_db(conn: &Connection) -> Result<(), rusqlite::Error> {
|
|||||||
[],
|
[],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Migrate invoices table — add template_id column (safe to re-run)
|
||||||
|
let invoice_migrations = [
|
||||||
|
"ALTER TABLE invoices ADD COLUMN template_id TEXT DEFAULT 'clean'",
|
||||||
|
];
|
||||||
|
for sql in &invoice_migrations {
|
||||||
|
match conn.execute(sql, []) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
let msg = e.to_string();
|
||||||
|
if !msg.contains("duplicate column") {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE TABLE IF NOT EXISTS invoice_items (
|
"CREATE TABLE IF NOT EXISTS invoice_items (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ pub fn run() {
|
|||||||
commands::get_invoices,
|
commands::get_invoices,
|
||||||
commands::update_invoice,
|
commands::update_invoice,
|
||||||
commands::delete_invoice,
|
commands::delete_invoice,
|
||||||
|
commands::update_invoice_template,
|
||||||
|
commands::get_invoice_items,
|
||||||
|
commands::create_invoice_item,
|
||||||
|
commands::delete_invoice_items,
|
||||||
commands::get_settings,
|
commands::get_settings,
|
||||||
commands::update_settings,
|
commands::update_settings,
|
||||||
commands::export_data,
|
commands::export_data,
|
||||||
@@ -83,6 +87,7 @@ pub fn run() {
|
|||||||
commands::get_timesheet_data,
|
commands::get_timesheet_data,
|
||||||
commands::import_entries,
|
commands::import_entries,
|
||||||
commands::import_json_data,
|
commands::import_json_data,
|
||||||
|
commands::save_binary_file,
|
||||||
commands::open_mini_timer,
|
commands::open_mini_timer,
|
||||||
commands::close_mini_timer,
|
commands::close_mini_timer,
|
||||||
])
|
])
|
||||||
|
|||||||
Reference in New Issue
Block a user