style(src-tauri): cargo fmt
Pure mechanical rustfmt reformat (default config, no rustfmt.toml).
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
use super::assets::relative_asset_path;
|
||||
use super::payments::{derive_summary, summary_for, today_ist};
|
||||
use super::series::{validate_series_format, MAX_NUMBER_LEN};
|
||||
use super::settings::{map_bank, map_settings, SETTINGS_COLS};
|
||||
use crate::db::format_number;
|
||||
use crate::gst::{self, TaxType};
|
||||
use super::payments::{derive_summary, summary_for, today_ist};
|
||||
use crate::models::{
|
||||
BankAccount, DraftSummary, Invoice, InvoiceInput, InvoiceItem, InvoiceSummary, PaymentSummary, Settings,
|
||||
BankAccount, DraftSummary, Invoice, InvoiceInput, InvoiceItem, InvoiceSummary, PaymentSummary,
|
||||
Settings,
|
||||
};
|
||||
use crate::AppState;
|
||||
use chrono::NaiveDate;
|
||||
@@ -140,7 +141,9 @@ fn validate_items(items: &[InvoiceItem]) -> Result<(), String> {
|
||||
match item.mode.as_str() {
|
||||
"rate" => {
|
||||
if !finite_ok(item.rate) || !item.quantity.is_finite() || item.quantity < 0.0 {
|
||||
return Err(format!("Line {n}: rate and quantity must be positive numbers"));
|
||||
return Err(format!(
|
||||
"Line {n}: rate and quantity must be positive numbers"
|
||||
));
|
||||
}
|
||||
if item.quantity > MAX_LINE_RUPEES {
|
||||
return Err(format!("Line {n}: quantity is too large"));
|
||||
@@ -271,8 +274,9 @@ pub fn issue_invoice_impl(
|
||||
.map_err(db)?
|
||||
.ok_or_else(|| "No active invoice series. Create one under Series.".to_string())?;
|
||||
|
||||
validate_series_format(&prefix, padding)
|
||||
.map_err(|e| format!("The active invoice series cannot be used: {e}. Start a new series."))?;
|
||||
validate_series_format(&prefix, padding).map_err(|e| {
|
||||
format!("The active invoice series cannot be used: {e}. Start a new series.")
|
||||
})?;
|
||||
let number = format_number(&prefix, padding, next_number);
|
||||
if number.len() > MAX_NUMBER_LEN {
|
||||
return Err(format!(
|
||||
@@ -296,10 +300,16 @@ pub fn issue_invoice_impl(
|
||||
};
|
||||
let pos = input.place_of_supply_state_code.trim().to_string();
|
||||
if !pos.is_empty() && !gst::state_exists(&pos) {
|
||||
return Err(format!("Place of supply \"{pos}\" is not a valid GST state code"));
|
||||
return Err(format!(
|
||||
"Place of supply \"{pos}\" is not a valid GST state code"
|
||||
));
|
||||
}
|
||||
|
||||
let derived = gst::derive_tax_type(&settings.gst_registration, &settings.vendor_state_code, &pos);
|
||||
let derived = gst::derive_tax_type(
|
||||
&settings.gst_registration,
|
||||
&settings.vendor_state_code,
|
||||
&pos,
|
||||
);
|
||||
if input.tax_type != derived.as_str() {
|
||||
return Err(format!(
|
||||
"Tax type \"{}\" does not match \"{}\", which follows from supplier state {} and place of supply {}",
|
||||
@@ -309,7 +319,11 @@ pub fn issue_invoice_impl(
|
||||
if pos.is_empty() { &settings.vendor_state_code } else { &pos },
|
||||
));
|
||||
}
|
||||
let tax_rate = if derived == TaxType::None { 0.0 } else { input.tax_rate };
|
||||
let tax_rate = if derived == TaxType::None {
|
||||
0.0
|
||||
} else {
|
||||
input.tax_rate
|
||||
};
|
||||
if !tax_rate.is_finite() || !(0.0..=100.0).contains(&tax_rate) {
|
||||
return Err("Tax rate must be between 0 and 100".into());
|
||||
}
|
||||
@@ -330,9 +344,9 @@ pub fn issue_invoice_impl(
|
||||
let snapshot = vendor_snapshot(&settings);
|
||||
let bank = bank_snapshot(&tx, input.bank_account_id)?;
|
||||
let signature_path = match input.signature_path.as_deref().map(str::trim) {
|
||||
Some(p) if !p.is_empty() => Some(
|
||||
relative_asset_path(data_dir, p).map_err(|e| format!("Signature image: {e}"))?,
|
||||
),
|
||||
Some(p) if !p.is_empty() => {
|
||||
Some(relative_asset_path(data_dir, p).map_err(|e| format!("Signature image: {e}"))?)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let prefs = serde_json::to_string(render_prefs).map_err(|e| e.to_string())?;
|
||||
@@ -445,8 +459,11 @@ pub fn issue_invoice_impl(
|
||||
.map_err(db)?;
|
||||
|
||||
if let Some(draft_id) = input.draft_id {
|
||||
tx.execute("DELETE FROM invoice_drafts WHERE id = ?1", params![draft_id])
|
||||
.map_err(db)?;
|
||||
tx.execute(
|
||||
"DELETE FROM invoice_drafts WHERE id = ?1",
|
||||
params![draft_id],
|
||||
)
|
||||
.map_err(db)?;
|
||||
}
|
||||
|
||||
tx.commit().map_err(db)?;
|
||||
@@ -464,9 +481,17 @@ pub fn issue_invoice(
|
||||
}
|
||||
|
||||
/// Issued invoices are never deleted: cancelling keeps the row and its number.
|
||||
pub fn cancel_invoice_impl(conn: &mut Connection, id: i64, reason: &str) -> Result<Invoice, String> {
|
||||
pub fn cancel_invoice_impl(
|
||||
conn: &mut Connection,
|
||||
id: i64,
|
||||
reason: &str,
|
||||
) -> Result<Invoice, String> {
|
||||
let payments: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM payments WHERE invoice_id = ?1", params![id], |r| r.get(0))
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM payments WHERE invoice_id = ?1",
|
||||
params![id],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
if payments > 0 {
|
||||
return Err("This invoice has payments recorded. Delete them before cancelling it.".into());
|
||||
@@ -482,7 +507,11 @@ pub fn cancel_invoice_impl(conn: &mut Connection, id: i64, reason: &str) -> Resu
|
||||
.map_err(|e| e.to_string())?;
|
||||
if changed == 0 {
|
||||
let status: Option<String> = conn
|
||||
.query_row("SELECT status FROM invoices WHERE id = ?1", params![id], |r| r.get(0))
|
||||
.query_row(
|
||||
"SELECT status FROM invoices WHERE id = ?1",
|
||||
params![id],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.optional()
|
||||
.map_err(|e| e.to_string())?;
|
||||
return Err(match status {
|
||||
@@ -525,7 +554,14 @@ pub fn list_invoices_impl(conn: &Connection) -> Result<Vec<InvoiceSummary>, Stri
|
||||
let status: String = row.get(6)?;
|
||||
let due_date: String = row.get(3)?;
|
||||
let total_paise = gst::rupees_to_paise(total);
|
||||
let payment = derive_summary(&status, total_paise, row.get(9)?, row.get(10)?, &due_date, &today);
|
||||
let payment = derive_summary(
|
||||
&status,
|
||||
total_paise,
|
||||
row.get(9)?,
|
||||
row.get(10)?,
|
||||
&due_date,
|
||||
&today,
|
||||
);
|
||||
Ok(InvoiceSummary {
|
||||
id: row.get(0)?,
|
||||
number: row.get(1)?,
|
||||
@@ -566,7 +602,8 @@ pub struct InvoiceLedgerRow {
|
||||
}
|
||||
|
||||
pub fn list_invoice_ledger_impl(conn: &Connection) -> Result<Vec<InvoiceLedgerRow>, String> {
|
||||
let mut hsn_by_invoice: std::collections::HashMap<i64, Vec<String>> = std::collections::HashMap::new();
|
||||
let mut hsn_by_invoice: std::collections::HashMap<i64, Vec<String>> =
|
||||
std::collections::HashMap::new();
|
||||
{
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT invoice_id, TRIM(hsn_sac) FROM invoice_items ORDER BY invoice_id, sort_order, id")
|
||||
@@ -666,11 +703,17 @@ pub fn save_draft_impl(
|
||||
|
||||
pub fn list_drafts_impl(conn: &Connection) -> Result<Vec<DraftSummary>, String> {
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT id, payload, updated_at FROM invoice_drafts ORDER BY updated_at DESC, id DESC")
|
||||
.prepare(
|
||||
"SELECT id, payload, updated_at FROM invoice_drafts ORDER BY updated_at DESC, id DESC",
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let rows = stmt
|
||||
.query_map([], |r| {
|
||||
Ok((r.get::<_, i64>(0)?, r.get::<_, String>(1)?, r.get::<_, String>(2)?))
|
||||
Ok((
|
||||
r.get::<_, i64>(0)?,
|
||||
r.get::<_, String>(1)?,
|
||||
r.get::<_, String>(2)?,
|
||||
))
|
||||
})
|
||||
.map_err(|e| e.to_string())?
|
||||
.collect::<rusqlite::Result<Vec<_>>>()
|
||||
@@ -682,7 +725,10 @@ pub fn list_drafts_impl(conn: &Connection) -> Result<Vec<DraftSummary>, String>
|
||||
DraftSummary {
|
||||
id,
|
||||
updated_at,
|
||||
client_name: json.get("clientName").and_then(|v| v.as_str()).map(String::from),
|
||||
client_name: json
|
||||
.get("clientName")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from),
|
||||
total: json.get("total").and_then(|v| v.as_f64()),
|
||||
}
|
||||
})
|
||||
@@ -691,7 +737,11 @@ pub fn list_drafts_impl(conn: &Connection) -> Result<Vec<DraftSummary>, String>
|
||||
|
||||
pub fn get_draft_impl(conn: &Connection, id: i64) -> Result<serde_json::Value, String> {
|
||||
let text: String = conn
|
||||
.query_row("SELECT payload FROM invoice_drafts WHERE id = ?1", params![id], |r| r.get(0))
|
||||
.query_row(
|
||||
"SELECT payload FROM invoice_drafts WHERE id = ?1",
|
||||
params![id],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.optional()
|
||||
.map_err(|e| e.to_string())?
|
||||
.ok_or_else(|| "Draft not found".to_string())?;
|
||||
@@ -765,19 +815,28 @@ mod tests {
|
||||
}
|
||||
|
||||
fn next_number(conn: &Connection) -> i64 {
|
||||
conn.query_row("SELECT next_number FROM invoice_series WHERE is_active = 1", [], |r| r.get(0))
|
||||
.unwrap()
|
||||
conn.query_row(
|
||||
"SELECT next_number FROM invoice_series WHERE is_active = 1",
|
||||
[],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn invoice_count(conn: &Connection) -> i64 {
|
||||
conn.query_row("SELECT COUNT(*) FROM invoices", [], |r| r.get(0)).unwrap()
|
||||
conn.query_row("SELECT COUNT(*) FROM invoices", [], |r| r.get(0))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ledger_rows_carry_the_tax_split_in_paise_and_distinct_hsn() {
|
||||
let mut conn = registered();
|
||||
let first = issue(&mut conn, input(json!({"placeOfSupplyStateCode": "27"}))).unwrap();
|
||||
conn.execute("UPDATE invoice_items SET hsn_sac = ' 9983 ' WHERE invoice_id = ?1", params![first.id]).unwrap();
|
||||
conn.execute(
|
||||
"UPDATE invoice_items SET hsn_sac = ' 9983 ' WHERE invoice_id = ?1",
|
||||
params![first.id],
|
||||
)
|
||||
.unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO invoice_items (invoice_id, description, amount, sort_order, hsn_sac) VALUES (?1, 'b', 1, 5, '9983'), (?1, 'c', 1, 6, '9984'), (?1, 'd', 1, 7, '')",
|
||||
params![first.id],
|
||||
@@ -791,7 +850,10 @@ mod tests {
|
||||
assert_eq!(row.doc_type, "tax_invoice");
|
||||
assert_eq!(row.hsn_sac, vec!["9983".to_string(), "9984".to_string()]);
|
||||
assert_eq!(row.taxable_paise, 731_000);
|
||||
assert_eq!((row.cgst_paise, row.sgst_paise, row.igst_paise), (65_790, 65_790, 0));
|
||||
assert_eq!(
|
||||
(row.cgst_paise, row.sgst_paise, row.igst_paise),
|
||||
(65_790, 65_790, 0)
|
||||
);
|
||||
assert!(!row.reverse_charge);
|
||||
}
|
||||
|
||||
@@ -862,7 +924,10 @@ mod tests {
|
||||
input(json!({"placeOfSupplyStateCode": "29", "taxType": "cgst_sgst"})),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.starts_with("Tax type \"cgst_sgst\" does not match \"igst\""), "{err}");
|
||||
assert!(
|
||||
err.starts_with("Tax type \"cgst_sgst\" does not match \"igst\""),
|
||||
"{err}"
|
||||
);
|
||||
let err = issue(&mut conn, input(json!({"taxType": "none"}))).unwrap_err();
|
||||
assert!(err.contains("does not match"), "{err}");
|
||||
assert_eq!(next_number(&conn), 1);
|
||||
@@ -872,7 +937,11 @@ mod tests {
|
||||
#[test]
|
||||
fn unregistered_supplier_issues_a_plain_invoice_without_tax() {
|
||||
let mut conn = crate::db::open_in_memory().unwrap();
|
||||
conn.execute("UPDATE app_settings SET vendor_gstin = '27AAPFU0939F1ZV'", []).unwrap();
|
||||
conn.execute(
|
||||
"UPDATE app_settings SET vendor_gstin = '27AAPFU0939F1ZV'",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
let inv = issue(
|
||||
&mut conn,
|
||||
input(json!({"taxType": "none", "taxRate": 18.0, "placeOfSupplyStateCode": "07"})),
|
||||
@@ -929,7 +998,8 @@ mod tests {
|
||||
assert!(inv.amount_in_words.contains("Eight Thousand"));
|
||||
|
||||
// Later settings edits do not touch the stored snapshot.
|
||||
conn.execute("UPDATE app_settings SET vendor_name = 'Renamed'", []).unwrap();
|
||||
conn.execute("UPDATE app_settings SET vendor_name = 'Renamed'", [])
|
||||
.unwrap();
|
||||
let again = fetch_invoice(&conn, inv.id).unwrap();
|
||||
assert_eq!(again.vendor_snapshot, inv.vendor_snapshot);
|
||||
}
|
||||
@@ -937,7 +1007,9 @@ mod tests {
|
||||
#[test]
|
||||
fn bank_snapshot_is_built_from_the_bank_row() {
|
||||
let mut conn = registered();
|
||||
let bank_id: i64 = conn.query_row("SELECT id FROM bank_accounts", [], |r| r.get(0)).unwrap();
|
||||
let bank_id: i64 = conn
|
||||
.query_row("SELECT id FROM bank_accounts", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
let inv = issue(&mut conn, input(json!({"bankAccountId": bank_id}))).unwrap();
|
||||
let bank: serde_json::Value = serde_json::from_str(&inv.bank_snapshot).unwrap();
|
||||
assert_eq!(bank["bankName"], "Test Bank");
|
||||
@@ -956,15 +1028,22 @@ mod tests {
|
||||
params![format!("INV/{year}-001")],
|
||||
)
|
||||
.unwrap();
|
||||
conn.execute("INSERT INTO invoice_drafts (payload, updated_at) VALUES ('{}', 'now')", [])
|
||||
.unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO invoice_drafts (payload, updated_at) VALUES ('{}', 'now')",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
let err = issue(&mut conn, input(json!({"saveClient": true, "draftId": 1}))).unwrap_err();
|
||||
assert!(err.contains("already exists"), "{err}");
|
||||
assert_eq!(next_number(&conn), 1);
|
||||
assert_eq!(invoice_count(&conn), 1);
|
||||
// The client insert and the draft delete were rolled back too.
|
||||
let clients: i64 = conn.query_row("SELECT COUNT(*) FROM clients", [], |r| r.get(0)).unwrap();
|
||||
let drafts: i64 = conn.query_row("SELECT COUNT(*) FROM invoice_drafts", [], |r| r.get(0)).unwrap();
|
||||
let clients: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM clients", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
let drafts: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM invoice_drafts", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
assert_eq!((clients, drafts), (0, 1));
|
||||
}
|
||||
|
||||
@@ -979,10 +1058,19 @@ mod tests {
|
||||
json!({"items": [{"mode": "rate", "rate": 0.0, "quantity": 2.0}]}),
|
||||
"greater than zero",
|
||||
),
|
||||
(json!({"items": [{"mode": "fixed", "amount": -1.0}]}), "zero or more"),
|
||||
(json!({"items": [{"mode": "weird", "amount": 1.0}]}), "unknown line type"),
|
||||
(
|
||||
json!({"items": [{"mode": "fixed", "amount": -1.0}]}),
|
||||
"zero or more",
|
||||
),
|
||||
(
|
||||
json!({"items": [{"mode": "weird", "amount": 1.0}]}),
|
||||
"unknown line type",
|
||||
),
|
||||
(json!({"clientGstin": "29ABCDE1234F1Z5"}), "Client GSTIN"),
|
||||
(json!({"placeOfSupplyStateCode": "99"}), "not a valid GST state code"),
|
||||
(
|
||||
json!({"placeOfSupplyStateCode": "99"}),
|
||||
"not a valid GST state code",
|
||||
),
|
||||
(json!({"taxRate": 150.0}), "between 0 and 100"),
|
||||
(json!({"signaturePath": "../voiced.db"}), "Signature image"),
|
||||
];
|
||||
@@ -1001,31 +1089,58 @@ mod tests {
|
||||
#[test]
|
||||
fn supplier_settings_are_validated() {
|
||||
let mut conn = registered();
|
||||
conn.execute("UPDATE app_settings SET vendor_gstin = '27AAPFU0939F1Z5'", []).unwrap();
|
||||
assert!(issue(&mut conn, input(json!({}))).unwrap_err().contains("check digit"));
|
||||
conn.execute("UPDATE app_settings SET vendor_gstin = ''", []).unwrap();
|
||||
assert!(issue(&mut conn, input(json!({}))).unwrap_err().contains("needs a GSTIN"));
|
||||
conn.execute("UPDATE app_settings SET vendor_gstin = '29AAGCB7383J1Z4'", []).unwrap();
|
||||
assert!(issue(&mut conn, input(json!({}))).unwrap_err().contains("state code 29"));
|
||||
conn.execute(
|
||||
"UPDATE app_settings SET vendor_gstin = '27AAPFU0939F1Z5'",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(issue(&mut conn, input(json!({})))
|
||||
.unwrap_err()
|
||||
.contains("check digit"));
|
||||
conn.execute("UPDATE app_settings SET vendor_gstin = ''", [])
|
||||
.unwrap();
|
||||
assert!(issue(&mut conn, input(json!({})))
|
||||
.unwrap_err()
|
||||
.contains("needs a GSTIN"));
|
||||
conn.execute(
|
||||
"UPDATE app_settings SET vendor_gstin = '29AAGCB7383J1Z4'",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(issue(&mut conn, input(json!({})))
|
||||
.unwrap_err()
|
||||
.contains("state code 29"));
|
||||
conn.execute(
|
||||
"UPDATE app_settings SET vendor_gstin = '27AAPFU0939F1ZV', vendor_pan = 'ABCDE1234F'",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(issue(&mut conn, input(json!({}))).unwrap_err().contains("PAN"));
|
||||
conn.execute("UPDATE app_settings SET vendor_pan = 'AAPFU0939F', vendor_state_code = '99'", [])
|
||||
.unwrap();
|
||||
assert!(issue(&mut conn, input(json!({}))).unwrap_err().contains("state code"));
|
||||
assert!(issue(&mut conn, input(json!({})))
|
||||
.unwrap_err()
|
||||
.contains("PAN"));
|
||||
conn.execute(
|
||||
"UPDATE app_settings SET vendor_pan = 'AAPFU0939F', vendor_state_code = '99'",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(issue(&mut conn, input(json!({})))
|
||||
.unwrap_err()
|
||||
.contains("state code"));
|
||||
assert_eq!(next_number(&conn), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_series_that_break_the_rules_are_refused() {
|
||||
let mut conn = registered();
|
||||
conn.execute("UPDATE invoice_series SET prefix = 'AP 2026 '", []).unwrap();
|
||||
conn.execute("UPDATE invoice_series SET prefix = 'AP 2026 '", [])
|
||||
.unwrap();
|
||||
let err = issue(&mut conn, input(json!({}))).unwrap_err();
|
||||
assert!(err.contains("active invoice series"), "{err}");
|
||||
conn.execute("UPDATE invoice_series SET prefix = 'ABCDEFGHIJKLMN', padding = 3", []).unwrap();
|
||||
conn.execute(
|
||||
"UPDATE invoice_series SET prefix = 'ABCDEFGHIJKLMN', padding = 3",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(issue(&mut conn, input(json!({}))).is_err());
|
||||
// A counter that outgrows its padding can push the number past 16 characters.
|
||||
conn.execute(
|
||||
@@ -1033,7 +1148,9 @@ mod tests {
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(issue(&mut conn, input(json!({}))).unwrap_err().contains("longer than 16"));
|
||||
assert!(issue(&mut conn, input(json!({})))
|
||||
.unwrap_err()
|
||||
.contains("longer than 16"));
|
||||
assert_eq!(invoice_count(&conn), 0);
|
||||
}
|
||||
|
||||
@@ -1061,7 +1178,10 @@ mod tests {
|
||||
&json!({"theme": "plain"}),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(inv.signature_path.as_deref(), Some("assets/signature-1.png"));
|
||||
assert_eq!(
|
||||
inv.signature_path.as_deref(),
|
||||
Some("assets/signature-1.png")
|
||||
);
|
||||
let prefs: String = conn
|
||||
.query_row("SELECT render_prefs FROM invoices", [], |r| r.get(0))
|
||||
.unwrap();
|
||||
@@ -1082,8 +1202,14 @@ mod tests {
|
||||
|
||||
let err = cancel_invoice_impl(&mut conn, inv.id, "again").unwrap_err();
|
||||
assert!(err.contains("cancelled"), "{err}");
|
||||
assert!(cancel_invoice_impl(&mut conn, 999, "").unwrap_err().contains("not found"));
|
||||
conn.execute("UPDATE invoices SET status = 'draft' WHERE id = ?1", params![inv.id]).unwrap();
|
||||
assert!(cancel_invoice_impl(&mut conn, 999, "")
|
||||
.unwrap_err()
|
||||
.contains("not found"));
|
||||
conn.execute(
|
||||
"UPDATE invoices SET status = 'draft' WHERE id = ?1",
|
||||
params![inv.id],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(cancel_invoice_impl(&mut conn, inv.id, "").is_err());
|
||||
|
||||
// The number is never reused.
|
||||
@@ -1095,10 +1221,16 @@ mod tests {
|
||||
#[test]
|
||||
fn drafts_round_trip() {
|
||||
let conn = crate::db::open_in_memory().unwrap();
|
||||
let a = save_draft_impl(&conn, None, &json!({"clientName": "Acme", "total": 1180.5})).unwrap();
|
||||
let a =
|
||||
save_draft_impl(&conn, None, &json!({"clientName": "Acme", "total": 1180.5})).unwrap();
|
||||
let b = save_draft_impl(&conn, None, &json!({"notes": "no client yet"})).unwrap();
|
||||
assert_ne!(a, b);
|
||||
let c = save_draft_impl(&conn, Some(a), &json!({"clientName": "Acme 2", "total": 10})).unwrap();
|
||||
let c = save_draft_impl(
|
||||
&conn,
|
||||
Some(a),
|
||||
&json!({"clientName": "Acme 2", "total": 10}),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(c, a);
|
||||
assert!(save_draft_impl(&conn, Some(999), &json!({})).is_err());
|
||||
|
||||
@@ -1111,7 +1243,8 @@ mod tests {
|
||||
let blank = list.iter().find(|d| d.id == b).unwrap();
|
||||
assert_eq!((blank.client_name.clone(), blank.total), (None, None));
|
||||
|
||||
conn.execute("DELETE FROM invoice_drafts WHERE id = ?1", params![a]).unwrap();
|
||||
conn.execute("DELETE FROM invoice_drafts WHERE id = ?1", params![a])
|
||||
.unwrap();
|
||||
assert!(get_draft_impl(&conn, a).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user